Skip to content

Instantly share code, notes, and snippets.

@jankovd
Last active January 21, 2022 11:47
Show Gist options
  • Save jankovd/8279075 to your computer and use it in GitHub Desktop.
Save jankovd/8279075 to your computer and use it in GitHub Desktop.
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
/**
* http://sriramramani.wordpress.com/2013/10/14/number-tweening
* <br/>
* Note that this is just an example of how you can do number morphing,
* you'll still need to modify the View so it can support resizing,
* correct update delay, etc.
*
* <br/>
* -added the control points
*/
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][1]
+ ((next[0][1] - current[0][1]) * 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][1] + ((next1[i - 1][1] - curr1[i - 1][1]) * factor),
curr2[i - 1][0] + ((next2[i - 1][0] - curr2[i - 1][0]) * factor),
curr2[i - 1][1] + ((next2[i - 1][1] - curr2[i - 1][1]) * factor), current[i][0]
+ ((next[i][0] - current[i][0]) * factor), current[i][1]
+ ((next[i][1] - current[i][1]) * 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);
}
}
}
@zst123
Copy link

zst123 commented Mar 1, 2014

Hi @jankovd,

I have made some changes to your awesome code.
https://gist.github.com/zst123/9283773/revisions

It should now support resizing. The stroke width should also adjust depending on how large the view is. I have also tweaked the update delay to 1000ms and is customizable by only changing only one integer DELAY_FRAMES and the rest will adapt to the new timing.

Regards,
zst123

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment