In my app , i want to use overshootInterpolator[android.R.anim.overshoot_interpolator].
Now ive created my custom overshootInterpolar in which ive set my own overshoot value.
But by default in overshootInterpolar the effect is like follows
Its accelerates right on start and slows down and then overshoots and come back.
What i want is it should not accelerate right on start.
So this is what i have tried
without success
<?xml version="1.0" encoding="utf-8"?>
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="#anim/hdr_accelerate_interpolar"
android:tension="1.3" />
Attribute: android:tension
Description: The amount of tension, the default is 2.
A larger tension will make the overshoot smaller and quicker.
#anim/hdr_accelerate_interpolar
<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="2" />
Attribute: android:factor
Description: A larger value causes a slower initial motion and a
faster acceleration towards the end.
MY ISSUE: The accelerate interpolar is not taking effect.
You are trying to combine two interpolators into one by specifying an android:interpolator attribute within an interpolator resource. Unfortunately, this is not possible using XML resources and you will have to write your custom interpolator as a Java class and also set the interpolator on you animation programmatically.
As an example, your interpolator could look like this.
public class AccelerateOvershootInterpolator implements Interpolator
{
private AccelerateInterpolator accelerate;
private OvershootInterpolator overshoot;
public AccelerateOvershootInterpolator(float factor, float tension)
{
accelerate = new AccelerateInterpolator(factor);
overshoot = new OvershootInterpolator(tension);
}
#Override
public float getInterpolation(float input)
{
return overshoot.getInterpolation(accelerate.getInterpolation(input));
}
}
Of course you don't need to use the AccelerateInterpolator or the OvershootInterpolator. The getInterpolation method can apply any mathematical function to the input. To use this interpolator for an animation you need to create an instance of the interpolator and set it on your animation, like this.
Animation animation = AnimationUtils.loadAnimation(this, R.anim.my_animation);
AccelerateOvershootInterpolator interpolator = new AccelerateOvershootInterpolator(2.0f, 1.7f);
animation.setInterpolator(interpolator);
animatedView.startAnimation(animation);
For an overview on interpolators and how to implement your own, you can look at this tutorial http://cogitolearning.co.uk/?p=1078
Related
Can one undo the changes he made on View properties using animate() on it?
In particular, how to undo changes made using animate().yBy(x)?
Note that I tried using animate().yBy(-x) and it works most of the times, but there are times that for some reason animate().yBy(x) seem not to be completed correctly (especially when the fragment pauses and then resumed) so animate().yBy(-x) is over-moving the view.
I'm looking for a way to make the View reset its properties to the way they were before I changed them using animate().
xBy() and yBy() animations affect the translationX and translationY properties. You can get the current values of those properties via getTranslationX() and getTranslationY(). So, to undo the previous animations, multiply the current property values by -1 and animate those. Or if you are seeking a "smash cut" jump (no animation), just call setTranslationX(0) or setTranslationY(0).
By using interpolator we can inverse the animation:
public class InverAnim implements Interpolator {
#Override
public float getInterpolation(float paramFloat) {
return Math.abs(paramFloat -1f);
}
}
On your animation you can set, new interpolator:
myAnimation.setInterpolator(new InverAnim());
I've got an animation to perform which consists of some arrow heads aligned horizontally where the alpha values of the arrows will change to achieve an animation effect (i.e. first arrow has alpha 1.0, then the second will get a value 1.0 etc.).
So if I have a function like this:
void highlightFirstArrow()
{
mArrow1.setAlpha(1.0f);
mArrow2.setAlpha(0.75f);
mArrow3.setAlpha(0.50f);
mArrow4.setAlpha(0.20f);
}
Then I'd want to start, repeat numerous times, then stop a function such as this:
void animateArrows()
{
highlightFirstArray();
pause;
highlightSecondArray();
pause;
etc.
}
Obviously this would lock up the GUI thread if it were performed in a for look for example. What are the options for achieving the desired animiation:
- run a for loop in a separate thread
- don't use a loop, instead constantly execute the functions individually via a timer
- use built in specific android animation mechanisms. If so which is most appropriate? Would AnimatorSet() be good for this scenario, or something else
You definitely shouldn't use any loops or timers. There're lots of built in classes which could help to animate your views. For instance you can use ValueAnimator:
ValueAnimator.ofFloat(1f, 0.2f).setDuration(1000).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
arrow1.setAlpha(value);
arrow2.setAlpha(value);
}
});
Background
I am working on an implementation of the "KenBurns effect" (demo here) on the action bar , as shown on this library's sample (except for the icon that moves, which I've done so myself).
In fact, I even asked about it a long time ago (here), which at this point I didn't even know its name. I was sure I've found a solution, but it has some problems.
Also, since I sometimes show the images from the device, some of them even need to be rotated, so I use a rotatableDrawable (as shown here).
The problem
The current implementation cannot handle multiple bitmaps that are given dynamically (from the Internet, for example), and doesn't even look at the input images' size.
Instead, it just does the zooming and translation in a random way, so many times it can zoom too much/little, and empty spaces can be shown.
The code
Here's the code that is related to the problems:
private float pickScale() {
return MIN_SCALE_FACTOR + this.random.nextFloat() * (MAX_SCALE_FACTOR - MIN_SCALE_FACTOR);
}
private float pickTranslation(final int value, final float ratio) {
return value * (ratio - 1.0f) * (this.random.nextFloat() - 0.5f);
}
public void animate(final ImageView view) {
final float fromScale = pickScale();
final float toScale = pickScale();
final float fromTranslationX = pickTranslation(view.getWidth(), fromScale);
final float fromTranslationY = pickTranslation(view.getHeight(), fromScale);
final float toTranslationX = pickTranslation(view.getWidth(), toScale);
final float toTranslationY = pickTranslation(view.getHeight(), toScale);
start(view, KenBurnsView.DELAY_BETWEEN_IMAGE_SWAPPING_IN_MS, fromScale, toScale, fromTranslationX,
fromTranslationY, toTranslationX, toTranslationY);
}
And here's the part of the animation itself, which animates the current ImageView:
private void start(View view, long duration, float fromScale, float toScale, float fromTranslationX, float fromTranslationY, float toTranslationX, float toTranslationY) {
view.setScaleX(fromScale);
view.setScaleY(fromScale);
view.setTranslationX(fromTranslationX);
view.setTranslationY(fromTranslationY);
ViewPropertyAnimator propertyAnimator = view.animate().translationX(toTranslationX).translationY(toTranslationY).scaleX(toScale).scaleY(toScale).setDuration(duration);
propertyAnimator.start();
}
As you can see, this doesn't look at the view/bitmap sizes, and just randomly selects how to zoom and pan.
What I've tried
I've made it work with dynamic bitmaps, but I don't understand what to change on it so that it will handle the sizes correctly.
I've also noticed there is another library (here) that does this work, but it also has the same problems, and it's even harder to understand how to fix them there. Plus it randomly crashes . Here's a post I've reported about it.
The question
What should be done in order to implement Ken-Burns effect correctly, so that it could handle dynamically created bitmaps?
I'm thinking that maybe the best solution is to customize the way the ImageView draws its content, so that at any given time, it will show a part of the bitmap that is given to it, and the real animation would be between two rectangles of the bitmap . Sadly, I'm not sure how to do this.
Again, the question isn't about getting bitmaps or decoding. It's about how to make them work well with this effect without crashes or weird zoom in/out which show empty spaces.
I have look at the source code of the KenBurnsView and it isn't actually that hard to implement the features you want, but there are a few things I have to clarify first:
1. Loading images dynamically
The current implementation cannot handle multiple bitmaps that are
given dynamically (from the Internet, for example),...
It isn't difficult to download images dynamically from the internet if you know what you are doing, but there are many ways to do it. Many people don't actually come up with their own solution but use a networking library like Volley to download the image or they go straight for Picasso or something similar. Personally I mostly use my own set of helper classes but you have to decide how exactly you want to download the images. Using a library like Picasso is most likely the best solution for you. My code samples in this answer will use the Picasso library, here is a quick example of how to use Picasso:
Picasso.with(context).load("http://foo.com/bar.png").into(imageView);
2. Image Size
...and doesn't even look at the input images' size.
I really don't understand what you mean by that. Internally the KenBurnsView uses ImageViews to display the images. They take care of properly scaling and displaying the image and they most certainly take the size of the images into account. I think your confusion might be caused by the scaleType which is set for the ImageViews. If you look at the layout file R.layout.view_kenburns which contains the layout of the KenBurnsView you see this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/image0"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="#+id/image1"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Notice that there are two ImageViews instead of just one to create the crossfade effect. The important part is this tag which is found on both ImageViews:
android:scaleType="centerCrop"
What this does is tell the ImageView to:
Center the image inside the ImageView
Scale the image so its width fits inside the ImageView
If the image is taller than the ImageView it will be cropped to the size of the ImageView
So in its current state the images inside the KenBurnsView may be cropped at all times. If you want the image to scale to fit completely inside the ImageView so nothing has to be cropped or removed you need to change the scaleType to one of those two:
android:scaleType="fitCenter"
android:scaleType="centerInside"
I don't remember the exact difference between those two, but they should both have the desired effect of scaling the image so it fits both on the X and Y axis inside the ImageView while at the same time centering it inside the ImageView.
IMPORTANT: Changing the scaleType potentially messes up the KenBurnsView!
If you really just use the KenBurnsView to display two images then changing the scaleType won't matter aside from how the images are displayed, but if you resize the KenBurnsView - for example in an Animation - and the ImageViews have the scaleType set to something other than centerCrop you will loose the parallax effect! Using centerCrop as scaleType of an ImageView is a quick and easy way to create parallax-like effects. The drawback of this trick is probably what you noticed: The image in the ImageView will most likely be cropped and not completely visible!
If you look at the layout you can see that all Views in there have match_parent as layout_height and layout_width. This could also be a problem for certain images as the match_parent constraint and certain scaleTypes sometimes produce strange results when the images are considerably smaller or larger than the ImageView.
The translate animation also takes the size of the image into account - or at least the size of the ImageView. If you look at the source code of animate(...) and pickTranslation(...) you will see this:
// The value which is passed to pickTranslation() is the size of the View!
private float pickTranslation(final int value, final float ratio) {
return value * (ratio - 1.0f) * (this.random.nextFloat() - 0.5f);
}
public void animate(final ImageView view) {
final float fromScale = pickScale();
final float toScale = pickScale();
// Depending on the axis either the width or the height is passed to pickTranslation()
final float fromTranslationX = pickTranslation(view.getWidth(), fromScale);
final float fromTranslationY = pickTranslation(view.getHeight(), fromScale);
final float toTranslationX = pickTranslation(view.getWidth(), toScale);
final float toTranslationY = pickTranslation(view.getHeight(), toScale);
start(view, KenBurnsView.DELAY_BETWEEN_IMAGE_SWAPPING_IN_MS, fromScale, toScale, fromTranslationX, fromTranslationY, toTranslationX, toTranslationY);
}
So the view already accounts for the images size and how much the image is scaled when calculating the translation. So the concept of how this works is okay, the only problem I see is that both the start and end values are randomised without any dependencies between those two values. What this means is one simple thing: The start and endpoint of the animation might be the exact same position or may be very close to each other. As a result of that the animation may sometimes be very significant and other times barely noticeable at all.
I can think of three main ways to fix that:
Instead of randomising both start and end values you just randomise
the start values and pick the end values based on the start values.
You keep the current strategy of randomising all values, but you impose range restrictions on each value. For example the fromScale should be a random value between 1.2f and 1.4f and toScale should be a random value between 1.6f and 1.8f.
Implement a fixed translation and scale animation (In other words the boring way).
Whether you choose approach #1 or #2 you are going to need this method:
// Returns a random value between min and max
private float randomRange(float min, float max) {
return random.nextFloat() * (max - min) + max;
}
Here I have modified the animate() method to force a certain distance between start and end points of the animation:
public void animate(View view) {
final float fromScale = randomRange(1.2f, 1.4f);
final float fromTranslationX = pickTranslation(view.getWidth(), fromScale);
final float fromTranslationY = pickTranslation(view.getHeight(), fromScale);
final float toScale = randomRange(1.6f, 1.8f);
final float toTranslationX = pickTranslation(view.getWidth(), toScale);
final float toTranslationY = pickTranslation(view.getHeight(), toScale);
start(view, this.mSwapMs, fromScale, toScale, fromTranslationX, fromTranslationY, toTranslationX, toTranslationY);
}
As you can see I only need to modify how fromScale and toScale are calculated because the translations values are calculated from the scale values. This is not a 100% fix, but it is a big improvement.
3. Solution #1: Fixing KenBurnsView
(Use solution #2 if possible)
To fix the KenBurnsView you can implement the suggestions I mentioned above. Additionally we need to implement a way for the images to be added dynamically. The implementation of how the KenBurnsView handles images is a little weird. We are going to need to modify that a bit. Since we are using Picasso this is actually going to be pretty simple:
Essentially you just need to modify the swapImage() method, I tested it like this and it is working:
private void swapImage() {
if (this.urlList.size() > 0) {
if(mActiveImageIndex == -1) {
mActiveImageIndex = 1;
animate(mImageViews[mActiveImageIndex]);
return;
}
final int inactiveIndex = mActiveImageIndex;
mActiveImageIndex = (1 + mActiveImageIndex) % mImageViews.length;
Log.d(TAG, "new active=" + mActiveImageIndex);
String url = this.urlList.get(this.urlIndex++);
this.urlIndex = this.urlIndex % this.urlList.size();
final ImageView activeImageView = mImageViews[mActiveImageIndex];
activeImageView.setAlpha(0.0f);
Picasso.with(this.context).load(url).into(activeImageView, new Callback() {
#Override
public void onSuccess() {
ImageView inactiveImageView = mImageViews[inactiveIndex];
animate(activeImageView);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(mFadeInOutMs);
animatorSet.playTogether(
ObjectAnimator.ofFloat(inactiveImageView, "alpha", 1.0f, 0.0f),
ObjectAnimator.ofFloat(activeImageView, "alpha", 0.0f, 1.0f)
);
animatorSet.start();
}
#Override
public void onError() {
Log.i(LOG_TAG, "Could not download next image");
}
});
}
}
I have omitted a few trivial parts, urlList is just a List<String> which contains all the urls to the images we want to display, urlIndex is used to cycle through the urlList. I moved the animation into the Callback. That way the image will be downloaded in the background and as soon as the image has been downloaded successfully the animations will play and the ImageViews will crossfade. A lot of the old code from the KenBurnsView can now be deleted, for example the methods setResourceIds() or fillImageViews() are now unnecessary.
4. Solution #2: Better KenBurnsView + Picasso
The second library you link to, this one, actually contains a MUCH better KenBurnsView. The KenBurnsView I talk about above is a subclass of FrameLayout and there are a few problems with the approach this View takes. The KenBurnsView from the second library is a subclass of ImageView, this is already a huge improvement. Because of it we can use image loader libraries like Picasso directly on the KenBurnsView and we don't have to take care of anything ourselves. You say that you experience random crashes with the second library? I have been testing it rather extensively the last few hours and didn't encounter a single crash.
With the KenBurnsView from the second library and Picasso this all becomes very easy and very few lines of code, you just have to create a KenBurnsView for example in xml:
<com.flaviofaria.kenburnsview.KenBurnsView
android:id="#+id/kbvExample"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="#drawable/image" />
And then in your Fragment you first have to find the view in the layout and then in onViewCreated() we load the image with Picasso:
private KenBurnsView kbvExample;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_kenburns_test, container, false);
this.kbvExample = (KenBurnsView) view.findViewById(R.id.kbvExample);
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Picasso.with(getActivity()).load(IMAGE_URL).into(this.kbvExample);
}
5. Testing
I tested everything on my Nexus 5 running Android 4.4.2. Since ViewPropertyAnimators are used this should all be compatible somewhere down to API Level 16, maybe even 12.
I have a omitted a few lines of code here and there so if you have any questions feel free to ask!
I have a ViewPager which I need to move as a whole on button press. I use an animation for this.
When I press it, I translate the 'x' for it. I use setFillAfter(true) to keep the new position.
But when I change the page of the ViewPager, it jumps back to the original x-position!
I only saw this issue on Android 4.1, with Android 4.0 there is no problem! So it looks like some kind of regression in Android.
I attached a testproject where I could reproduce the issue without all my other stuff around it. I think it is best if you want to help me figure this out to import the project in your Eclipse and see it for yourself.
I also added to video's, one on my HTC One X where I see the issue, and the other on a tablet with Android 4.0, where the issue is not there.
I have been desperately looking to fix this ugly side effect, but no luck till now...
(Sorry for the big movie files...)
Video of Android 4.0 without the side effect
Video Android 4.1 with the side effect
the project where you can reproduce the issue with
Edit:
I added the following:
#Override
public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
if (!i)
lp.setMargins(300,0,0,0);
else
lp.setMargins(0,0,0,0);
myViewPager.setLayoutParams(lp);
}
After that it stays at the correct position, but it 'flickers' quickly, like the animation is still showing at the end and when I change the margin, it still shows the offset it had after animation. Then it jumps to the correct position.
The main problem seems to be incorrect choice of animation type. You see, View Animation as a tool is not intended to be used with complex interactive objects like ViewPager. It offers only low-cost animation of the drawing place of views. The visual behaivior of the animated ViewPager in response to user-actions is undefined and should not be relied on.
Ugly flicks, when you substitute a "gost" with the real object are only natural.
The mechanism, that is intended to use in your case since API 11 is specialized property animator built in Views for optimized performance: ViewPropertyAnimator, or not specialized, but more versatile ObjectAnimator and AnimatorSet.
Property animation makes the View to really change its place and function normally there.
To make project, to use, say, ViewPropertyAnimator, change your listener setting to this:
btn.setOnClickListener(new OnClickListener() {
boolean b = false;
#Override
public void onClick(View v) {
if(b) {
myViewPager.animate().translationX(0f).setDuration(700);
}
else {
myViewPager.animate().translationX(300f).setDuration(700);
}
b=!b;
}
});
If you want to use xml configuration only, stick to |ObjectAnimator and AnimatorSet. Read through the above link for further information.
In case, you are anxious to support pre-Honeycomb devices, you can use Jake Warton's NineOldAndroids project. Hope that helps.
That's because the Animation's setFillAfter(true) doesn't actually change the position or any attributes of the View; all it does is create a Bitmap of the view's drawing cache and leaves it where the animation ends. Once the screen is invalidated again (ie. changing the page in the ViewPager), the bitmap will be removed and it will appear as if the View is returning to it's original position, when in fact it was already there.
If you want the View to retain it's position after the animation has finished, you need to actually adjust the View's LayoutParams to match your desired effect. To achieve this, you can override the onAnimationEnd method of the Animation, and adjust the LayoutParams of the View inside there.
Once you adjust the LayoutParams, you can remove your call to setFillAfter(true) and your View will actually stay where you expect it to.
Regarding the flicker issue:
I have encountered this issue before, and it stems from the possibility of the onAnimationEnd() call not syncing up with the next layout pass. Animation works by applying a transformation to a View, drawing it relative to its current position.
However, it is possible for a View to be rendered after you have moved it in your onAnimationEnd() method. In this case, the Animation's transformation is still being applied correctly, but the Animation thinks the View has not changed its original position, which means it will be drawn relative to its ENDING position instead of its STARTING position.
My solution was to create a custom subclass of Animation and add a method, changeYOffset(int change), which modifies the y translation that is applied during the Animation's applyTransformation method. I call this new method in my View's onLayout() method, and pass the new y-offset.
Here is some of my code from my Animation, MenuAnimation:
/**
* Signal to this animation that a layout pass has caused the View on which this animation is
* running to have its "top" coordinate changed.
*
* #param change
* the difference in pixels
*/
public void changeYOffset(int change) {
fromY -= change;
toY -= change;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float reverseTime = 1f - interpolatedTime;
float dy = (interpolatedTime * toY) + (reverseTime * fromY);
float alpha = (interpolatedTime * toAlpha) + (reverseTime * fromAlpha);
if (alpha > 1f) {
alpha = 1f;
}
else if (alpha < 0f) {
alpha = 0f;
}
t.setAlpha(alpha);
t.getMatrix().setTranslate(0f, dy);
}
And from the View class:
private int lastTop;
// ...
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// the animation is expecting that its View will not be moved by the container
// during its time period. if this does happen, we need to inform it of the change.
Animation anim = getAnimation();
if (anim != null && anim instanceof MenuAnimation) {
MenuAnimation animation = (MenuAnimation) anim;
animation.changeYOffset(top - lastTop);
}
// ...
lastTop = top;
super.onLayout(changed, left, top, right, bottom);
}
Crucero has it right about setFillAfter not adjusting params post invalidation. When the view is re-layed out (which'll happen the pass after it's invalidated), its layout params will be the ones that always applied, so it should go back to the original position.
And Jschools is right about onAnimationEnd. Strongly encourage you to step through the source code with a debugger, where you'll instructively discover that an update is made that affects the drawn position of the view after onAnimationEnd is fired, at which point you've actually applied the layout params, hence the flicker caused by doubled up offset.
But this can be solved quite simply by making sure you relayout at the right time. You want to put your re-positioning logic at the end of the ui message queue at the time of animation end so that it is polled after the animation but before laying out. There's nowhere that suggests doing this, annoyingly, but I've yet find a reason in any release of the SDK reason why (when doing this just once and not incorrectly using ui thread) this shouldn't work.
Also clear the animation due to another issue we found on some older devices.
So, try:
#Override
public void onAnimationEnd(final Animation animation) {
myViewPager.post(new Runnable() {
#Override
public public void run() {
final RelativeLayout.LayoutParams lp = (android.widget.RelativeLayout.LayoutParams) myViewPager.getLayoutParams();
if (!someBooleanIPresume)
lp.setMargins(300,0,0,0);
else
lp.setMargins(0,0,0,0);
myViewPager.setLayoutParams(lp);
myViewPager.clearAnimation();
}
}
I have a property animation, but it's not smooth, i.e I'm getting jumps in the animation. I tried to play with the parameters for the animator, such as the interpolator, the duration, and the frame delay, but can't get a smooth effect. Does anyone have some tricks / examples? Here's my current code for setting the animator:
rotationAnimator=new ObjectAnimator();
rotationAnimator.setTarget(rotationController);
rotationAnimator.setPropertyName("mapRotation");
rotationAnimator.setInterpolator(new LinearInterpolator());
ValueAnimator.setFrameDelay(24);
rotationAnimator.setDuration(ROTATION_DURATION);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
(There's also a call to setFloatValues, but it comes later in the code, before I start the animation)
EDIT: I was asked to show what I'm animating, so here it is:
That's the setter function that gets the value from the animation:
public void setMapRotation(float rotationDegrees)
{
if ((rotationAnimator!=null)&&(rotationAnimator.isRunning()))
rotationAnimator.cancel();
rotationDegrees%=360;
if (rotationDegrees<0) rotationDegrees+=360;
rotationView.setRotationDegrees(rotationDegrees);
if (rotationDegrees!=oldRotationDegrees)
{
notifyRotationListeners();
oldRotationDegrees=rotationDegrees;
}
}
If you look at the code, you'll see there's another function that gets called (setRotationDegrees), so here it is:
public void setRotationDegrees(float rotationDegrees)
{
this.rotationDegrees=rotationDegrees%360;
if (this.rotationDegrees<0) this.rotationDegrees+=360;
Log.i("MapView","View degrees: " + this.rotationDegrees);
invalidate();
}
And that's what happens after the invalidation:
#Override protected void dispatchDraw(Canvas canvas)
{
Log.i("MapView","Redrawing");
int saveCount=canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(rotationDegrees,getWidth()/2,getHeight()/2);
canvas.setDrawFilter(drawFilter);
canvas.getMatrix(rotationMatrix);
super.dispatchDraw(canvas);
canvas.restoreToCount(saveCount);
}
I don't know if there's something particularly heavy here, but I may be wrong...
Defining an AnimatorSet and set the Interpolator on it may solve your issue:
rotationAnimator=new ObjectAnimator();
rotationAnimator.setTarget(rotationController);
rotationAnimator.setPropertyName("mapRotation");
ValueAnimator.setFrameDelay(24);
rotationAnimator.setDuration(ROTATION_DURATION);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.getChildAnimations().add(rotationAnimator);
animatorSet.setInterpolator(new LinearInterpolator());
...
animatorSet.start();
I've resolved by adding HW acceleration directly to my animated view and view container (myViewContainer - in my case a RelativeLayout):
if(!myViewContainer.isHardwareAccelerated())
{
myViewContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
myAnimatedView1.setLayerType(View.LAYER_TYPE_HARDWARE, null);
myAnimatedView2.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
Jumps in the middle of the animation are more likely to be caused by either something in your rotationController class, that is receiving the animated value "mapRotation". Or it could be something to do with your layout.
I would imagine that there is something quite intense, memory or cpu wise, that is causing the juttering.
If your animating something that has lots of layouts then that could be the cause. If in your code you have lots of findViewByID that is getting called as a result of your animation then that to can cause a slow down.
If your just animating a simple image, then im not entirely sure what the issue would be.
Personally i think your code looks fine, perhaps show/describe what your animating.
[edit]
I have limited knowledge of animating with the canvas so i took a quick look at how you were using canvas.save() and canvas.restoreToCount() which i hadn't seen before.
It all looks good though i do find it odd that several tutorials rotate the canvas rather than the ball image to create the rotation. Still, the only reference i found to improving the animating was a comment in the following link that suggested using postInvalidateOnAnimation(). Not sure where though, perhaps instead of invalidate().