// __________________________________________________
// | _ |
// | /|,/ _ _ _ / ` /_ _ . _ _/_ _ _ _ _|
// |/ / /_' / / /_/ /_, / / / / _\ / / / / /_| _\ |
// | _/ |
// | ~~** kimira **~~ |
// |__________________________________________________|
//
//
// , , , , , , , , ,
// , , , .____. , , , , ,
// , , , , | :| , , , ,
// , , , __|====|__ |||||| , ,
// , , , * / o o \ ||||||, , , ,
// , , , * | -= | \====/ , , ,
// , U==\__//__. \\// , ,
// , , , , / \\==// \ \ || , , ,
// , , ,| o || | \|| , , ,
// , , , | o "" |\_|B), , , ,
// , , , \__ --__/ || , , ,
// , , , / \ ||, , , ,
// , , | | || , , ,
// , , , ,| | || , , , , ,
// ------______------\__ --_ __/__LJ________---------_
老规矩先方效果图,吸引一波火力。
从效果图上可以看到,数字变化规律是数字先从个位开始变化,之前的数字向上移动一定距离,透明度变暗到不可见,新的数字从下向上移动一定距离到达正常展示区域,数字变化是从透明到不透明。如果个位数字刚好是到达9,需要位数递增,所以变化是个位+十位一起移动。
分析完动画效果后,我们在看看如何实现这种动画效果,根据之前的分析,应该可以有一定的思路处理了吧。
1、首先需要将数字进行打散,拆分成为一个数字数组,变化操作针对于数组中的单个元素操作即可,对整个数字的宽高度计算,可以将单个数字计算出来累积即可得到。
2、在数字变化的时候,可以看到变化的时候,数字滚动有一定间隔,所以控件的实际大小,应该是文字高度加上上下可移动间隔距离。
3、数字变化规律上面,如果数字个位<9,只在个位数字变化,如果出现9, 99, 999 这种操作的话,就需要对数字的递增位数也一起移动。要实现这个,可以保存一下之前的数字,两个数字的数组位数逐个比较,数字不同的位数一起移动即可。
4、让数字动起来,需要将老数字向上移动,并修改透明度,所以应该移动距离是(0—>-间隔距离),新数字向上移动,透明度从0到1,移动距离应该是(间隔距离—>0)。
好了,看看详细代码怎么实现。
//新数字拆分成的数组
private List flipNumbers = new ArrayList<>();
//老数字拆分成的数组
private List flipOutterNumbers = new ArrayList<>();
拆分数字成为数组
String flipNumberString = String.valueOf(mFlipNumber);
for (int i = 0; i < flipNumberString.length(); i++) {
flipNumbers.add(String.valueOf(flipNumberString.charAt(i)));
}
String flipOutterNumberString = String.valueOf(mOutterFlipNumber);
for (int i = 0; i < flipNumberString.length(); i++) {
flipOutterNumbers.add(String.valueOf(flipOutterNumberString.charAt(i)));
}
在来看看怎么绘制数字以及数组如何滚动
float curTextWidth = 0;
for (int i = 0; i < flipNumbers.size(); i++) {
//
paint.getTextBounds(flipNumbers.get(i), 0, flipNumbers.get(i).length(), textRect);
final int numWidth = textRect.width();
//这里逐个位置判断数字是否相同,用来处理个位、十位数字变化递增情况,比如老的位数位789, 新的位780,则需要处理变化的位89一起移动
if (flipNumbers.get(i).equals(flipOutterNumbers.get(i))) {
//位数数字相同,直接绘制上去不做任何动画效果
paint.setAlpha(255);
canvas.drawText(flipNumbers.get(i), getWidth() / 2 - textWidth / 2 + curTextWidth, getHeight() / 2 + textRect.height() / 2, paint);
} else {
//位数数字不同,表示需要需要处理移动操作。
paint.setAlpha((int) (255 * (1 - mCurrentAlphaValue)));
canvas.drawText(flipOutterNumbers.get(i), getWidth() / 2 - textWidth / 2 + curTextWidth, mOutterMoveHeight + getHeight() / 2 + textRect.height() / 2, paint);
paint.setAlpha((int) (255 * mCurrentAlphaValue));
canvas.drawText(flipNumbers.get(i), getWidth() / 2 - textWidth / 2 + curTextWidth, mCurrentMoveHeight + getHeight() / 2 + textRect.height() / 2, paint);
}
curTextWidth += (numWidth + 20);
}
上面的变量mOutterMoveHeight
表示的是老数字移动的距离,使用动画去动态控制,从-间隔距离到0的变化操作,以实现数字的变化效果。
mOutterFlipNumber = mFlipNumber;
mFlipNumber++;
ValueAnimator animator = ValueAnimator.ofFloat(mMaxMoveHeight, 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentMoveHeight = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(1000);
animator.start();
ValueAnimator animator1 = ValueAnimator.ofFloat(0, 1);
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentAlphaValue = (float) animation.getAnimatedValue();
invalidate();
}
});
animator1.setDuration(1000);
animator1.start();
ValueAnimator animator2 = ValueAnimator.ofFloat(0, -mMaxMoveHeight);
animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOutterMoveHeight = (float) animation.getAnimatedValue();
invalidate();
}
});
animator2.setDuration(1000);
animator2.start();
如果搞清楚了逻辑,相信实现起来并不困难。之前支付宝的金额数字动态变化效果,实际上就是让自动动起来而已,在上面的实现逻辑稍做操作就可以。
下面放出来完整的实现代码,重点是实现思路,切勿盲目直接用在代码中,没有整理成为独立控件,后面有空在完善整理啊。
public class NumberFlipView extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
private int mFlipNumber = 1990;
private int mOutterFlipNumber = mFlipNumber;
private Rect textRect = new Rect();
private final float mMaxMoveHeight;
private float mCurrentMoveHeight;
private float mOutterMoveHeight;
private float mCurrentAlphaValue;
private List flipNumbers = new ArrayList<>();
private List flipOutterNumbers = new ArrayList<>();
public NumberFlipView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
paint.setColor(Color.WHITE);
int fontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 54, context.getResources().getDisplayMetrics());
paint.setTextSize(fontSize);
paint.setStyle(Paint.Style.STROKE);
mMaxMoveHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, context.getResources().getDisplayMetrics());
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
flipNumbers.clear();
flipOutterNumbers.clear();
String flipNumberString = String.valueOf(mFlipNumber);
for (int i = 0; i < flipNumberString.length(); i++) {
flipNumbers.add(String.valueOf(flipNumberString.charAt(i)));
}
String flipOutterNumberString = String.valueOf(mOutterFlipNumber);
for (int i = 0; i < flipNumberString.length(); i++) {
flipOutterNumbers.add(String.valueOf(flipOutterNumberString.charAt(i)));
}
paint.getTextBounds(String.valueOf(mFlipNumber), 0, String.valueOf(mFlipNumber).length(), textRect);
final int textWidth = textRect.width() + 80;
float curTextWidth = 0;
for (int i = 0; i < flipNumbers.size(); i++) {
paint.getTextBounds(flipNumbers.get(i), 0, flipNumbers.get(i).length(), textRect);
final int numWidth = textRect.width();
if (flipNumbers.get(i).equals(flipOutterNumbers.get(i))) {
paint.setAlpha(255);
canvas.drawText(flipNumbers.get(i), getWidth() / 2 - textWidth / 2 + curTextWidth, getHeight() / 2 + textRect.height() / 2, paint);
} else {
paint.setAlpha((int) (255 * (1 - mCurrentAlphaValue)));
canvas.drawText(flipOutterNumbers.get(i), getWidth() / 2 - textWidth / 2 + curTextWidth, mOutterMoveHeight + getHeight() / 2 + textRect.height() / 2, paint);
paint.setAlpha((int) (255 * mCurrentAlphaValue));
canvas.drawText(flipNumbers.get(i), getWidth() / 2 - textWidth / 2 + curTextWidth, mCurrentMoveHeight + getHeight() / 2 + textRect.height() / 2, paint);
}
curTextWidth += (numWidth + 20);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
jumpNumber();
}
return super.onTouchEvent(event);
}
private void jumpNumber() {
mOutterFlipNumber = mFlipNumber;
mFlipNumber++;
ValueAnimator animator = ValueAnimator.ofFloat(mMaxMoveHeight, 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentMoveHeight = (float) animation.getAnimatedValue();
invalidate();
}
});
animator.setDuration(1000);
animator.start();
ValueAnimator animator1 = ValueAnimator.ofFloat(0, 1);
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentAlphaValue = (float) animation.getAnimatedValue();
invalidate();
}
});
animator1.setDuration(1000);
animator1.start();
ValueAnimator animator2 = ValueAnimator.ofFloat(0, -mMaxMoveHeight);
animator2.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mOutterMoveHeight = (float) animation.getAnimatedValue();
invalidate();
}
});
animator2.setDuration(1000);
animator2.start();
}
}