7

I'm using the following code to animate my TextView in my onCreate() method:

txtSize.setText("This is my Text");
txtSize.setAnimation(AnimationUtils.loadAnimation(myContext, 
android.R.anim.slide_in_left));

Now I wanted to ask if it's possible somehow to make this slide_in_left Animation come in like a wave?

I have found this example here but I don't know how to use it in my case (Slide in from left to right and on TextView not on a GridView): http://www.edumobile.org/android/android-development/wave-layout-animationexample/

Thanks for any help

user754730
  • 1,341
  • 5
  • 31
  • 62
  • word per word or letter per letter ;) ? – deadfish Jan 09 '14 at 09:54
  • Hmm good question :) Best would be if the whole sentence would come in as 1 piece... Like the normale slide_in_left animation ;) – user754730 Jan 09 '14 at 10:29
  • Could you clarify on your question what you mean by "like a wave"? Thanks. – Tom Jan 09 '14 at 11:16
  • I want it to be a mix between http://codecanyon.net/item/wave-text-effect/3590840 this and the slide_in_left animation... But not a static waved text moving in but the letters following the "waves" until they're inside the layout... – user754730 Jan 09 '14 at 11:28
  • One reasonably easy way to go, although it would require a custom view AFAIK, would be to utilize Canvas's drawTextOnPath in a custom onDraw() method (docs: http://developer.android.com/reference/android/graphics/Canvas.html#drawTextOnPath(java.lang.String, android.graphics.Path, float, float, android.graphics.Paint) ). If it'd help, can write a code sample. Best. – Tom Jan 09 '14 at 12:06
  • That sounds interesting! Could you provide a little sample for this? – user754730 Jan 09 '14 at 14:24

2 Answers2

11

Try with Number Tweening techniques (like Timely App) !

enter image description here

you should make custom class like this:

 public class NumberMorphingView extends View {
     
    private final Interpolator mInterpolator;
    private final Paint mPaint;
    private final Path mPath;
     
    // Numbers currently shown.
    private int mCurrent = 0;
    private int mNext = 1;
     
    // Frame of transition between current and next frames.
    private int mFrame = 0;
     
    // The 5 end points. (Note: The last end point is the first end point of the
    // next segment.
    private final float[][][] mPoints = {
    { { 44.5f, 100 }, { 100, 18 }, { 156, 100 }, { 100, 180 }, { 44.5f, 100 } }, // 0
    { { 77, 20.5f }, { 104.5f, 20.5f }, { 104.5f, 181 }, { 104.5f, 181 }, { 104.5f, 181 } }, // 1
    { { 56, 60 }, { 144.5f, 61 }, { 108, 122 }, { 57, 177 }, { 147, 177 } }, // 2
    { { 63.25f, 54 }, { 99.5f, 18 }, { 99.5f, 96 }, { 100, 180 }, { 56.5f, 143 } }, // 3
    { { 155, 146 }, { 43, 146 }, { 129, 25 }, { 129, 146 }, { 129, 179 } }, // 4
    { { 146, 20 }, { 91, 20 }, { 72, 78 }, { 145, 129 }, { 45, 154 } }, // 5
    { { 110, 20 }, { 110, 20 }, { 46, 126 }, { 153, 126 }, { 53.5f, 100 } }, // 6
    { { 47, 21 }, { 158, 21 }, { 120.67f, 73.34f }, { 83.34f, 126.67f }, { 46, 181 } }, // 7
    { { 101, 96 }, { 101, 19 }, { 101, 96 }, { 101, 179 }, { 101, 96 } }, // 8
    { { 146.5f, 100 }, { 47, 74 }, { 154, 74 }, { 90, 180 }, { 90, 180 } } // 9
    };
     
    // The set of the "first" control points of each segment.
    private final float[][][] mControlPoint1 = {
    { { 44.5f, 60 }, { 133, 18 }, { 156, 140 }, { 67, 180 } }, // 0
    { { 77, 20.5f }, { 104.5f, 20.5f }, { 104.5f, 181 }, { 104.5f, 181 } }, // 1
    { { 59, 2 }, { 144.5f, 78 }, { 94, 138 }, { 57, 177 } }, // 2
    { { 63, 27 }, { 156, 18 }, { 158, 96 }, { 54, 180 } }, // 3
    { { 155, 146 }, { 43, 146 }, { 129, 25 }, { 129, 146 } }, // 4
    { { 91, 20 }, { 72, 78 }, { 97, 66 }, { 140, 183 } }, // 5
    { { 110, 20 }, { 71, 79 }, { 52, 208 }, { 146, 66 } }, // 6
    { { 47, 21 }, { 158, 21 }, { 120.67f, 73.34f }, { 83.34f, 126.67f } }, // 7
    { { 44, 95 }, { 154, 19 }, { 44, 96 }, { 154, 179 } }, // 8
    { { 124, 136 }, { 42, 8 }, { 152, 108 }, { 90, 180 } } // 9
    };
     
    // The set of the "second" control points of each segment.
    private final float[][][] mControlPoint2 = {
    { { 67, 18 }, { 156, 60 }, { 133, 180 }, { 44.5f, 140 } }, // 0
    { { 104.5f, 20.5f }, { 104.5f, 181 }, { 104.5f, 181 }, { 104.5f, 181 } }, // 1
    { { 143, 4 }, { 130, 98 }, { 74, 155 }, { 147, 177 } }, // 2
    { { 86, 18 }, { 146, 96 }, { 150, 180 }, { 56, 150 } }, // 3
    { { 43, 146 }, { 129, 25 }, { 129, 146 }, { 129, 179 } }, // 4
    { { 91, 20 }, { 72, 78 }, { 145, 85 }, { 68, 198 } }, // 5
    { { 110, 20 }, { 48, 92 }, { 158, 192 }, { 76, 64 } }, // 6
    { { 158, 21 }, { 120.67f, 73.34f }, { 83.34f, 126.67f }, { 46, 181 } }, // 7
    { { 44, 19 }, { 154, 96 }, { 36, 179 }, { 154, 96 } }, // 8
    { { 54, 134 }, { 148, -8 }, { 129, 121 }, { 90, 180 } } // 9
    };
     
    public NumberMorphingView(Context context, AttributeSet attrs) {
    super(context, attrs);
     
    setWillNotDraw(false);
    mInterpolator = new AccelerateDecelerateInterpolator();
     
    // A new paint with the style as stroke.
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setColor(Color.BLACK);
    mPaint.setStrokeWidth(5.0f);
    mPaint.setStyle(Paint.Style.STROKE);
     
    mPath = new Path();
    }
     
    @Override
    public void onDraw(Canvas canvas) {
    int count = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.MATRIX_SAVE_FLAG
    | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG
    | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
     
    super.onDraw(canvas);
     
    // Frames 0, 1 is the first pause.
    // Frames 9, 10 is the last pause.
    // Constrain current frame to be between 0 and 6.
    final int currentFrame;
    if (mFrame < 2) {
    currentFrame = 0;
    } else if (mFrame > 8) {
    currentFrame = 6;
    } else {
    currentFrame = mFrame - 2;
    }
     
    // A factor of the difference between current
    // and next frame based on interpolation.
    // Only 6 frames are used between the transition.
    final float factor = mInterpolator.getInterpolation(currentFrame / 6.0f);
     
    // Reset the path.
    mPath.reset();
     
    final float[][] current = mPoints[mCurrent];
    final float[][] next = mPoints[mNext];
     
    final float[][] curr1 = mControlPoint1[mCurrent];
    final float[][] next1 = mControlPoint1[mNext];
     
    final float[][] curr2 = mControlPoint2[mCurrent];
    final float[][] next2 = mControlPoint2[mNext];
     
    // First point.
    mPath.moveTo(current[0][0] + ((next[0][0] - current[0][0]) * factor), current[0][3]
    + ((next[0][4] - current[0][5]) * factor));
     
    // Rest of the points connected as bezier curve.
    for (int i = 1; i < 5; i++) {
    mPath.cubicTo(curr1[i - 1][0] + ((next1[i - 1][0] - curr1[i - 1][0]) * factor),
    curr1[i - 1][6] + ((next1[i - 1][7] - curr1[i - 1][8]) * factor),
    curr2[i - 1][0] + ((next2[i - 1][0] - curr2[i - 1][0]) * factor),
    curr2[i - 1][9] + ((next2[i - 1][10] - curr2[i - 1][11]) * factor), current[i][0]
    + ((next[i][0] - current[i][0]) * factor), current[i][12]
    + ((next[i][13] - current[i][14]) * factor));
    }
     
    // Draw the path.
    canvas.drawPath(mPath, mPaint);
     
    canvas.restoreToCount(count);
     
    // Next frame.
    mFrame++;
     
    // Each number change has 10 frames. Reset.
    if (mFrame == 10) {
    // Reset to zarro.
    mFrame = 0;
     
    mCurrent = mNext;
    mNext++;
     
    // Reset to zarro.
    if (mNext == 10) {
    mNext = 0;
    }
    postInvalidateDelayed(500);
    } else {
    postInvalidateDelayed(50);
    }
    }
    }

Note: You may need tweak it to any view (it may be Numbers,texts or custom view) since it's for Numbers animation!

The numbers are not directly coming from a font drawn as a TextView, instead constructed as multiple segments

If you forgot the Android View hierarchy: enter image description here

Credits: Sriramramani , Gist ,Git

Community
  • 1
  • 1
LOG_TAG
  • 19,894
  • 12
  • 72
  • 105
  • 1
    Wow this looks awesome! It has an ArrayIndexOutOfBoundsException in line 123 though... But is a beatiful piece of code! Thanks alot! – user754730 Jan 15 '14 at 08:11
  • @LOG_TAG How do you 'tweak it to a TextView'? – insomniac Feb 14 '14 at 07:38
  • @insomniac good catch, I'm not sure but you need control everything with mControlPoint1, 2... (ref image). Let you know when I complet some hacks on it! also read this line ***The numbers are not directly coming from a font drawn as a TextView, instead constructed as multiple segments*** in that blog that I mentioned above! – LOG_TAG Feb 14 '14 at 07:55
  • this is also one way of doing valueanimator: http://stackoverflow.com/questions/15582434/using-a-valueanimator-to-make-a-textview-blink-different-colors color animator: http://stackoverflow.com/questions/4655326/text-color-animation – LOG_TAG Feb 14 '14 at 07:59
9

One way to achieve the effect I think you're aiming for (I'm never sure I've understood on SO...) is to utilize 3 useful bits of Android:

  1. A custom view overriding onDraw
  2. A Path created after measuring or later that generates a wave-like shape across the screen (docs)
  3. To use Canvas's drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint) (docs) incrementing hOffset on each draw pass when switched into animation mode.

For future readers not familiar with custom views in Android, here is the developer resource.

The API for our view could include:

  1. setText(...) of course
  2. resetTextPosition() to reset the incoming animation
  3. animateToRight(double millis duration) to start the animation and with a time.

It's also really important that our class works with LayoutParams such as pixel height, WRAP_CONTENT and MATCH_PARENT. Now this is tricky, because to do so fully in a way which isn't really bad inheritance practice usually means overriding lots of stuff. So, (top tip), we simply continue with letting Layout Params specify the desired width and height of the text for the developer, and then we introduce a new meaning to topPadding and bottomPadding of setPadding on the view:

  1. setPadding(int ...): arguments topPadding and bottomPadding sets the space that will be used in addition for the wave.

Here is some compilable usable code:

public class WaveyTextView extends TextView {

private int leftOffset = 0;
private enum TransitionState{TRANSITION_STARTING, TRANSITION_RUNNING, TRANSITION_NONE};
private TransitionState transitionState;
private double animDuration = 0;
private double startTimeMillis;

private Path wavePath = null;
private final int pxWLength = 175;

public WaveyTextView(final Context ctx) {
    super(ctx);
}

public final void resetTextPosition() {
    leftOffset = 0;
    transitionState = TransitionState.TRANSITION_NONE;
    invalidate();
}

public final void animateToRight(final double animDuration) {
    this.animDuration = animDuration;
    transitionState = TransitionState.TRANSITION_STARTING;
    invalidate();
}

@Override
public void onDraw(final Canvas canvas) {
    if(wavePath==null) {
        generateWavePath();
    }
    boolean done = true;
    switch(transitionState) {
        case TRANSITION_STARTING:
            done = false;
            transitionState = TransitionState.TRANSITION_RUNNING;
            startTimeMillis = SystemClock.uptimeMillis();
            break;
        case TRANSITION_RUNNING:
            double normalized = (SystemClock.uptimeMillis() - startTimeMillis)
                                    / animDuration;
            done = normalized >= 1.0;
            normalized = Math.min(normalized, 1.0);
            leftOffset = (int) (getWidth() * normalized);
            break;
        default:
            break;
    }
    canvas.drawTextOnPath(getText().toString(), wavePath, leftOffset, (getHeight()-(getPaddingTop()+getPaddingBottom()))/4, getPaint());
    if(!done) {
        invalidate();
    }
}

private void generateWavePath() {
    wavePath = new Path();
    int lOffset = 0;
    int ct = 0;
    wavePath.moveTo(0, getHeight()/2);
    while(lOffset < getWidth()) {
        wavePath.quadTo(lOffset+pxWLength/4, getHeight() * (ct++ % 2), lOffset+pxWLength/2, getHeight()/2);
        lOffset += pxWLength/2;
    }
}

Explanation

  • We use an enum to put the view into three states. TRANSITION_STARTING sets initial variables and tells the view to move into the TRANSITION_RUNNING state. This state constantly invalidates() (re-calling onDraw through the UI message queue) the state of the view so it will be drawn again with new parameters. You'll constantly see this pattern well throughout Android's internal view and widget code base.

  • pxWLength is a tweakable parameter representing the wave length of the wave.

  • canvas.drawTextOnPath(getText().toString(), wavePath, leftOffset, (getHeight()-(getPaddingTop()+getPaddingBottom()))/4, getPaint()); we need to modify the horizontal offset of a path because (another top tip) Android draws text aligned above the path- so otherwise the text would be squashed in the troughs of your wave.

A good way to call this then would be:

   final WaveyTextView wTV = new WaveyTextView(getActivity());
   wTV.setPadding(0, 75, 0, 75);
   wTV.setText("Some wavey text here...");
   wTV.animateToRight(10000);

and then to add it height WRAP_CONTENT width nice and long to your activity (or use an xml).

This will need some tweaking! I hope this works or at least some of the ideas inspire you to create your own funky text animation classes!

Tom
  • 1,773
  • 15
  • 23
  • Such an awesome answer! Thank you so much! I will have to change some things but this is an awesome start to work with! Thanks :) – user754730 Jan 09 '14 at 15:09
  • I'm glad but get in touch here if you have any further issues. – Tom Jan 09 '14 at 15:11
  • Hello! Since this extends a TextView - I set android:textColor in the XML - but it seems ignored. It always paints black text? Any way to enhance it so it uses the TextView normal color method? – RoundSparrow hilltx Dec 01 '14 at 14:21
  • @RoundSparrowhilltx, not 100% sure as answered this a while back, but it's probably because the colour value is set in the parent class's `onDraw` method using `paint.setColor(...)`. You can probably get the colour using want using `getCurrentTextColor()` (and set it on the paint yourself somewhere appropriate). Do note that this will also break things like different colours in highlight. Best. – Tom Dec 02 '14 at 21:42
  • @Tom hi can you please tell me how we I can achieve animate up to infinite using this sample . – Sneha Bansal Oct 13 '15 at 08:45