Last active

Embed URL

HTTPS clone URL

SSH clone URL

You can clone with HTTPS or SSH.

Download Gist
View NumberMorphingView.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
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);
}
}
}

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
Something went wrong with that request. Please try again.