android自定义View(五)、数字滚动效果实现以及原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wanggang514260663/article/details/85273674
//      __________________________________________________
//     |                    _                             |
//     | /|,/ _   _ _      / ` /_  _ .  _ _/_ _ _   _    _|
//     |/  / /_' / / /_/  /_, / / / / _\  /  / / / /_| _\ |
//     |             _/                                   |
//     |                 ~~** kimira **~~                 |
//     |__________________________________________________|
//
//
//        ,     ,   ,       ,   ,      ,     ,     ,    ,
//        ,         ,   ,   .____. ,   ,     ,      ,   ,
//          ,   ,     ,   , |   :|         ,   , ,        ,
//      ,     ,   ,       __|====|__ ||||||  ,        ,
//         ,    ,    ,  *  / o  o \  ||||||,   ,  ,      ,
//       ,   ,     ,     * | -=   |  \====/ ,       ,  ,
//                      , U==\__//__. \\//    ,  ,
//      ,    ,  , ,       / \\==// \ \ ||  ,   ,      ,
//         ,         ,  ,|    o ||  | \||   ,      ,     ,
//      ,      ,  ,      |    o ""  |\_|B),    ,  ,   ,
//         ,         ,  , \__  --__/   ||  ,        ,    ,
//          ,   ,     ,  /          \  ||,   ,   ,        ,
//       ,         ,    |            | ||      ,  ,    ,
//      ,    ,    ,    ,|            | || ,  ,  ,   , ,
//     ------______------\__ --_  __/__LJ________---------_

老规矩先方效果图,吸引一波火力。

从效果图上可以看到,数字变化规律是数字先从个位开始变化,之前的数字向上移动一定距离,透明度变暗到不可见,新的数字从下向上移动一定距离到达正常展示区域,数字变化是从透明到不透明。如果个位数字刚好是到达9,需要位数递增,所以变化是个位+十位一起移动。

分析完动画效果后,我们在看看如何实现这种动画效果,根据之前的分析,应该可以有一定的思路处理了吧。

1、首先需要将数字进行打散,拆分成为一个数字数组,变化操作针对于数组中的单个元素操作即可,对整个数字的宽高度计算,可以将单个数字计算出来累积即可得到。
2、在数字变化的时候,可以看到变化的时候,数字滚动有一定间隔,所以控件的实际大小,应该是文字高度加上上下可移动间隔距离。
3、数字变化规律上面,如果数字个位<9,只在个位数字变化,如果出现9, 99, 999 这种操作的话,就需要对数字的递增位数也一起移动。要实现这个,可以保存一下之前的数字,两个数字的数组位数逐个比较,数字不同的位数一起移动即可。
4、让数字动起来,需要将老数字向上移动,并修改透明度,所以应该移动距离是(0—>-间隔距离),新数字向上移动,透明度从0到1,移动距离应该是(间隔距离—>0)。

好了,看看详细代码怎么实现。

//新数字拆分成的数组
private List<String> flipNumbers = new ArrayList<>();
//老数字拆分成的数组
private List<String> 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<String> flipNumbers = new ArrayList<>();
   private List<String> 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();
   }
}

猜你喜欢

转载自blog.csdn.net/wanggang514260663/article/details/85273674