To gain experience in android I'm making a decisionmaking app. I'm trying to add a spinning wheel (wheel of fortune like). I have an animation that works fine (just need some little tweaking but fine for now). The problem I'm having is to detect where the wheel has stopped. If I look at the rotation and match it to precalculated values it makes sense, but visually it seems off.
For example:
Begin state is always like this. I tap it and it starts to rotate.
It stops rotating at red (visually), but in the model it says 51, hence my model detects it stopped rotating at yellow:
Cur pos and Prev pos is not currently implemented. Rnd pos is the relative number of degrees it needs to rotate (relative to 0). Rnd rot is number of extra rotations it has to make before stopping. True pos is the absolute number of degrees it has to rotate. Total time: the time the animation takes. Corner: number of degrees one piece has.
SpinWheel method in activity:
private void spinWheel() {
Roulette r = Roulette.getInstance();
r.init(rouletteItemsDEBUG);
r.spinRoulette();
final int truePos = r.getTruePosition();
final long totalTime = r.getTotalTime();
final ObjectAnimator anim = ObjectAnimator.ofFloat(imgSpinningWheel, "rotation", 0, truePos);
anim.setDuration(totalTime);
anim.setInterpolator(new DecelerateInterpolator());
anim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
imgSpinningWheel.setEnabled(false);
}
#Override
public void onAnimationEnd(Animator animation) {
imgSpinningWheel.setEnabled(true);
txtResult.setText(Roulette.getInstance().getSelectedItem().getValue());
Log.d(TAG, Roulette.getInstance().toString());
Log.d(TAG, imgSpinningWheel.getRotation() + "");
Log.d(TAG, imgSpinningWheel.getRotationX() + "");
Log.d(TAG, imgSpinningWheel.getRotationY() + "");
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
}
Roulette.java:
public class Roulette {
private static Roulette instance;
private static final long TIME_IN_WHEEL = 1000; //time in one rotation (speed of rotation)
private static final int MIN_ROT = 5;
private static final int MAX_ROT = 6;
private static final Random rnd = new Random();
private RouletteItem currentItem;
private RouletteItem[] rouletteItems;
private NavigableMap<Integer, RouletteItem> navigableMap;
private int randomRotation;
private int randomPosition;
private int truePosition;
private int corner;
private long totalTime;
private Roulette() {
}
public void init(RouletteItem[] rouletteItems) {
this.rouletteItems = rouletteItems;
navigableMap = new TreeMap<>();
for (int i = 0; i < getNumberOfItems(); i++) {
navigableMap.put(i * 51, rouletteItems[i]);
}
}
public void spinRoulette() {
if (rouletteItems == null || rouletteItems.length < 2) {
throw new RouletteException("You need at least 2 rouletteItems added to the wheel!");
}
calculateCorner();
calculateRandomPosition();
calculateRandomRotation();
calculateTruePosition();
calculateTotalTime();
}
/**
* Pinpoint random position in the wheel
*/
private void calculateRandomPosition() {
randomPosition = corner * rnd.nextInt(getNumberOfItems());
}
/**
* Calculates the points one RouletteItem has
*/
private void calculateCorner() {
corner = 360 / getNumberOfItems();
}
/**
* Calculates the time needed to spin to the chosen random position
*/
private void calculateTotalTime() {
totalTime = (TIME_IN_WHEEL * randomRotation + (TIME_IN_WHEEL / 360) * randomPosition);
}
/**
* Calculates random rotation
*/
private void calculateRandomRotation() {
randomRotation = MIN_ROT + rnd.nextInt(MAX_ROT - MIN_ROT);
}
/**
* Calculates the true position
*/
private void calculateTruePosition() {
truePosition = (randomRotation * 360 + randomPosition);
}
//getters and to string omitted
}
The map is used to map rouletteItems to a range of degrees.
My guess is that the animation takes too long or something like that. But I don't really see how to fix it. Anyone who does?
Thanks in advance!
Um, doesn't Java implement rotation in radians rather than degrees?
That could lead to a offset between the visual rotation graphic and the number from the maths. Perhaps check by replacing all the assumed degree calculations (ie x/360) with radian calcs (ie. x/(2*pi))?
Related
I am investigating Android ViewPager animation in my current project.
I would like to give my users an affordance in relation to my ViewPager.
The effect I am looking for it to "shake" the ViewPager from left to right to partially show the neighbouring pages to the selected page.
When the user selects the first page (F), I would "Shake" between F and F + 1.
When the user selects the last page (L), I would "Shake" between L and L - 1.
Otherwise, when the user selects any other page (X), I would "Shake" between X - 1, X and X + 1.
I have tried the following approaches which have all given unsatisfactory results.
Fake drag using screen density to calculate shake distance and valueAnimator and ObjectAnimator.
Animating ViewPager.PageTransformer.
I am unable to get a satisfactory "Shake" effect.
I would like to achieve a Spring Dampening style of animation.
The FakeDrag was closest, although the effect was not reliable, it seemed to never work correctly the first time I started the animation.
Even the FakeDrag would not give the desired smooth shake between page swipes.
I would like to employ the physics-based animations on my view pager but couldn't see how.
Is it possible to achieve my desired animation?
UPDATE
I have developed this code which gives the desired effect, however its JANKY!
#Override
protected void onResume() {
super.onResume();
final Handler handler = new Handler();
handler.postDelayed(() -> animateViewPager(ANIMATION_OFFSET, ANIMATION_DURATION), ANIMATION_DELAY);
}
/**
* #param offset
* #param duration
*/
private void animateViewPager(final int offset, final int duration) {
if (animator.isRunning()) {
return;
}
animator.removeAllUpdateListeners();
animator.removeAllListeners();
animator.setIntValues(0, -offset);
animator.setDuration(duration);
animator.setRepeatCount(getRepeatCount());
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(constructUpdateListener());
animator.addListener(constructAnimatorListener());
animator.start();
}
/**
*
* #return
*/
private Animator.AnimatorListener constructAnimatorListener() {
return new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(final Animator animation) {
animFactor = 1;
}
#Override
public void onAnimationEnd(final Animator animation) {
viewPager.endFakeDrag();
if (xAnimation == null) {
xAnimation = createSpringAnimation(viewPager, SpringAnimation.X, 0, SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
} else {
xAnimation.cancel();
}
viewPager.animate().x(viewPager.getWidth() * 0.1f).setDuration(0).start();
xAnimation.start();
}
#Override
public void onAnimationRepeat(final Animator animation) {
animFactor = -1;
}
};
}
/**
* #return
*/
private int getRepeatCount() {
if (isOnlyPage()) {
return 0;
}
if (isFirstListItem()) {
return REPEAT_ONCE;
}
if (isLastListItem()) {
return REPEAT_ONCE;
}
return REPEAT_TWICE;
}
#SuppressWarnings("StatementWithEmptyBody")
private ValueAnimator.AnimatorUpdateListener constructUpdateListener() {
return animation -> {
final Integer value = animFactor * (Integer) animation.getAnimatedValue();
if (viewPager.isFakeDragging()) {
} else {
viewPager.beginFakeDrag();
}
viewPager.fakeDragBy(value);
};
}
/**
* #param view
* #param property
* #param finalPosition
* #param stiffness
* #param dampingRatio
* #return
*/
private SpringAnimation createSpringAnimation(final View view, final DynamicAnimation.ViewProperty property, final float finalPosition, final float stiffness, final float dampingRatio) {
final SpringAnimation animation = new SpringAnimation(view, property);
final SpringForce springForce = new SpringForce(finalPosition);
springForce.setStiffness(stiffness);
springForce.setDampingRatio(dampingRatio);
animation.setSpring(springForce);
return animation;
}
I want to animate a rectangle filled with one opaque color. The attributes I will be animating is the translation, and the width of the active menu item.
I know how to animate things, but in this case, I want it to do no layouts on the view, since my animation will occur inside a LinearLayout, and it will not exceed it's size.
The Blue line on top of my layout is what I will be animating. It will go horizontally to the left and right, while changing it's width, so that it fits on the selected menu item.
I usually work with animations on the margin, but it consumes a lot of processing to re-calculate bounds on the layout process.
Any suggestions on how to do that?
That depends entirely on what API level you're targetting, if you're only targeting >3.0 then ObjectAnimator and ValueAnimator or the nicer ViewPropertyAnimator are your best friend, they let you do simple things like "move the X value of this 100dp while increasing the width by a factor of two, in 300ms".
If you're targeting a lower API level check out NineOldAndroids which brings that functionality over to all versions of Android.
To do what you want to do it'd be something along the lines of:
myImageView.scaleXBy(FACTOR_NEEDED_FOR_NEW_WIDTH);
and that's all to it.
As a sidenote: It looks like you might be attempting to replicate a ViewPager's indicator, in which case you should be using an actual indicator.
I had to animate the Margin and the Width of the view, because there was no way out, since I'm using android version >=8.
Here is my two classes that can do this:
MarginAnimation class:
public class MarginAnimation extends Animation{// implements AnimationListener{
public static String TAG = "MarginAnimation";
protected View animatingView;
protected int fromMarginLeft = 0;
protected int fromMarginTop = 0;
protected int toMarginLeft = 0;
protected int toMarginTop = 0;
protected LayoutParams layoutParam;
public MarginAnimation(View v, int toMarginLeft, int toMarginTop) {
this.toMarginLeft = toMarginLeft;
this.toMarginTop = toMarginTop;
this.animatingView = v;
// Save layout param
layoutParam = (LayoutParams) animatingView.getLayoutParams();
// Save current margins as initial state
saveCurrent();
// Set the listner to be self object
// setAnimationListener(this);
}
public MarginAnimation(View v, int fromMarginLeft, int toMarginLeft, int fromMarginTop, int toMarginTop) {
this.fromMarginLeft = fromMarginLeft;
this.toMarginLeft = toMarginLeft;
this.fromMarginTop = fromMarginTop;
this.toMarginTop = toMarginTop;
this.animatingView = v;
// Save layout param
layoutParam = (LayoutParams) animatingView.getLayoutParams();
// Set the listner to be self object
// setAnimationListener(this);
}
protected void saveCurrent(){
fromMarginLeft = layoutParam.leftMargin;
fromMarginTop = layoutParam.topMargin;
}
long lastTime = 0;
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// long thisTime = System.nanoTime();
// if(lastTime != 0)
// Log.e(TAG, ((thisTime - lastTime) / 1000) + "delta Anim.");
// lastTime = thisTime;
layoutParam.leftMargin = (int)(fromMarginLeft + (toMarginLeft - fromMarginLeft) * interpolatedTime);
layoutParam.topMargin = (int)(fromMarginTop + (toMarginTop- fromMarginTop) * interpolatedTime);
animatingView.setLayoutParams(layoutParam);
}
#Override
public boolean willChangeBounds() {
return false;
}
}
MarginAndWidthAnimation class:
public class MarginAndWidthAnimation extends MarginAnimation{
public static String TAG = "MarginAndWidthAnimation";
int toWidth;
int fromWidth;
public MarginAndWidthAnimation(View v, int toMarginLeft, int toMarginTop, int toWidth) {
super(v, toMarginLeft,toMarginTop);
this.toWidth = toWidth;
// Log.i(TAG, "++F: "+this.fromWidth+" T: "+this.toWidth);
}
protected void saveCurrent(){
super.saveCurrent();
// fromWidth = animatingView.getWidth();
fromWidth = layoutParam.width;
// Log.i(TAG, "F: "+fromWidth+" T: "+toWidth);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
layoutParam.width = (int)(fromWidth + (toWidth - fromWidth) * interpolatedTime);
// Log.i(TAG, "F: "+fromWidth+" T: "+toWidth+" S: "+layoutParam.width);
super.applyTransformation(interpolatedTime, t);
}
}
I've been working on the example from http://obviam.net/index.php/a-very-basic-the-game-loop-for-android/ In this I want to make few changes.
Speed.java
public class Speed {
public static final int DIRECTION_RIGHT = 1;
public static final int DIRECTION_LEFT = -1;
public static final int DIRECTION_UP = -1;
public static final int DIRECTION_DOWN = 1;
private float xv = 1; // velocity value on the X axis
private float yv = 1; // velocity value on the Y axis
private int xDirection = DIRECTION_RIGHT;
private int yDirection = DIRECTION_DOWN;
public Speed() {
this.xv = 1;
this.yv = 1;
}
public Speed(float xv, float yv) {
this.xv = xv;
this.yv = yv;
}
public float getXv() {
return xv;
}
public void setXv(float xv) {
this.xv = xv;
}
public float getYv() {
return yv;
}
public void setYv(float yv) {
this.yv = yv;
}
public int getxDirection() {
return xDirection;
}
public void setxDirection(int xDirection) {
this.xDirection = xDirection;
}
public int getyDirection() {
return yDirection;
}
public void setyDirection(int yDirection) {
this.yDirection = yDirection;
}
// changes the direction on the X axis
public void toggleXDirection() {
xDirection = xDirection * -1;
}
// changes the direction on the Y axis
public void toggleYDirection() {
yDirection = yDirection * -1;
}
}
Using this, the image moves in all directions. Now I just want to limit this movement from bottom to top. And the functionality for onclick is that, we can click and drag the image to required position. I want to replace that with just make the image to disappear or go to another activity. Please help me in making changes to this code. Thanks in advance.
if you have used SurfaceView then in onDraw(Canvas canvas) method the code is for moving image from bottom to top is something like this.
canvas.drawBitmap(bitmap,xPoint,yPoint,paint);
where bitmap is an image(Bitmap which you want to move),xPoint is the x coordinate,yPoint is the y coordinate and paint is a Paint which is also can be null.
and for bottom to top movement just update
yPoint = yPoint - 1;
in any thread before onDraw() call.
May this help you.
I have to animate an image as per (x,y) from one point to another point, then that point to another point and so on. I have around 300 points. For that I am using the following code.
/** code starts **/
public class CircleAnimation extends Activity implements OnPreparedListener {
/** Called when the activity is first created. */
ImageView imv1;
int totalAnimTime = 0;
double[][] points = {{258.8505,143.2875,67},
{259.642, 143.3665,120},
{260.429, 142.992,240},
{259.257, 139.3575,180},
......................
......................
{255.1335,146.8135,67},
{255.1395,146.794,67},
{255.0635,146.7785,67},
{254.9045,146.797,1200}
};
int j=0;
double loc[] = new double[2];
double x1 = 0,y1 = 0,x2 = 0,y2 = 0,anim_end=0, xstart=258.8505, ystart=143.2875, xnow, ynow;
protected boolean _active = true;
protected int _animTime = 66;
int k=1;
double xFactor = 1.779167, yFactor = 1.5;
private int displayWidth, displayHeight;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
imv1 = (ImageView)findViewById(R.id.imv1);
try{
LaunchInAnimation();
}catch(Exception e){
e.printStackTrace();
}
}
class LocalAnimationListener implements AnimationListener {
public void onAnimationEnd(Animation animation){
imv1.post(mLaunchSecondAnimation);
k = k ++;
}
public void onAnimationRepeat(Animation animation)
{
}
public void onAnimationStart(Animation animation)
{
}
};
private Runnable mLaunchSecondAnimation = new Runnable(){
public void run(){
LaunchInAnimation();
}
};
LocalAnimationListener MyAnimationListener = new LocalAnimationListener();
public void LaunchInAnimation() {
//animation
if(k<points.length) {
if(k==0) {
x1 = xstart;
y1 = ystart;
anim_end=1;
} else {
x1 = points[k-1][0];
y1 = points[k-1][1];
}
x2 = points[k][0];
y2 = points[k][1];
_animTime = (int) (points[k][2]);
TranslateAnimation translateAnimation = new TranslateAnimation((float)x1, (float)x2, (float)y1, (float)y2);
translateAnimation.setDuration(_animTime);
translateAnimation.setFillBefore(true);
translateAnimation.setFillAfter(true);
translateAnimation.setAnimationListener(MyAnimationListener);
imv1.startAnimation(translateAnimation);
totalAnimTime += _animTime;
}
}
}
/** code ends **/
To complete all the animations it should take totalAnimTime, but it is taking less time to complete. Moreover this time is varying from one device to another device. For this, I facing problem to synchronize other event. What is the problem in this code? Is there any other better way to control this type animation.
I was messing with this variance in performance myself, and then I switched to OpenGL to do all my rendering and have since found everything much smoother and simpler once you understand what you're doing.
If you feel OpenGL/SurfaceView is a little too dense for what you're asking for, try using Canvas?
Should be much easier with one of those because they're more stable from device to device. XML animations are simply tricking you into thinking they're animations, their actual X/Y don't ever really change. They have to "flicker" from their original X/Y to the "next step X/Y" right before drawn if I'm correct....
When I use MapController.setZoom(x) and, for instance, zoom from level 5 to 15 the zoom is perform very fast and often the map tiles of the new level are not loaded.
This does not look so good to the user. Any Maps build in function to change this to a more slow zoom so tiles can be loaded, or at least almost loaded, before level 15 is reached?
Best regards
P
A simpler way is to take advantage of the MapController.zoomIn() method that provides some simple animation for zooming a step level.
Here's some code:
// a Quick runnable to zoom in
int zoomLevel = mapView.getZoomLevel();
int targetZoomLevel = 18;
long delay = 0;
while (zoomLevel++ < targetZoomLevel) {
handler.postDelayed(new Runnable() {
#Override
public void run() {
mapController.zoomIn();
}
}, delay);
delay += 350; // Change this to whatever is good on the device
}
What it does is create a sequence of delayed runnables each one of which will call zoomIn() 350ms after the previous one.
This assumes that you have a Handler attached to your main UI thread called 'handler'
:-)
There's no simple way to do this. However, I can help you out.
Firstly, here's a free gift of one of my personal utility classes, Tween.java:
import android.os.Handler;
public class Tween {
public static interface TweenCallback {
public void onTick(float time, long duration);
public void onFinished();
}
long start;
long duration;
Handler handler;
TweenCallback callback;
public Tween(TweenCallback callback) {
handler = new Handler();
this.callback = callback;
}
public void start(final int duration) {
start = android.os.SystemClock.uptimeMillis();
this.duration = duration;
tickRunnable.run();
}
public void stop() {
handler.removeCallbacks(tickRunnable);
}
Runnable tickRunnable= new Runnable() {
public void run() {
long now = android.os.SystemClock.uptimeMillis();
float time = now - start;
boolean finished = (time >= duration);
if (finished) {
time = duration;
}
callback.onTick(time, duration);
if (!finished) {
handler.post(tickRunnable);
}
else {
callback.onFinished();
}
}
};
//
// Tweening functions. The 4 parameters are :
//
// t - time, ranges from 0 to d
// b - begin, i.e. the initial value for the quantity being changed over time
// c - change, the amount b will be changed by at the end
// d - duration, of the transition, normally in milliseconds.
//
// All were adapted from http://jstween.sourceforge.net/Tween.js
//
public static float strongEaseInOut(float t, float b, float c, float d) {
t/=d/2;
if (t < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
}
public static float regularEaseIn(float t, float b, float c, float d) {
return c*(t/=d)*t + b;
}
public static float strongEaseIn(float t, float b, float c, float d) {
return c*(t/=d)*t*t*t*t + b;
}
}
What I recommend you do is use MapController.zoomToSpan() in conjunction with a Tween... here's some completely untested code that should work, maybe with a tweak or two, you just pass it the target lat & lon spans. :
public void slowZoom(int latE6spanTarget, int lonE6spanTarget) {
final float initialLatE6span = mapView.getLatitudeSpan();
final float initialLonE6span = mapView.getLongitudeSpan();
final float latSpanChange = (float)(latE6spanTarget - initialLatE6span);
final float lonSpanChange = (float)(lonE6spanTarget - initialLonE6span);
Tween tween = new Tween(new Tween.TweenCallback() {
public void onTick(float time, long duration) {
float latSpan = Tween.strongEaseIn(time, initialLatE6span, latSpanChange, duration);
float lonSpan = Tween.strongEaseIn(time, initialLonE6span, lonSpanChange, duration);
mapView.getController().zoomToSpan((int)latSpan, (int)lonSpan);
}
public void onFinished() {
}
});
tween.start(5000);
}