Goal
I'd like to implement a countdown timer that just scrolls numbers (not graphics) from left to right.
Effect
The effect would look like the number zooms in from the left, slows down towards the middle, and then zooms off to the right.
Notes
Since I'm already using a TimerTask to execute code every second, I could use that to trigger the next number to scroll across the horizontally-scrolling textview.
Could this just be implemented as a textview inside a scrollview ? Looking for a code sample to start off with....
Using Animations would be the simplest solution. You can create your own or try and combine multiple TranslateAnimations and ScaleAnimations.
This would mean putting each number into its own TextView instead of using a scroll view.
Then you could control the acceleration to the middle with an Interpolator. Interpolators are how Android handles easing. You would probably want the AccelerateDecelerateInterpolator for the speeding up / slowing down effect.
You can use an AnimationSet to apply multiple animations to the same View. Figuring out how to put together a good AnimationSet will be the most challenging part of the project. Make sure to pay attention to the "fill" property. In fact after playing around a little, I think a custom animation is simpler than using the ready made ones.
You can fork my GitHub project that implements a very simple version of this. April 17 and before I used multiple pre made Animations. If you look at the most recent version, you'll see the custom animation.
The timing for each Animation takes care of itself after you set the duration for one Animation. A Handler calls the next number after the previous one finishes. I think this is a little neater than having to call a function every X seconds to update everything.
The outline of functionality:
An Activity (CountDownActivity.java) over sees everything.
The Activitiy's layout XML has a button that is used to start the count down.
Once the countdown starts, the button disappears. It reappears when the count down is done.
The Activity contains a Handler (MotionHandler.java). The Handler controls the movement and timing of the numbers.
The Handler uses a AnimationSet to move the numbers
The AnimationSet is a passed in dependency
This is for flexibility. Simply pass in a different AnimationSet to change how the numbers move
The AnimationSet is made of four Animations a custom Animation (see below)
The AnimationSet uses a shared AccelerateDecelerateInterpolator, which seems to work decently. There are other options, including writing your own.
The Handler uses a delayed message to start the next number
The Handler notifies the Activity when the count down is done using a custom listener (MotionHandler >> CountdownListener)
Rotating the device will restart the count down.
Note - previously I was using four ready made Animations in one AnimationSet, I've edited to include just one custom Animation... You can tweak its algorithm to your liking.
This custom animation uses a Cycloid to make the numbers appear larger and smaller.
/**
* A custom animation to move and scale the numbers.
*
*/
public class NumberAnimation extends Animation
{
final public static float MINIMUM = 3;
private int mHorizontal;
private int mScaling;
public NumberAnimation(int horizontalMovement, int scaling)
{
mHorizontal = horizontalMovement;
mScaling = scaling;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
// Cycloid repeats every 2pi - scale interpolatedTime to that
double time = 2 * Math.PI * interpolatedTime;
// Cycloid function
float currentScale = (float) (mScaling * (1 - Math.cos(time))) + MINIMUM;
Matrix matrix = t.getMatrix();
matrix.preScale(currentScale, currentScale);
matrix.postTranslate(mHorizontal * interpolatedTime, 0);
}
}
Easing will help you control the speed.
Related
I am trying to animate alpha of an Android view (two animations, both fade in and fade out). It all works fine if the view's alpha is initially 1, by default. However, I want that view to be transparent initially, hence I've set it's alpha to zero:
indicatorContainer.setAlpha(0);
Now, the animations won't work. It will never become visible. If I comment out that line, the view is initially displayed (which I don't want), but my animations works fine when I invoke them. I though it's something trivial but apparently it's not. What am I doing wrong?
UPDATE: I've also tried a floating point 0f instead of integer 0 after reading some API changes involving the setAlpha method, thinking that my call may be calling the incorrect overload, but nothing changed.
Try something like this:
mRelativeLayout.setAlpha(0f);
mRelativeLayout.animate().alpha(1f).setDuration(500).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mRelativeLayout.setVisibility(View.VISIBLE);
//OR
mRelativeLayout.setAlpha(1f);
}
});
I faced same issue where indicatorContainer is ImageButton.
Below code fixes this very, very annoying issue...
// XXX: Does not work if just 0. It calls `ImageView#setAlpha(int)` deprecated method.
indicatorContainer.setAlpha(0.0f);
ViewCompat.animate(indicatorContainer).alpha(1);
One can try following, a more simple way:
view.animate().alpha(1).setDuration(ANIMATION_DURATION);
This might be irrelevant to the OP but I thought I'd share it regardless as it might help someone out in the future.
Please be aware that if you're initial animation combines animate().alpha(0.0f) with a manipulation of the View's Y or X-axis translation (e.g. animate().translationYBy(500) you have to reset this property before fading back in (using animate().alpha(1.0f).
I came across this SO post thinking that setting alpha back to 1.0f was failing when in actual fact it was still working as it should, but the animation was occurring off screen because I hadn't reset my Y-Axis translation (Homer Simpson *doh* moment).
Handily, to easily resolve this issue, you can add a AnimatorEndListener to your animation for when it finishes (as #Nikhil Verma mentioned above) where you can add a single line of code to reset the X/Y-axis translation.
In the scenario I faced I wanted the animation to float down and fade out so adjusted the Y-axis & alpha accordingly. After it had floated and faded I set a Listener to reset the Y axis translation like so:
loadingMask.animate().translationYBy(500); //starts animation by moving it down the screen
loadingMask.animate().alpha(0.0f) //fades out
.setDuration(1500) //over 1.5s
.setListener(new AnimatorEndListener() { //when animation completes
#Override
public void onAnimationEnd(Animator animation) {
loadingMask.setTranslationY(0); //reset the y axis translation
}
});
Now, when I want the animation to repeat again, I can set the alpha of my View to 1.0f and it works as intended.
Here's how I was able to kinda solve for this - not particularly elegant, but manageable:
Set the view to the initial alpha you want 0.0f or otherwise.
When the event occurs where you need the view to have more (or less) visibility/alpha - eg, right before you start an animation - at that point you can update the alpha and then run the animation on the view
I'm still getting some choppiness when the animation repeats, but this approach might work for scenarios where the animation is not repeated
I have two RelativeLayout views. The first one has about three children. The second one has only the first one view as its child. I have two animations, fadeIn and translateIn, which must be applied to the views firstView and secondView respectively. The code below is working. They start and end at the same time, but it's lagging. It's not so smooth. Is there any thing I can do to run them simultaneously and smooth?
Here's the code:
private static final int duration = 500;
protected static boolean ANIMATION_FINISHED;
protected static void onShowAnimation() {
int width;
ANIMATION_FINISHED = false;
width = getScreenWidth();
final AlphaAnimation fadeIn = new AlphaAnimation(0, 1);
final TranslateAnimation translateIn = new TranslateAnimation(-width, 0, 1, 1);
fadeIn.setDuration(duration);
translateIn.setDuration(duration);
firstView.startAnimation(translateIn);
secondView.startAnimation(fadeIn);
secondView.postDelayed(new Runnable() {
#Override
public void run() {
ANIMATION_FINISHED = true;
}
}, duration);
}
animations up to gingerbread always been pretty laggy in Android. In Honeycomb and forward they fixed it with the new framework, but then the developers (that's you) must know which framework to use.
to have a nice smooth hardware accelerated animation you should use the android.animation not the android.view.animation
Here you have the complete guide for the property animation framework: https://developer.android.com/guide/topics/graphics/prop-animation.html
and here is the base class for it:
https://developer.android.com/reference/android/animation/package-summary.html
if you need to target devices on gingerbread or below, I suggest you to use the NineOldAndroid library that automatically handles the best possible framework for the device. (Remembering it will still be laggy on 2.3, but at least 3.0+ will be smooth).
ps.: You shouldn't use a postDelayed to know when the animation is over, instead use an Animation Listener
After searching a lot finally i successfully remove lagging during animation.
you need to set,
yourAnimatedView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
before starting animation, and after completion of animation you need to set,
yourAnimatedView.setLayerType(View.LAYER_TYPE_NONE, null);
During animations your views may be redrawn each frame. If you use view layers, instead of having to redraw each frame, views render once into an off-screen buffer which can be reused.
In addition, hardware layers are cached on the GPU, which makes certain operations during animation much faster. Simple transformations (translation, rotation, scaling and alpha) can be rendered quickly with layers. Since many animations are just a combination of these transformations, layers can supercharge animation performance
I am making a learning game for teaching numbers to kindergarten kids. It requires some animation like this but I am failing in achieving this animation smoothly in canvas.
I want exactly the same animation appearing using this dotted line.
Please guide me to perform exactly this type of animation.
What you need is:
a loop
path definition
The loop should update current image position and rotation. Then draw the updated animation on a screen. You can just override dispatchDraw method and call invalidate at the very end of it. You can update position right before drawing. Your case is similar to game programming, so you can look for articles about game loops.
Path definition should allow you to calculate position depending on time. Fo example you can use Bezier curves. Bezier curve equation is parametric and the parameter is 'time'. Using the animation time gives you smooth movement along the path.
Example of a generic game loop: http://www.gameprogblog.com/generic-game-loop/
Bezier curve: http://en.wikipedia.org/wiki/B%C3%A9zier_curve
long time = System.currentTimeMillis();
protected void dispatchDraw(Canvas canvas) {
PointF pos = getBezierPos(System.currentTimeMillis() - time);
drawImage(pos);
invalidate();
}
As we know , the android coordinate system is start from the top left corner of the android screen. The x-axis is down growth and the y-axis is right growth.But I found it's not right for the animation.
For example, I initialized the TranslateAnimation using the constructed function:
TranslateAnimation ta = new TranslateAnimation(0.0f, 200, 0.0f, 200);
Does the coordinate system have changed ? I found it didn't start from the top left corner.
Then I initialized the other translateAnimation for moving up and right direction :
TranslateAnimation ta = new TranslateAnimation(0.0f, 200, 0.0f, -200);
ta.setReaptMode(Animation.REVERSE);
The same behavior would be found.
I am confused about it.
I believe that constructor for TranslateAnimation uses deltas. See this. Or look at the constructor sig. : (float fromXDelta, float toXDelta, float fromYDelta, float toYDelta). So if you want your anim. to jump up first, you could use a negative third ctor param.
More precisely:
An animation can never start until after the layout has been measured. One usually shouldn't have to worry about how this works beyond that the algorithm is mostly very good and you can take control of its strategies by setting layout parameters. In short, by the time an animation might be started, we know where you want the view to be on the screen, because you set layout parameters.
Translate animation then takes deltas from that position. So your current animation shouldn't start from the top left, but rather wherever those layout params were evaluated by onMeasure.
Some would say- how annoying. It's gonna get complicated even if you just want to do some simple up-down type animations... Well, here's an advisable development strategy; it snould make android animation development a breeeze. Set an animationListener on every animation. In onAnimationEnd, in possibly a parametized way, reset the layout parameters on the view your animating to where you expect it to be. That way, you'll get no surprising "jumps" when you re-apply an animation again. You may need to invalidate in some circumstances, or clearAnimation. The reason that this works is that the measure pass will be caused to come round again and you'll have a new offset for your TranslateAnimation. Finally, you may want to do all this resetting posted to the message queue of a view using post(Runnable runnable) in the listener, so you're off the last pass of the animation draw.
I too found android Animations can occasionally surprise you and cause jumpy behaviour. But if you do it like this, constructors taking delta params shouldn't be confusing again.
I have a problem while using the scroller in my application.
I have used the scroller but it is not working properly as it is not able to maintain a consistent speed while automatically scrolling. The speed of the text is getting slower with time as the text moves upward.
Here is the code that I used for this purpose:
length = prompt_text.getLineCount();
Log.d("length",""+length);
prompt_text.setScroller(scroll);
scroll.startScroll(0,0,0,10 * length, 100000 / speedAmount);
Have you tried setting up the Scroller interpolator as a LinearInterpolator?
http://developer.android.com/reference/android/widget/Scroller.html#Scroller(android.content.Context,%20android.view.animation.Interpolator)
Sample:
yourScroller = new Scroller(yourContext, new LinearInterpolator());