I'm trying to get a ball to bounce and roll around inside another ball, ultimately based on the accelerometer. There are countless tutorials out there to detect circle collisions and such, and they are indeed marginally helpful. Unfortunately, none that I have found deal with circle-inside-of-a-circle collision only circles bouncing around in a rectangle view.
Several helpful URLs are, where I got most of this code:
http://xiangchen.wordpress.com/2011/12/17/an-android-accelerometer-example/
circle-circle collision
..but again, this isn't quite what I'm after.
I want to get a circle to bounce and roll around inside of another circle. Then, after that, I will want the inner ball to roll down the inside of the outer circle at the right time as the velocity lessens, not simply bounce to the bottom. Am I articulating that clearly? And finally, the bounce angle will need to be adjusted I'm sure so I will ultimately need to figure out how to do that as well.
My code is a mess because I've tried so many things, so in particular, the commented block isn't even close to what it needs to be I don't think. It is just my latest attempt.
Anyone know a little something about this and willing to give me a hand? I would appreciate it.
Edit: This guy is very close to what I'm after, but I am having trouble making sense of it and converting the selected answer into Java. Help? https://gamedev.stackexchange.com/questions/29650/circle-inside-circle-collision
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Main extends Activity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private ShapeView mShapeView;
private int mWidthScreen;
private int mHeightScreen;
private final float FACTOR_FRICTION = 0.2f; // imaginary friction on the screen
private final float GRAVITY = 9.8f; // acceleration of gravity
private float mAx; // acceleration along x axis
private float mAy; // acceleration along y axis
private final float mDeltaT = 0.5f; // imaginary time interval between each acceleration updates
private static final float OUTERSTROKE = 5;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
mWidthScreen = displaymetrics.widthPixels;
mHeightScreen = displaymetrics.heightPixels;
mShapeView = new ShapeView(this);
mShapeView.initOvalCenter((int) (mWidthScreen * 0.6), (int) (mHeightScreen * 0.6));
setContentView(mShapeView);
}
#Override
public void onSensorChanged(SensorEvent event) {
// obtain the three accelerations from sensors
mAx = event.values[0];
mAy = event.values[1];
float mAz = event.values[2];
// taking into account the frictions
mAx = Math.signum(mAx) * Math.abs(mAx) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
mAy = Math.signum(mAy) * Math.abs(mAy) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
protected void onResume() {
super.onResume();
// start sensor sensing
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onPause() {
super.onPause();
// stop sensor sensing
mSensorManager.unregisterListener(this);
}
// the view that renders the ball
private class ShapeView extends SurfaceView implements SurfaceHolder.Callback {
private final int BALLRADIUS = 100;
private final float FACTOR_BOUNCEBACK = 0.15f;
private final int OUTERRADIUS = 300;
private Point ballCenter = new Point();
private RectF mRectF;
private final Paint mPaint;
private ShapeThread mThread;
private float mVx;
private float mVy;
private final Paint outerPaint;
private RectF outerBounds;
private Point outerCenter;
private final double outerDiagonal;
public ShapeView(Context context) {
super(context);
getHolder().addCallback(this);
mThread = new ShapeThread(getHolder(), this);
setFocusable(true);
mPaint = new Paint();
mPaint.setColor(0xFFFFFFFF);
mPaint.setAlpha(192);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
outerPaint= new Paint();
outerPaint.setColor(0xFFFFFFFF);
outerPaint.setAlpha(255);
outerPaint.setStrokeWidth(OUTERSTROKE);
outerPaint.setStyle(Paint.Style.STROKE);
outerPaint.setAntiAlias(true);
mRectF = new RectF();
outerDiagonal= Math.pow(BALLRADIUS - OUTERRADIUS, 2);
}
public void initOvalCenter(int x, int y) {
mShapeView.setOvalCenter(x, y);
outerCenter= new Point(x, y);
outerBounds = new RectF(x - OUTERRADIUS, y - OUTERRADIUS, x + OUTERRADIUS, y + OUTERRADIUS);
}
public boolean setOvalCenter(int x, int y) {
ballCenter.set(x, y);
return true;
}
public boolean updateOvalCenter() {
/*-------
* This is where the trouble is, currently. How do I "snap" the inner circle back into the
* outer circle? Or even better, how do I keep it from crossing the line to bring with?
*/
mVx -= mAx * mDeltaT;
mVy += mAy * mDeltaT;
Point newBallCenter = new Point();
newBallCenter.x = ballCenter.x + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
newBallCenter.y = ballCenter.y + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));
double distance = Math.sqrt(Math.pow(newBallCenter.x - outerCenter.x, 2) + Math.pow(newBallCenter.y - outerCenter.y, 2));
if(distance >= OUTERRADIUS - BALLRADIUS) {
mVx = -mVx * FACTOR_BOUNCEBACK;
mVy = -mVy * FACTOR_BOUNCEBACK;
newBallCenter.x = ballCenter.x + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
newBallCenter.y = ballCenter.y + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));
}
ballCenter.x = newBallCenter.x;
ballCenter.y = newBallCenter.y;
return true;
}
protected void doDraw(Canvas canvas) {
if (mRectF != null && canvas != null) {
mRectF.set(ballCenter.x - BALLRADIUS, ballCenter.y - BALLRADIUS, ballCenter.x + BALLRADIUS, ballCenter.y + BALLRADIUS);
canvas.drawColor(0XFF000000);
canvas.drawOval(mRectF, mPaint);
canvas.drawOval(outerBounds, outerPaint);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException ignored) { }
}
}
}
class ShapeThread extends Thread {
private final SurfaceHolder mSurfaceHolder;
private ShapeView mShapeView;
private boolean mRun = false;
public ShapeThread(SurfaceHolder surfaceHolder, ShapeView shapeView) {
mSurfaceHolder = surfaceHolder;
mShapeView = shapeView;
}
public void setRunning(boolean run) {
mRun = run;
}
#Override
public void run() {
Canvas c;
while (mRun) {
mShapeView.updateOvalCenter();
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
mShapeView.doDraw(c);
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
I thought i would answer this to at least help you even if it doesnt answer your question completely.
Below is your code with changes to a few things
i fixed your issue with snapping the inner circle back when a collision occurs.
Basically you need to move the inner circle back by one frame once a collision has occured. i think you tried doing this however you were resetting these values just before the collision check. I also added a little check to the velocity to say if it was less than 0.5 then just move the inner circle to the last frame without a bounce to get rid of the judering bouncing effect when it is trying to settle.
package com.test.circleincircle;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class Main extends Activity implements SensorEventListener {
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private ShapeView mShapeView;
private int mWidthScreen;
private int mHeightScreen;
private final float FACTOR_FRICTION = 0.2f; // imaginary friction on the screen
private final float GRAVITY = 9.8f; // acceleration of gravity
private float mAx; // acceleration along x axis
private float mAy; // acceleration along y axis
private final float mDeltaT = 0.5f; // imaginary time interval between each acceleration updates
private int previousInnerX, previousInnerY;
private static final float OUTERSTROKE = 5;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
mWidthScreen = displaymetrics.widthPixels;
mHeightScreen = displaymetrics.heightPixels;
mShapeView = new ShapeView(this);
mShapeView.initOvalCenter((int) (mWidthScreen * 0.6), (int) (mHeightScreen * 0.6));
setContentView(mShapeView);
}
#Override
public void onSensorChanged(SensorEvent event) {
// obtain the three accelerations from sensors
mAx = event.values[0];
mAy = event.values[1];
float mAz = event.values[2];
// taking into account the frictions
mAx = Math.signum(mAx) * Math.abs(mAx) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
mAy = Math.signum(mAy) * Math.abs(mAy) * (1 - FACTOR_FRICTION * Math.abs(mAz) / GRAVITY);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
protected void onResume() {
super.onResume();
// start sensor sensing
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onPause() {
super.onPause();
// stop sensor sensing
mSensorManager.unregisterListener(this);
}
// the view that renders the ball
private class ShapeView extends SurfaceView implements SurfaceHolder.Callback {
private final int BALLRADIUS = 100;
private final float FACTOR_BOUNCEBACK = 0.45f;
private final int OUTERRADIUS = 300;
private Point ballCenter = new Point();
private RectF mRectF;
private final Paint mPaint;
private ShapeThread mThread;
private float mVx;
private float mVy;
private final Paint outerPaint;
private RectF outerBounds;
private Point outerCenter;
private final double outerDiagonal;
public ShapeView(Context context) {
super(context);
getHolder().addCallback(this);
mThread = new ShapeThread(getHolder(), this);
setFocusable(true);
mPaint = new Paint();
mPaint.setColor(0xFFFFFFFF);
mPaint.setAlpha(192);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
outerPaint= new Paint();
outerPaint.setColor(0xFFFFFFFF);
outerPaint.setAlpha(255);
outerPaint.setStrokeWidth(OUTERSTROKE);
outerPaint.setStyle(Paint.Style.STROKE);
outerPaint.setAntiAlias(true);
mRectF = new RectF();
outerDiagonal= Math.pow(BALLRADIUS - OUTERRADIUS, 2);
}
public void initOvalCenter(int x, int y) {
mShapeView.setOvalCenter(x, y);
outerCenter= new Point(x, y);
outerBounds = new RectF(x - OUTERRADIUS, y - OUTERRADIUS, x + OUTERRADIUS, y + OUTERRADIUS);
}
public boolean setOvalCenter(int x, int y) {
ballCenter.set(x, y);
return true;
}
public boolean updateOvalCenter() {
Point newBallCenter = new Point();
newBallCenter.x = ballCenter.x + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
newBallCenter.y = ballCenter.y + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));
double distance = Math.sqrt(Math.pow(newBallCenter.x - outerCenter.x, 2) + Math.pow(newBallCenter.y - outerCenter.y, 2));
if(distance >= OUTERRADIUS - BALLRADIUS) {
mVx = -mVx * FACTOR_BOUNCEBACK;
mVy = -mVy * FACTOR_BOUNCEBACK;
if(Math.abs(mVx) > 0.5)
{
newBallCenter.x = previousInnerX + (int) (mDeltaT * (mVx + 0.5 * mAx * mDeltaT));
newBallCenter.y = previousInnerY + (int) (mDeltaT * (mVy + 0.5 * mAy * mDeltaT));
}
else
{
newBallCenter.x = previousInnerX;
newBallCenter.y = previousInnerY;
}
}
else
{
mVx -= mAx * mDeltaT;
mVy += mAy * mDeltaT;
}
previousInnerX = ballCenter.x;
previousInnerY = ballCenter.y;
ballCenter.x = newBallCenter.x;
ballCenter.y = newBallCenter.y;
return true;
}
protected void doDraw(Canvas canvas) {
if (mRectF != null && canvas != null) {
mRectF.set(ballCenter.x - BALLRADIUS, ballCenter.y - BALLRADIUS, ballCenter.x + BALLRADIUS, ballCenter.y + BALLRADIUS);
canvas.drawColor(0XFF000000);
canvas.drawOval(mRectF, mPaint);
canvas.drawOval(outerBounds, outerPaint);
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException ignored) { }
}
}
}
class ShapeThread extends Thread {
private final SurfaceHolder mSurfaceHolder;
private ShapeView mShapeView;
private boolean mRun = false;
public ShapeThread(SurfaceHolder surfaceHolder, ShapeView shapeView) {
mSurfaceHolder = surfaceHolder;
mShapeView = shapeView;
}
public void setRunning(boolean run) {
mRun = run;
}
#Override
public void run() {
Canvas c;
while (mRun) {
mShapeView.updateOvalCenter();
c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
mShapeView.doDraw(c);
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
}
Adding in the smooth movement of the iner circle rolling within the outer one while also bouncing will be a lot more difficult to implement. The correct way would be to have the inner circle rotating and following the instructions from the question you reference.
Maybe you can ask a seperate question for that part after you are happy with the bouncing.
If anything this may just help you along your journey and hopefully you will be able to add to this.
Related
hey guys I am working on a launcher that's based off the rootless pixel launcher and am trying to change the folder icon to a squircle shape rather than a oval/circle shape. It looks like the launcher is making the folder icon using canvas draw method but I have little experience with this approach usually i'd make a xml shape in reference the shape that way, but when doing research on this it looks like you can not make a squircle xml shape so with that being said here is the class that makes the folder icon:
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.folder;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RadialGradient;
import android.graphics.Region;
import android.graphics.Shader;
import android.support.v4.graphics.ColorUtils;
import android.util.Property;
import android.view.View;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.util.Themes;
/**
* This object represents a FolderIcon preview background. It stores drawing / measurement
* information, handles drawing, and animation (accept state <--> rest state).
*/
public class PreviewBackground {
private static final int CONSUMPTION_ANIMATION_DURATION = 100;
private final PorterDuffXfermode mClipPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
// Create a RadialGradient such that it draws a black circle and then extends with
// transparent. To achieve this, we keep the gradient to black for the range [0, 1) and
// just at the edge quickly change it to transparent.
private final RadialGradient mClipShader = new RadialGradient(0, 0, 1,
new int[] {Color.BLACK, Color.BLACK, Color.TRANSPARENT },
new float[] {0, 0.999f, 1},
Shader.TileMode.CLAMP);
private final PorterDuffXfermode mShadowPorterDuffXfermode
= new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
private RadialGradient mShadowShader = null;
private final Matrix mShaderMatrix = new Matrix();
private final Path mPath = new Path();
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
float mScale = 1f;
private float mColorMultiplier = 1f;
private int mBgColor;
private float mStrokeWidth;
private int mStrokeAlpha = MAX_BG_OPACITY;
private int mShadowAlpha = 255;
private View mInvalidateDelegate;
int previewSize;
int basePreviewOffsetX;
int basePreviewOffsetY;
private CellLayout mDrawingDelegate;
public int delegateCellX;
public int delegateCellY;
// When the PreviewBackground is drawn under an icon (for creating a folder) the border
// should not occlude the icon
public boolean isClipping = true;
// Drawing / animation configurations
private static final float ACCEPT_SCALE_FACTOR = 1.20f;
private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
// Expressed on a scale from 0 to 255.
private static final int BG_OPACITY = 160;
private static final int MAX_BG_OPACITY = 225;
private static final int SHADOW_OPACITY = 40;
private ValueAnimator mScaleAnimator;
private ObjectAnimator mStrokeAlphaAnimator;
private ObjectAnimator mShadowAnimator;
private static final Property<PreviewBackground, Integer> STROKE_ALPHA =
new Property<PreviewBackground, Integer>(Integer.class, "strokeAlpha") {
#Override
public Integer get(PreviewBackground previewBackground) {
return previewBackground.mStrokeAlpha;
}
#Override
public void set(PreviewBackground previewBackground, Integer alpha) {
previewBackground.mStrokeAlpha = alpha;
previewBackground.invalidate();
}
};
private static final Property<PreviewBackground, Integer> SHADOW_ALPHA =
new Property<PreviewBackground, Integer>(Integer.class, "shadowAlpha") {
#Override
public Integer get(PreviewBackground previewBackground) {
return previewBackground.mShadowAlpha;
}
#Override
public void set(PreviewBackground previewBackground, Integer alpha) {
previewBackground.mShadowAlpha = alpha;
previewBackground.invalidate();
}
};
public void setup(Launcher launcher, View invalidateDelegate,
int availableSpace, int topPadding) {
mInvalidateDelegate = invalidateDelegate;
mBgColor = Themes.getAttrColor(launcher, android.R.attr.colorPrimary);
DeviceProfile grid = launcher.getDeviceProfile();
final int previewSize = grid.folderIconSizePx;
final int previewPadding = grid.folderIconPreviewPadding;
this.previewSize = (previewSize - 2 * previewPadding);
basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
// Stroke width is 1dp
mStrokeWidth = launcher.getResources().getDisplayMetrics().density;
float radius = getScaledRadius();
float shadowRadius = radius + mStrokeWidth;
int shadowColor = Color.argb(SHADOW_OPACITY, 0, 0, 0);
mShadowShader = new RadialGradient(0, 0, 1,
new int[] {shadowColor, Color.TRANSPARENT},
new float[] {radius / shadowRadius, 1},
Shader.TileMode.CLAMP);
invalidate();
}
int getRadius() {
return previewSize / 2;
}
int getScaledRadius() {
return (int) (mScale * getRadius());
}
int getOffsetX() {
return basePreviewOffsetX - (getScaledRadius() - getRadius());
}
int getOffsetY() {
return basePreviewOffsetY - (getScaledRadius() - getRadius());
}
/**
* Returns the progress of the scale animation, where 0 means the scale is at 1f
* and 1 means the scale is at ACCEPT_SCALE_FACTOR.
*/
float getScaleProgress() {
return (mScale - 1f) / (ACCEPT_SCALE_FACTOR - 1f);
}
void invalidate() {
if (mInvalidateDelegate != null) {
mInvalidateDelegate.invalidate();
}
if (mDrawingDelegate != null) {
mDrawingDelegate.invalidate();
}
}
void setInvalidateDelegate(View invalidateDelegate) {
mInvalidateDelegate = invalidateDelegate;
invalidate();
}
public int getBgColor() {
int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
return ColorUtils.setAlphaComponent(mBgColor, alpha);
}
public void drawBackground(Canvas canvas) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(getBgColor());
//drawCircle(canvas, 0 /* deltaRadius */);
drawShadow(canvas);
}
public void drawShadow(Canvas canvas) {
if (mShadowShader == null) {
return;
}
float radius = getScaledRadius();
float shadowRadius = radius + mStrokeWidth;
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.BLACK);
int offsetX = getOffsetX();
int offsetY = getOffsetY();
final int saveCount;
if (canvas.isHardwareAccelerated()) {
saveCount = canvas.saveLayer(offsetX - mStrokeWidth, offsetY,
offsetX + radius + shadowRadius, offsetY + shadowRadius + shadowRadius,
null, Canvas.CLIP_TO_LAYER_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
} else {
saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipPath(getClipPath(), Region.Op.DIFFERENCE);
}
mShaderMatrix.setScale(shadowRadius, shadowRadius);
mShaderMatrix.postTranslate(radius + offsetX, shadowRadius + offsetY);
mShadowShader.setLocalMatrix(mShaderMatrix);
mPaint.setAlpha(mShadowAlpha);
mPaint.setShader(mShadowShader);
canvas.drawPaint(mPaint);
mPaint.setAlpha(255);
mPaint.setShader(null);
if (canvas.isHardwareAccelerated()) {
mPaint.setXfermode(mShadowPorterDuffXfermode);
canvas.drawCircle(radius + offsetX, radius + offsetY, radius, mPaint);
mPaint.setXfermode(null);
}
canvas.restoreToCount(saveCount);
}
public void fadeInBackgroundShadow() {
if (mShadowAnimator != null) {
mShadowAnimator.cancel();
}
mShadowAnimator = ObjectAnimator
.ofInt(this, SHADOW_ALPHA, 0, 255)
.setDuration(100);
mShadowAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mShadowAnimator = null;
}
});
mShadowAnimator.start();
}
public void animateBackgroundStroke() {
if (mStrokeAlphaAnimator != null) {
mStrokeAlphaAnimator.cancel();
}
mStrokeAlphaAnimator = ObjectAnimator
.ofInt(this, STROKE_ALPHA, MAX_BG_OPACITY / 2, MAX_BG_OPACITY)
.setDuration(100);
mStrokeAlphaAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mStrokeAlphaAnimator = null;
}
});
mStrokeAlphaAnimator.start();
}
public void drawBackgroundStroke(Canvas canvas) {
mPaint.setColor(ColorUtils.setAlphaComponent(mBgColor, mStrokeAlpha));
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mStrokeWidth);
drawCircle(canvas, 1 /* deltaRadius */);
}
public void drawLeaveBehind(Canvas canvas) {
float originalScale = mScale;
mScale = 0.5f;
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(Color.argb(160, 245, 245, 245));
drawCircle(canvas, 0 /* deltaRadius */);
mScale = originalScale;
}
private void drawCircle(Canvas canvas,float deltaRadius) {
float radius = getScaledRadius();
canvas.drawCircle(radius + getOffsetX(), radius + getOffsetY(),
radius - deltaRadius, mPaint);
}
public Path getClipPath() {
mPath.reset();
float r = getScaledRadius();
mPath.addCircle(r + getOffsetX(), r + getOffsetY(), r, Path.Direction.CW);
return mPath;
}
// It is the callers responsibility to save and restore the canvas layers.
void clipCanvasHardware(Canvas canvas) {
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setXfermode(mClipPorterDuffXfermode);
float radius = getScaledRadius();
mShaderMatrix.setScale(radius, radius);
mShaderMatrix.postTranslate(radius + getOffsetX(), radius + getOffsetY());
mClipShader.setLocalMatrix(mShaderMatrix);
mPaint.setShader(mClipShader);
canvas.drawPaint(mPaint);
mPaint.setXfermode(null);
mPaint.setShader(null);
}
private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
if (mDrawingDelegate != delegate) {
delegate.addFolderBackground(this);
}
mDrawingDelegate = delegate;
delegateCellX = cellX;
delegateCellY = cellY;
invalidate();
}
private void clearDrawingDelegate() {
if (mDrawingDelegate != null) {
mDrawingDelegate.removeFolderBackground(this);
}
mDrawingDelegate = null;
isClipping = true;
invalidate();
}
boolean drawingDelegated() {
return mDrawingDelegate != null;
}
private void animateScale(float finalScale, float finalMultiplier,
final Runnable onStart, final Runnable onEnd) {
final float scale0 = mScale;
final float scale1 = finalScale;
final float bgMultiplier0 = mColorMultiplier;
final float bgMultiplier1 = finalMultiplier;
if (mScaleAnimator != null) {
mScaleAnimator.cancel();
}
mScaleAnimator = LauncherAnimUtils.ofFloat(0f, 1.0f);
mScaleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
float prog = animation.getAnimatedFraction();
mScale = prog * scale1 + (1 - prog) * scale0;
mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
invalidate();
}
});
mScaleAnimator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
if (onStart != null) {
onStart.run();
}
}
#Override
public void onAnimationEnd(Animator animation) {
if (onEnd != null) {
onEnd.run();
}
mScaleAnimator = null;
}
});
mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
mScaleAnimator.start();
}
public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) {
Runnable onStart = new Runnable() {
#Override
public void run() {
delegateDrawing(cl, cellX, cellY);
}
};
animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null);
}
public void animateToRest() {
// This can be called multiple times -- we need to make sure the drawing delegate
// is saved and restored at the beginning of the animation, since cancelling the
// existing animation can clear the delgate.
final CellLayout cl = mDrawingDelegate;
final int cellX = delegateCellX;
final int cellY = delegateCellY;
Runnable onStart = new Runnable() {
#Override
public void run() {
delegateDrawing(cl, cellX, cellY);
}
};
Runnable onEnd = new Runnable() {
#Override
public void run() {
clearDrawingDelegate();
}
};
animateScale(1f, 1f, onStart, onEnd);
}
public int getBackgroundAlpha() {
return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
}
public float getStrokeWidth() {
return mStrokeWidth;
}
}
Any help would be amazing
Thanks in advance :)
I want a custom view that moves depending on the accelerometers and creating the parallax effect.
Now I have the custom view listening the accelerometers values but, how I can use these values to move the view properly?
code:
public class ParallaxView extends AppCompatImageView
implements SensorEventListener {
private static final int SENSOR_DELAY = SensorManager.SENSOR_DELAY_FASTEST;
//...
public ParallaxView(Context context) {
super(context);
}
public ParallaxView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ParallaxView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void init() {
WindowManager windowManager = (WindowManager) getContext().getSystemService(WINDOW_SERVICE);
mDisplay = windowManager.getDefaultDisplay();
mSensorManager = (SensorManager) getContext().getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
public void setNewPosition(
#Nullable Float sensorX,
#Nullable Float sensorY) {
// ???
}
//...
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER){
setNewPosition(event.values[0], event.values[1]);
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
public void registerSensorListener() {
mSensorManager.registerListener(this, mAccelerometer, SENSOR_DELAY);
}
public void unregisterSensorListener() {
mSensorManager.unregisterListener(this);
}
}
Use this view in the Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
mParallaxView.init();
}
#Override
protected void onResume() {
mParallaxView.registerSensorListener();
super.onResume();
}
#Override
protected void onPause() {
mParallaxView.unregisterSensorListener();
super.onPause();
}
Thanks in advance
Finally I created my custom view for get what I want.
Here the repository: https://github.com/GVMarc/ParallaxView
Here the code:
public class ParallaxView extends AppCompatImageView implements SensorEventListener {
private static final int DEFAULT_SENSOR_DELAY = SensorManager.SENSOR_DELAY_FASTEST;
public static final int DEFAULT_MOVEMENT_MULTIPLIER = 3;
public static final int DEFAULT_MIN_MOVED_PIXELS = 1;
private static final float DEFAULT_MIN_SENSIBILITY = 0;
private float mMovementMultiplier = DEFAULT_MOVEMENT_MULTIPLIER;
private int mSensorDelay = DEFAULT_SENSOR_DELAY;
private int mMinMovedPixelsToUpdate = DEFAULT_MIN_MOVED_PIXELS;
private float mMinSensibility = DEFAULT_MIN_SENSIBILITY;
private float mSensorX;
private float mSensorY;
private Float mFirstSensorX;
private Float mFirstSensorY;
private Float mPreviousSensorX;
private Float mPreviousSensorY;
private float mTranslationX = 0;
private float mTranslationY = 0;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
public enum SensorDelay {
FASTEST,
GAME,
UI,
NORMAL
}
public ParallaxView(Context context) {
super(context);
}
public ParallaxView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ParallaxView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void init() {
mSensorManager = (SensorManager) getContext().getSystemService(SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
}
private void setNewPosition() {
int destinyX = (int) ((mFirstSensorX - mSensorX) * mMovementMultiplier);
int destinyY = (int) ((mFirstSensorY - mSensorY) * mMovementMultiplier);
calculateTranslationX(destinyX);
calculateTranslationY(destinyY);
}
private void calculateTranslationX(int destinyX) {
if (mTranslationX + mMinMovedPixelsToUpdate < destinyX)
mTranslationX++;
else if (mTranslationX - mMinMovedPixelsToUpdate > destinyX)
mTranslationX--;
}
private void calculateTranslationY(int destinyY) {
if (mTranslationY + mMinMovedPixelsToUpdate < destinyY)
mTranslationY++;
else if (mTranslationY - mMinMovedPixelsToUpdate > destinyY)
mTranslationY--;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
setTranslationX(mTranslationX);
setTranslationY(mTranslationY);
invalidate();
}
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mSensorX = event.values[0];
mSensorY = -event.values[1];
manageSensorValues();
}
}
private void manageSensorValues() {
if (mFirstSensorX == null)
setFirstSensorValues();
if (mPreviousSensorX == null || isSensorValuesMovedEnough()) {
setNewPosition();
setPreviousSensorValues();
}
}
private void setFirstSensorValues() {
mFirstSensorX = mSensorX;
mFirstSensorY = mSensorY;
}
private void setPreviousSensorValues() {
mPreviousSensorX = mSensorX;
mPreviousSensorY = mSensorY;
}
private boolean isSensorValuesMovedEnough() {
return mSensorX > mPreviousSensorX + mMinSensibility ||
mSensorX < mPreviousSensorX - mMinSensibility ||
mSensorY > mPreviousSensorY + mMinSensibility ||
mSensorY < mPreviousSensorX - mMinSensibility;
}
public void registerSensorListener() {
mSensorManager.registerListener(this, mAccelerometer, mSensorDelay);
}
public void registerSensorListener(SensorDelay sensorDelay) {
switch (sensorDelay) {
case FASTEST:
mSensorDelay = SensorManager.SENSOR_DELAY_FASTEST;
break;
case GAME:
mSensorDelay = SensorManager.SENSOR_DELAY_GAME;
break;
case UI:
mSensorDelay = SensorManager.SENSOR_DELAY_UI;
break;
case NORMAL:
mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
break;
}
registerSensorListener();
}
public void unregisterSensorListener() {
mSensorManager.unregisterListener(this);
}
public void setMovementMultiplier(float multiplier) {
mMovementMultiplier = multiplier;
}
public void setMinimumMovedPixelsToUpdate(int minMovedPixelsToUpdate) {
mMinMovedPixelsToUpdate = minMovedPixelsToUpdate;
}
public void setMinimumSensibility(int minSensibility) {
mMinSensibility = minSensibility;
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
}
Here is a ParallaxImageView class I have used before:
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.widget.ImageView;
import yourpackagename.R;
/**
* I did not write this, I just cant remember where I got it from and thought it could be useful for others
*/
public class ParallaxImageView extends ImageView implements SensorEventListener {
private static final String TAG = ParallaxImageView.class.getName();
/**
* If the x and y axis' intensities are scaled to the image's aspect ratio (true) or
* equal to the smaller of the axis' intensities (false). If true, the image will be able to
* translate up to it's view bounds, independent of aspect ratio. If not true,
* the image will limit it's translation equally so that motion in either axis results
* in proportional translation.
*/
private boolean mScaledIntensities = false;
/**
* The intensity of the parallax effect, giving the perspective of depth.
*/
private float mParallaxIntensity = 1.15f;
/**
* The maximum percentage of offset translation that the image can move for each
* sensor input. Set to a negative number to disable.
*/
private float mMaximumJump = .1f;
// Instance variables used during matrix manipulation.
private SensorInterpreter mSensorInterpreter;
private SensorManager mSensorManager;
private Matrix mTranslationMatrix;
private float mXTranslation;
private float mYTranslation;
private float mXOffset;
private float mYOffset;
public ParallaxImageView(Context context) {
this(context, null);
}
public ParallaxImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ParallaxImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Instantiate future objects
mTranslationMatrix = new Matrix();
mSensorInterpreter = new SensorInterpreter();
// Sets scale type
setScaleType(ScaleType.MATRIX);
// Set available attributes
if (attrs != null) {
final TypedArray customAttrs = context.obtainStyledAttributes(attrs, R.styleable.ParallaxImageView);
if (customAttrs != null) {
if (customAttrs.hasValue(R.styleable.ParallaxImageView_intensity))
setParallaxIntensity(customAttrs.getFloat(R.styleable.ParallaxImageView_intensity, mParallaxIntensity));
if (customAttrs.hasValue(R.styleable.ParallaxImageView_scaledIntensity))
setScaledIntensities(customAttrs.getBoolean(R.styleable.ParallaxImageView_scaledIntensity, mScaledIntensities));
if (customAttrs.hasValue(R.styleable.ParallaxImageView_tiltSensitivity))
setTiltSensitivity(customAttrs.getFloat(R.styleable.ParallaxImageView_tiltSensitivity,
mSensorInterpreter.getTiltSensitivity()));
if (customAttrs.hasValue(R.styleable.ParallaxImageView_forwardTiltOffset))
setForwardTiltOffset(customAttrs.getFloat(R.styleable.ParallaxImageView_forwardTiltOffset,
mSensorInterpreter.getForwardTiltOffset()));
customAttrs.recycle();
}
}
// Configure matrix as early as possible by posting to MessageQueue
post(new Runnable() {
#Override
public void run() {
configureMatrix();
}
});
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
configureMatrix();
}
/**
* Sets the intensity of the parallax effect. The stronger the effect, the more distance
* the image will have to move around.
*
* #param parallaxIntensity the new intensity
*/
public void setParallaxIntensity(float parallaxIntensity) {
if (parallaxIntensity < 1)
throw new IllegalArgumentException("Parallax effect must have a intensity of 1.0 or greater");
mParallaxIntensity = parallaxIntensity;
configureMatrix();
}
/**
* Sets the parallax tilt sensitivity for the image view. The stronger the sensitivity,
* the more a given tilt will adjust the image and the smaller needed tilt to reach the
* image bounds.
*
* #param sensitivity the new tilt sensitivity
*/
public void setTiltSensitivity(float sensitivity) {
mSensorInterpreter.setTiltSensitivity(sensitivity);
}
/**
* Sets the forward tilt offset dimension, allowing for the image to be
* centered while the phone is "naturally" tilted forwards.
*
* #param forwardTiltOffset the new tilt forward adjustment
*/
public void setForwardTiltOffset(float forwardTiltOffset) {
if (Math.abs(forwardTiltOffset) > 1)
throw new IllegalArgumentException("Parallax forward tilt offset must be less than or equal to 1.0");
mSensorInterpreter.setForwardTiltOffset(forwardTiltOffset);
}
/**
* Sets whether translation should be limited to the image's bounds or should be limited
* to the smaller of the two axis' translation limits.
*
* #param scaledIntensities the scaledIntensities flag
*/
public void setScaledIntensities(boolean scaledIntensities) {
mScaledIntensities = scaledIntensities;
}
/**
* Sets the maximum percentage of the image that image matrix is allowed to translate
* for each sensor reading.
*
* #param maximumJump the new maximum jump
*/
public void setMaximumJump(float maximumJump) {
mMaximumJump = maximumJump;
}
/**
* Sets the image view's translation coordinates. These values must be between -1 and 1,
* representing the transaction percentage from the center.
*
* #param x the horizontal translation
* #param y the vertical translation
*/
private void setTranslate(float x, float y) {
if (Math.abs(x) > 1 || Math.abs(y) > 1)
throw new IllegalArgumentException("Parallax effect cannot translate more than 100% of its off-screen size");
float xScale, yScale;
if (mScaledIntensities) {
// Set both scales to their offset values
xScale = mXOffset;
yScale = mYOffset;
} else {
// Set both scales to the max offset (should be negative, so smaller absolute value)
xScale = Math.max(mXOffset, mYOffset);
yScale = Math.max(mXOffset, mYOffset);
}
// Make sure below maximum jump limit
if (mMaximumJump > 0) {
// Limit x jump
if (x - mXTranslation / xScale > mMaximumJump) {
x = mXTranslation / xScale + mMaximumJump;
} else if (x - mXTranslation / xScale < -mMaximumJump) {
x = mXTranslation / xScale - mMaximumJump;
}
// Limit y jump
if (y - mYTranslation / yScale > mMaximumJump) {
y = mYTranslation / yScale + mMaximumJump;
} else if (y - mYTranslation / yScale < -mMaximumJump) {
y = mYTranslation / yScale - mMaximumJump;
}
}
mXTranslation = x * xScale;
mYTranslation = y * yScale;
configureMatrix();
}
/**
* Configures the ImageView's imageMatrix to allow for movement of the
* source image.
*/
private void configureMatrix() {
if (getDrawable() == null || getWidth() == 0 || getHeight() == 0) return;
int dWidth = getDrawable().getIntrinsicWidth();
int dHeight = getDrawable().getIntrinsicHeight();
int vWidth = getWidth();
int vHeight = getHeight();
float scale;
float dx, dy;
if (dWidth * vHeight > vWidth * dHeight) {
scale = (float) vHeight / (float) dHeight;
mXOffset = (vWidth - dWidth * scale * mParallaxIntensity) * 0.5f;
mYOffset = (vHeight - dHeight * scale * mParallaxIntensity) * 0.5f;
} else {
scale = (float) vWidth / (float) dWidth;
mXOffset = (vWidth - dWidth * scale * mParallaxIntensity) * 0.5f;
mYOffset = (vHeight - dHeight * scale * mParallaxIntensity) * 0.5f;
}
dx = mXOffset + mXTranslation;
dy = mYOffset + mYTranslation;
mTranslationMatrix.set(getImageMatrix());
mTranslationMatrix.setScale(mParallaxIntensity * scale, mParallaxIntensity * scale);
mTranslationMatrix.postTranslate(dx, dy);
setImageMatrix(mTranslationMatrix);
}
/**
* Registers a sensor manager with the parallax ImageView. Should be called in onResume
* from an Activity or Fragment.
*
*/
#SuppressWarnings("deprecation")
public void registerSensorManager() {
if (getContext() == null || mSensorManager != null) return;
// Acquires a sensor manager
mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
if (mSensorManager != null) {
mSensorManager.registerListener(this,
mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_FASTEST);
}
}
/**
* Unregisters the ParallaxImageView's SensorManager. Should be called in onPause from
* an Activity or Fragment to avoid continuing sensor usage.
*/
public void unregisterSensorManager() {
unregisterSensorManager(false);
}
/**
* Unregisters the ParallaxImageView's SensorManager. Should be called in onPause from
* an Activity or Fragment to avoid continuing sensor usage.
* #param resetTranslation if the image translation should be reset to the origin
*/
public void unregisterSensorManager(boolean resetTranslation) {
if (mSensorManager == null) return;
mSensorManager.unregisterListener(this);
mSensorManager = null;
if (resetTranslation) {
setTranslate(0, 0);
}
}
#Override
public void onSensorChanged(SensorEvent event) {
final float [] vectors = mSensorInterpreter.interpretSensorEvent(getContext(), event);
// Return if interpretation of data failed
if (vectors == null) return;
// Set translation on ImageView matrix
setTranslate(vectors[2], vectors[1]);
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) { }
}
SensorInterpreter
import android.content.Context;
import android.hardware.SensorEvent;
import android.view.Surface;
import android.view.WindowManager;
/**
* I did not write this, I just cant remember where I got it from and thought it could be useful for others
*/
public class SensorInterpreter {
private static final String TAG = SensorInterpreter.class.getName();
private float[] mVectors;
private float mTiltSensitivity = 2.0f;
private float mForwardTiltOffset = 0.3f;
public SensorInterpreter() {
mVectors = new float[3];
}
public final float[] interpretSensorEvent(Context context, SensorEvent event) {
if (event == null || event.values.length < 3 || event.values[0] == 0
|| event.values[1] == 0 || event.values[2] == 0)
return null;
// Acquire rotation of screen
final int rotation = ((WindowManager) context
.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
.getRotation();
// Adjust for forward tilt based on screen orientation
switch (rotation) {
case Surface.ROTATION_90:
mVectors[0] = event.values[0];
mVectors[1] = event.values[2];
mVectors[2] = -event.values[1];
break;
case Surface.ROTATION_180:
mVectors[0] = event.values[0];
mVectors[1] = event.values[1];
mVectors[2] = event.values[2];
break;
case Surface.ROTATION_270:
mVectors[0] = event.values[0];
mVectors[1] = -event.values[2];
mVectors[2] = event.values[1];
break;
default:
mVectors[0] = event.values[0];
mVectors[1] = -event.values[1];
mVectors[2] = -event.values[2];
break;
}
// Adjust roll for sensitivity differences based on pitch
// double tiltScale = 1/Math.cos(mVectors[1] * Math.PI/180);
// if (tiltScale > 12) tiltScale = 12;
// if (tiltScale < -12) tiltScale = -12;
// mVectors[2] *= tiltScale;
// Make roll and pitch percentages out of 1
mVectors[1] /= 90;
mVectors[2] /= 90;
// Add in forward tilt offset
mVectors[1] -= mForwardTiltOffset;
if (mVectors[1] < -1)
mVectors[1] += 2;
// Adjust for tilt sensitivity
mVectors[1] *= mTiltSensitivity;
mVectors[2] *= mTiltSensitivity;
// Clamp values to image bounds
if (mVectors[1] > 1)
mVectors[1] = 1f;
if (mVectors[1] < -1)
mVectors[1] = -1f;
if (mVectors[2] > 1)
mVectors[2] = 1f;
if (mVectors[2] < -1)
mVectors[2] = -1f;
return mVectors;
}
public float getForwardTiltOffset() {
return mForwardTiltOffset;
}
public void setForwardTiltOffset(float forwardTiltOffset) {
mForwardTiltOffset = forwardTiltOffset;
}
public float getTiltSensitivity() {
return mTiltSensitivity;
}
public void setTiltSensitivity(float tiltSensitivity) {
mTiltSensitivity = tiltSensitivity;
}
Add this to attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ParallaxImageView">
<attr name="intensity" format="float" />
<attr name="tiltSensitivity" format="float" />
<attr name="forwardTiltOffset" format="float" />
<attr name="scaledIntensity" format="boolean" />
</declare-styleable>
Add this to layout xml
<yourpackagenamehere.ParallaxImageView
android:id="#+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
Add this to Activity class
private ParallaxImageView background;
background = (ParallaxImageView) findViewById(R.id.background);
background.setImageResource(R.drawable.main_back);
#Override
public void onResume() {
background.registerSensorManager();
super.onResume();
}
#Override
public void onPause() {
background.unregisterSensorManager();
super.onPause();
}
When the play button is pressed to start the game(activity), I'd like to create the instance of a ball falling from the top of the screen (the ball will hit something later). I'm not sure how to go about it. Can anyone assist?
What I'm working with so far:
public class GameActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Turn title off
requestWindowFeature(Window.FEATURE_NO_TITLE);
// set Game to fullscreen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
View ballView = new BallView(this);
setContentView(ballView);
// ballView.setBackgroundColor(Color.TRANSPARENT);
}
}
and
public class BallView extends View {
// Ball attributes
private float ballRadius = 30;
private float ballX = 50;
private float ballY = 50;
private RectF ballBounds;
private Paint ballColor;
// For game elements
private int score = 0;
public BallView(Context context){
super(context);
// Initialize game elements
ballBounds = new RectF();
ballColor = new Paint();
this.setFocusableInTouchMode(true);
}
public void onDraw(Canvas canvas){
// Draw ball
ballBounds.set(ballX - ballRadius, ballY - ballRadius, ballX + ballRadius, ballY + ballRadius);
ballColor.setColor(Color.RED);
canvas.drawOval(ballBounds, ballColor);
}
}
From the description of your intent, you should probably use OpenGL or a framework like libgdx which has physics and collision detection built in.
But to answer you question, you try this quick example:
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import android.view.ViewTreeObserver;
public class BallView extends View{
// Ball attributes
private float ballRadius = 30;
private float ballX = 50;
private float ballY = 50;
private float ballSpeed = 10.0f;
private RectF ballBounds;
private Paint ballColor;
boolean doBallAnimation = false;
// For game elements
private int score = 0;
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
long startTime, prevTime; // Used to track elapsed time for animations and fps
public BallView(Context context){
super(context);
// Initialize game elements
ballBounds = new RectF();
ballColor = new Paint();
this.setFocusableInTouchMode(true);
getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener(){
#Override
public void onGlobalLayout(){
getViewTreeObserver().removeOnGlobalLayoutListener(this);
ballY = 0;
ballX = (getWidth()- ballRadius) / 2.0f;
doBallAnimation = true;
animator.start();
}
}
);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
#Override
public void onAnimationUpdate(ValueAnimator arg0){
long nowTime = System.currentTimeMillis();
float secs = (float)(nowTime - prevTime) / 1000f;
prevTime = nowTime;
if((ballY + ballSpeed) > (getHeight() - (ballRadius)))
{
animator.cancel();
return;
}
ballY += ballSpeed;
// Force a redraw to see the ball in its new position
invalidate();
}
});
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setDuration(3000);
}
public void onDraw(Canvas canvas){
// Draw ball
if(doBallAnimation)
{
ballBounds.set(ballX - ballRadius, ballY - ballRadius, ballX + ballRadius, ballY + ballRadius);
ballColor.setColor(Color.RED);
canvas.drawOval(ballBounds, ballColor);
}
}
}
I have to do an app in Android that moves a ball around the screen. I need to move the ball with the accelerometer.
I have this code but the ball go around the border and doesn't bounce.
package com.example.test2;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.ShapeDrawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public class test2 extends Activity implements SensorEventListener {
CustomDrawableView mCustomDrawableView = null;
ShapeDrawable mDrawable = new ShapeDrawable();
public float xPosition, xAcceleration, xVelocity = 0.0f;
public float yPosition, yAcceleration, yVelocity = 0.0f;
public float xmax, ymax;
private Bitmap mBitmap;
private Bitmap mWood;
private SensorManager sensorManager = null;
public float frameTime = 0.666f;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set FullScreen & portrait
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// Get a reference to a SensorManager
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_GAME);
mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView);
// setContentView(R.layout.main);
// Calculate Boundry
Display display = getWindowManager().getDefaultDisplay();
xmax = (float) display.getWidth() - 50;
ymax = (float) display.getHeight() - 50;
}
// This method will update the UI on new sensor events
public void onSensorChanged(SensorEvent sensorEvent) {
{
if (sensorEvent.sensor.getType() == Sensor.TYPE_ORIENTATION) {
// Set sensor values as acceleration
yAcceleration = sensorEvent.values[1];
xAcceleration = sensorEvent.values[2];
updateBall();
}
}
}
private void updateBall() {
// Calculate new speed
xVelocity += (xAcceleration * frameTime);
yVelocity += (yAcceleration * frameTime);
// Calc distance travelled in that time
float xS = (xVelocity / 2) * frameTime;
float yS = (yVelocity / 2) * frameTime;
// Add to position negative due to sensor
// readings being opposite to what we want!
xPosition -= xS;
yPosition -= yS;
if (xPosition > xmax) {
xPosition = xmax;
} else if (xPosition < 0) {
xPosition = 0;
}
if (yPosition > ymax) {
yPosition = ymax;
} else if (yPosition < 0) {
yPosition = 0;
}
}
// I've chosen to not implement this method
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
#Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this,
sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_GAME);
}
#Override
protected void onStop() {
// Unregister the listener
sensorManager.unregisterListener(this);
super.onStop();
}
public class CustomDrawableView extends View {
public CustomDrawableView(Context context) {
super(context);
Bitmap ball = BitmapFactory.decodeResource(getResources(),
R.drawable.ball);
final int dstWidth = 50;
final int dstHeight = 50;
mBitmap = Bitmap
.createScaledBitmap(ball, dstWidth, dstHeight, true);
mWood = BitmapFactory.decodeResource(getResources(),
R.drawable.wood);
}
protected void onDraw(Canvas canvas) {
final Bitmap bitmap = mBitmap;
canvas.drawBitmap(mWood, 0, 0, null);
canvas.drawBitmap(bitmap, xPosition, yPosition, null);
invalidate();
}
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
// TODO Auto-generated method stub
super.onConfigurationChanged(newConfig);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
How can I solve it?
Thank you
You want to move it with the accelerometer, but you're registering an orientation sensor listener. Register for the accelerometer instead.
I have read articles/tutorial about accessing the phone's accelerometer (acceleration and orientation) values. I am trying to build a simple app where I can move a ball image using the these values. Here is my code:
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class Accelerometer extends Activity implements SensorEventListener {
/** Called when the activity is first created. */
CustomDrawableView mCustomDrawableView = null;
ShapeDrawable mDrawable = new ShapeDrawable();
int x ;
int y ;
private SensorManager sensorManager = null;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get a reference to a SensorManager
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView);
// setContentView(R.layout.main);
}
// This method will update the UI on new sensor events
public void onSensorChanged(SensorEvent sensorEvent) {
{
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
int someNumber = 100;
float xChange = someNumber * sensorEvent.values[1];
//values[2] can be -90 to 90
float yChange = someNumber * 2 * sensorEvent.values[2];
x = x + (int)xChange;
y = y + (int)yChange;
}
if (sensorEvent.sensor.getType() == Sensor.TYPE_ORIENTATION) {
}
}
}
// I've chosen to not implement this method
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
#Override
protected void onResume() {
super.onResume();
// Register this class as a listener for the accelerometer sensor
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL);
// ...and the orientation sensor
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onStop() {
// Unregister the listener
sensorManager.unregisterListener(this);
super.onStop();
}
public class CustomDrawableView extends View {
public CustomDrawableView(Context context) {
super(context);
int width = 50;
int height = 50;
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
invalidate();
}
}
}
I am getting an oval shape displayed on the screen but nothing happens after that.
thanks
Use this code. You were never setting the location of the drawable after you intialized that class. You'll have to do some calculations to set the balls location properly. The way you were doing it was getting values over 10000 which was drawing the oval off screen.
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.View;
public class Accelerometer extends Activity implements SensorEventListener
{
/** Called when the activity is first created. */
CustomDrawableView mCustomDrawableView = null;
ShapeDrawable mDrawable = new ShapeDrawable();
public static int x;
public static int y;
private SensorManager sensorManager = null;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Get a reference to a SensorManager
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView);
// setContentView(R.layout.main);
}
// This method will update the UI on new sensor events
public void onSensorChanged(SensorEvent sensorEvent)
{
{
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
// the values you were calculating originally here were over 10000!
x = (int) Math.pow(sensorEvent.values[1], 2);
y = (int) Math.pow(sensorEvent.values[2], 2);
}
if (sensorEvent.sensor.getType() == Sensor.TYPE_ORIENTATION) {
}
}
}
// I've chosen to not implement this method
public void onAccuracyChanged(Sensor arg0, int arg1)
{
// TODO Auto-generated method stub
}
#Override
protected void onResume()
{
super.onResume();
// Register this class as a listener for the accelerometer sensor
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_NORMAL);
// ...and the orientation sensor
sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION),
SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
protected void onStop()
{
// Unregister the listener
sensorManager.unregisterListener(this);
super.onStop();
}
public class CustomDrawableView extends View
{
static final int width = 50;
static final int height = 50;
public CustomDrawableView(Context context)
{
super(context);
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas)
{
RectF oval = new RectF(Accelerometer.x, Accelerometer.y, Accelerometer.x + width, Accelerometer.y
+ height); // set bounds of rectangle
Paint p = new Paint(); // set some paint options
p.setColor(Color.BLUE);
canvas.drawOval(oval, p);
invalidate();
}
}
}
Here is my implementation of this problem. Dymmeh's solution kept throwing problems at me, so I refactored it until I got it working.
package edu.ian495.accelerometertest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ImageView;
public class MainActivity extends Activity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor accelerometer;
private long lastUpdate;
AnimatedView animatedView = null;
ShapeDrawable mDrawable = new ShapeDrawable();
public static int x;
public static int y;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
accelerometer = sensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
lastUpdate = System.currentTimeMillis();
animatedView = new AnimatedView(this);
setContentView(animatedView);
}
#Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, accelerometer,
SensorManager.SENSOR_DELAY_GAME);
}
#Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
public void onAccuracyChanged(Sensor arg0, int arg1) {
// TODO Auto-generated method stub
}
#Override
public void onSensorChanged(SensorEvent event) {
// TODO Auto-generated method stub
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
x -= (int) event.values[0];
y += (int) event.values[1];
}
}
public class AnimatedView extends ImageView {
static final int width = 50;
static final int height = 50;
public AnimatedView(Context context) {
super(context);
// TODO Auto-generated constructor stub
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xffffAC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
#Override
protected void onDraw(Canvas canvas) {
mDrawable.setBounds(x, y, x + width, y + height);
mDrawable.draw(canvas);
invalidate();
}
}
}
I have done some changes in onSensorChange code to move the ball in the screen. With the example in my case the ball donĀ“t move correctly and for that I did the changes. This example Works fine for my.
public void onSensorChanged(SensorEvent sensorEvent)
{
//Try synchronize the events
synchronized(this){
//For each sensor
switch (sensorEvent.sensor.getType()) {
case Sensor.TYPE_MAGNETIC_FIELD: //Magnetic sensor to know when the screen is landscape or portrait
//Save values to calculate the orientation
mMagneticValues = sensorEvent.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER://Accelerometer to move the ball
if (bOrientacion==true){//Landscape
//Positive values to move on x
if (sensorEvent.values[1]>0){
//In margenMax I save the margin of the screen this value depends of the screen where we run the application. With this the ball not disapears of the screen
if (x<=margenMaxX){
//We plus in x to move the ball
x = x + (int) Math.pow(sensorEvent.values[1], 2);
}
}
else{
//Move the ball to the other side
if (x>=margenMinX){
x = x - (int) Math.pow(sensorEvent.values[1], 2);
}
}
//Same in y
if (sensorEvent.values[0]>0){
if (y<=margenMaxY){
y = y + (int) Math.pow(sensorEvent.values[0], 2);
}
}
else{
if (y>=margenMinY){
y = y - (int) Math.pow(sensorEvent.values[0], 2);
}
}
}
else{//Portrait
//Eje X
if (sensorEvent.values[0]<0){
if (x<=margenMaxX){
x = x + (int) Math.pow(sensorEvent.values[0], 2);
}
}
else{
if (x>=margenMinX){
x = x - (int) Math.pow(sensorEvent.values[0], 2);
}
}
//Eje Y
if (sensorEvent.values[1]>0){
if (y<=margenMaxY){
y = y + (int) Math.pow(sensorEvent.values[1], 2);
}
}
else{
if (y>=margenMinY){
y = y - (int) Math.pow(sensorEvent.values[1], 2);
}
}
}
//Save the values to calculate the orientation
mAccelerometerValues = sensorEvent.values.clone();
break;
case Sensor.TYPE_ROTATION_VECTOR: //Rotation sensor
//With this value I do the ball bigger or smaller
if (sensorEvent.values[1]>0){
z=z+ (int) Math.pow(sensorEvent.values[1]+1, 2);
}
else{
z=z- (int) Math.pow(sensorEvent.values[1]+1, 2);
}
default:
break;
}
//Screen Orientation
if (mMagneticValues != null && mAccelerometerValues != null) {
float[] R = new float[16];
SensorManager.getRotationMatrix(R, null, mAccelerometerValues, mMagneticValues);
float[] orientation = new float[3];
SensorManager.getOrientation(R, orientation);
//if x have positives values the screen orientation is landscape in other case is portrait
if (orientation[0]>0){//LandScape
//Here I change the margins of the screen for the ball not disapear
bOrientacion=true;
margenMaxX=1200;
margenMinX=0;
margenMaxY=500;
margenMinY=0;
}
else{//Portrait
bOrientacion=false;
margenMaxX=600;
margenMinX=0;
margenMaxY=1000;
margenMinY=0;
}
}
}
}
The view class where I draw the ball
public class CustomDrawableView extends View
{
static final int width = 50;
static final int height = 50;
//Constructor de la figura
public CustomDrawableView(Context context)
{
super(context);
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
//Dibujamos la figura
protected void onDraw(Canvas canvas)
{
//Actividad_Principal x,y,z are variables from the main activity where I have the onSensorChange
RectF oval = new RectF(Actividad_Principal.x+Actividad_Principal.z, Actividad_Principal.y+Actividad_Principal.z, Actividad_Principal.x + width, Actividad_Principal.y + height);
Paint p = new Paint();
p.setColor(Color.BLUE);
canvas.drawOval(oval, p);
invalidate();
}
}
}
That is all, I hope help us.
Try using sensorEvent.values[0] for your xChange and sensorEvents.values[1] for your yChange if you want to use the acceleration sensor, if not use the same values and move it into the (sensorEvent.sensor.getType() == Sensor.TYPE_ORIENTATION) if statement, this will give you the tilt of the handset rather than how quickly its moving along an axis.
You also need to call invalidate(); on the View when you set or change a sensor.
The Sensor.TYPE_ACCELEROMETER returns:
values[0]: Acceleration minus Gx on the x-axis
values[1]: Acceleration minus Gy on the y-axis
values[2]: Acceleration minus Gz on the z-axis
The Sensor.TYPE_ORIENTATION returns:
values[0]: Azimuth, angle between the magnetic north direction and the y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South, 270=West
values[1]: Pitch, rotation around x-axis (-180 to 180), with positive values when the z-axis moves toward the y-axis.
values[2]: Roll, rotation around y-axis (-90 to 90), with positive values when the x-axis moves toward the z-axis.
use the following library instead scrolling motion
add this line in top XML view
xmlns:parallax="http://schemas.android.com/apk/res-auto"
for Layout
<com.nvanbenschoten.motion.ParallaxImageView
android:id="#+id/parallex"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/image_hd"
parallax:motionTiltSensitivity="2.5" />
for code in your onCreate() method
ParallaxImageView mBackground = (ParallaxImageView) findViewById(R.id.parallex);
in your onResume() method
if(mBackground!=null)
mBackground.registerSensorManager();
in your onDestroy() method
// Unregister SensorManager when exiting
mBackground.unregisterSensorManager();
I think you need to invalidate your view in the onSensorChanged() method or at a specific fps rate that you have to implement.