NumberRunningTextView(数字会滚动的 TextView)

3,549 阅读9分钟

转载请注明出处:blog.csdn.net/chay_chan/a…

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

NumberRunningTextView介绍

  NumberRunningTextView是一个自带数字滚动动画的TextView,通过使用setContent(String str)方法,传入相应的金额数字字符串(如”1354.00”),或者数字的字符串(如200),当页面初始化完成的时候,就可以看到数字滚动的效果,和支付宝中进入余额宝界面,显示余额滚动的效果类似,具体的效果如下:

使用

在布局文件中,使用NumberRunningTextView,代码如下:

演示金额滚动的NumberRunningTextView

 <com.chaychan.viewlib.NumberRunningTextView
            android:id="@+id/tv_money"
            android:layout_width="wrap_content"
            android:layout_height="wrap_cointent"
            android:layout_centerInParent="true"
            android:text="0.00"
            android:textColor="#fff"
            android:textSize="30sp"
            android:textStyle="bold"
            />

演示整数数字滚动的NumberRunningTextView

 <com.chaychan.viewlib.NumberRunningTextView
            android:id="@+id/tv_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="200"
            android:textColor="#fff"
            android:textSize="30sp"
            app:textType="num"
            />

  二者的区别在于textType的设置,textType是用于指定内容的格式,总共有money(金钱格式,带小数)和num(整数格式),默认是金钱的格式,不配置textType的话,默认就是使用金钱格式。

Java文件中根据id找到对应的view后,调用setContent()方法即可。

 tvMoney.setContent("1354.00");
 tvNum.setContent("200");

关闭金额的自动格式化(每三位数字添加一个逗号)

  上图所示,最后显示的金额数字经过了处理,每三位添加一个逗号,这使得数字看起来比较好看,金额默认是使用这种格式,如果不想要这种格式的数字,可以在布局文件中,NumberRunningTextView的配置中,将useCommaFormat设置为false,这样最终的数字就不会是带有逗号了,效果如下:

关闭执行动画的时机

  当一开始进入界面的时候,初始化数据完毕,NumberRunningTextView设置数据完毕,会自动执行数字滚动的动画,如果进行刷新操作,从服务器获取新的数据,重新设置数据,NumberRunningTextView会自动判断传入的内容是否有变化,如果没有变化,则不会再次滚动,这和支付宝的余额宝界面中的金额类似,当在余额宝界面下拉刷新时,金额没有变化,数字不会再次滚动,而当提现后重新回到该界面,金额发生变化后,就会再次滚动,效果如下:

SwipeRefreshLayout的刷新回调中,只做了这样的处理,NumberRunningTextView设置的内容还是原来的数据。

 srlRoot.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            tvMoney.setContent("1354.00");
            tvNum.setContent("200");
            srlRoot.setRefreshing(false);
        }
    });

  当你进行下拉刷新的时候,内容如果没有发生变化,数字是不会滚动的,如果内容发生变化,数字又会重新进行滚动,这里修改下拉刷新的代码,模拟数据变化的情况,演示一下:

 srlRoot.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            tvMoney.setContent("1454.00");
            tvNum.setContent("300");
            srlRoot.setRefreshing(false);
        }
    });

效果如下:

  如果想要在刷新的时候,无论内容是否有变化都要执行滚动动画的话,可以在布局文件中,NumberRunningTextView的配置中,将runWhenChange设置为false即可,此时,无论内容是否有变化,都会执行滚动动画,效果如下:

修改帧数

  NumberRunningTextView默认是30帧,如果需要修改帧数,可以在布局文件中,NumberRunningTextView的配置中,将frameNum设置为自己想要的帧数。

NumberRunningTextView源码解析

  NumberRunningTextView的原理其实很简单,只是将一个数字除以帧数(默认是30帧),分成每段固定的大小进行递增。

首先介绍下NumberRunningTextView自定义的属性:

<declare-styleable name="NumberRunningTextView">
    <!--帧数-->
    <attr name="frameNum" format="integer"></attr>
    <!--内容的格式-->
    <attr name="textType">
        <enum name="money" value="0"></enum>
        <enum name="num" value="1"></enum>
    </attr>
    <!--是否使用每三位数字一个逗号-->
    <attr name="useCommaFormat" format="boolean"></attr>
    <!--是否当内容改变的时候使用动画,不改变则不使用动画-->
    <attr name="runWhenChange" format="boolean"></attr>

</declare-styleable>

NumberRunningTextView继承于TextView

public class NumberRunningTextView extends TextView {

    public NumberRunningTextView(Context context) {
        this(context, null);
    }

    public NumberRunningTextView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.textViewStyle);
    }

    public NumberRunningTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.NumberRunningTextView);
        frameNum = ta.getInt(R.styleable.NumberRunningTextView_frameNum, 30);
        textType = ta.getInt(R.styleable.NumberRunningTextView_textType, MONEY_TYPE);
        useCommaFormat = ta.getBoolean(R.styleable.NumberRunningTextView_useCommaFormat, true);
        runWhenChange = ta.getBoolean(R.styleable.NumberRunningTextView_runWhenChange,true);
    }

    ...

}

当调用setContent()方法时,里面做了相应的判断。

  /**
 * 设置需要滚动的金钱(必须为正数)或整数(必须为正数)的字符串
 *
 * @param str
 */
public void setContent(String str) {
    //如果是当内容改变的时候才执行滚动动画,判断内容是否有变化
    if (runWhenChange){
        if (TextUtils.isEmpty(preStr)){
            //如果上一次的str为空
            preStr = str;
            useAnimByType(str);
            return;
        }

        //如果上一次的str不为空,判断两次内容是否一致
        if (preStr.equals(str)){
            //如果两次内容一致,则不做处理
            return;
        }

        preStr = str;//如果两次内容不一致,记录最新的str
    }

    useAnimByType(str);
}

  如果runWhenChange为true,即当内容改变的时候才会滚动,进行如下判断,如果上次的内容为空(即第一次设置),则执行滚动,否则判断此次内容和上一次内容是否一致,如果一致,则不做处理,如果两次内容不一致,即内容发生了变化,记录当前的内容,并执行相应的滚动操作。

useAnimByType()方法只是用于判断内容的类型,并执行相应的动画,NumRunningTextView的核心为playMoneyAnim()和playNumAnim()

  private void useAnimByType(String str) {
    if (textType == MONEY_TYPE) {
        playMoneyAnim(str);
    } else {
        playNumAnim(str);
    }
}

下面对这两个方法进行解析:

/**
 * 播放金钱数字动画的方法
 *
 * @param moneyStr
 */
public void playMoneyAnim(String moneyStr) {
    String money = moneyStr.replace(",", "").replace("-", "");//如果传入的数字已经是使用逗号格式化过的,或者含有符号,去除逗号和负号
    try {
        finalMoneyNum = Double.parseDouble(money);
        if (finalMoneyNum == 0) {
            //如果传入的为0,则直接使用setText()
            NumberRunningTextView.this.setText(moneyStr);
            return;
        }
        nowMoneyNum = 0;//记录每帧增加后的数字,一开始为0
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                Message msg = handler.obtainMessage();
                double size = finalMoneyNum / frameNum;//数字除以帧数,得到每帧跳跃的大小
                msg.what = MONEY_TYPE;
                msg.obj = size < 0.01 ? 0.01 : size;// 如果每帧的间隔比0.01小,就设置为0.01
                handler.sendMessage(msg);// 发送通知改变UI
            }
        });

    } catch (NumberFormatException e) {
        e.printStackTrace();
        NumberRunningTextView.this.setText(moneyStr);//如果转换Double失败则直接用setText
    }
}

1.先对传入的字符串进行修改,如果有含有逗号或者负号,则去除逗号和负号;

2.对字符串进行格式转换,如果转换出现异常,则直接调用setText()方法设置内容;

3.如果传入的数字是0,则不用使用滚动动画,直接调用setText()设置内容;

4.如果传入的是正确的金额数字,将数字除以帧数frameNum,得到每一帧的大小(间隔),通过使用handler的sendMessage,传递当前内容的类型和每一帧数的大小,在Handler的handleMessage()方法中进行相应的处理。其中的threadPool为含有一个线程的线程池。threadPool = Executors.newFixedThreadPool(1);

Handler中的相应处理:

 private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MONEY_TYPE://金钱数字的滚动
                String str = formatter.format(nowMoneyNum).toString();//保留两位小数的字符串

                // 更新显示的内容
                if (useCommaFormat) {
                    //使用每三位数字一个逗号的格式
                    String formatStr = StringUtils.addComma(str);//三位一个逗号格式的字符串
                    NumberRunningTextView.this.setText(formatStr);
                } else {
                    NumberRunningTextView.this.setText(str);
                }

                nowMoneyNum += (double) msg.obj;//记录当前每帧递增后的数字

                if (nowMoneyNum < finalMoneyNum) {
                    //如果当前记录的金额数字小于目标金额数字,即还没达到目标金额数字,继续递增
                    Message msg2 = handler.obtainMessage();
                    msg2.what = MONEY_TYPE;
                    msg2.obj = msg.obj;
                    handler.sendMessage(msg2);// 继续发送通知改变UI
                } else {
                    //已经达到目标的金额数字,显示最终的数字
                    if (useCommaFormat) {
                        NumberRunningTextView.this.setText(StringUtils.addComma(formatter.format(finalMoneyNum)));
                    } else {
                        NumberRunningTextView.this.setText(formatter.format(finalMoneyNum));
                    }
                }
                break;

            case NUM_TYPE://普通数字滚动
                ...
                break;
        }
    }
};

handleMessage()方法中,根据内容的类型进行相应的处理。

1.根据是否使用逗号进行格式化,显示每帧增加后的数字;

2.如果其小于finalMoneyNum目标数字(最终的数字),则继续调用sendMessage()方法,形成递归操作,直至等于或大于目标数字;

3.达到目标数字后,根据是否需要使用逗号进行格式化显示最终的效果

其中formatter = new DecimalFormat(“0.00”);// 格式化金额,保留两位小数,用于使double类型数字保留两位小数。

  到这里具体的设计思路应该都清楚了,其实就是通过使用handler不断发消息,形成递归,让数字进行递增,当达到最终的数字的时候,就停止发送消息,直接设置最终的内容。playNumAnim()也是一样的思路,在这里就不重复说明了,感觉自己的写的注释已经足够详细了,顺便贴一下playNumAnim()的方法。

/**
 * 播放数字动画的方法
 *
 * @param numStr
 */
public void playNumAnim(String numStr) {
    String num = numStr.replace(",", "").replace("-", "");//如果传入的数字已经是使用逗号格式化过的,或者含有符号,去除逗号和负号
    try {
        finalNum = Integer.parseInt(num);
        if (finalNum < frameNum) {
            //由于是整数,每次是递增1,所以如果传入的数字比帧数小,则直接使用setText()
            NumberRunningTextView.this.setText(numStr);
            return;
        }
        nowNum = 0;// 默认都是从0开始动画
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                Message msg = handler.obtainMessage();
                int temp = finalNum / frameNum;//数字除以帧数,得到每帧跳跃的大小
                msg.what = NUM_TYPE;
                msg.obj = temp;
                handler.sendMessage(msg);// 发送通知改变UI
            }
        });

    } catch (NumberFormatException e) {
        e.printStackTrace();
        NumberRunningTextView.this.setText(numStr);//如果转换Double失败则直接用setText
    }
}

handler中对应的处理:

 private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case MONEY_TYPE://金钱数字的滚动
                ...
                break;

            case NUM_TYPE://普通数字滚动
                NumberRunningTextView.this.setText(String.valueOf(nowNum));
                nowNum += (Integer) msg.obj;//记录当前每帧递增后的数字
                if (nowNum < finalNum) {
                    //如果当前记录的数字小于目标数字,即还没达到目标数字,继续递增
                    Message msg2 = handler.obtainMessage();
                    msg2.what = NUM_TYPE;
                    msg2.obj = msg.obj;
                    handler.sendMessage(msg2);// 继续发送通知改变UI
                } else {
                    //已经达到目标的数字,显示最终的内容
                    NumberRunningTextView.this.setText(String.valueOf(finalNum));
                }
                break;
        }
    }
};

到这里为止,NumberRunningTextView的使用介绍和源码解析就结束了。下面介绍该控件的导入方式

导入方式

在项目根目录下的build.gradle中的allprojects{}中,添加jitpack仓库地址,如下:

allprojects {
    repositories {
        jcenter()
        maven { url 'https://jitpack.io' }//添加jitpack仓库地址
    }
}

打开app的module中的build.gradle,在dependencies{}中,添加依赖,如下:

dependencies {
        ......
        compile 'com.github.chaychan:PowerfulViewLibrary:1.1.0'
}

源码github地址:github.com/chaychan/Po…

  依旧花了半天时间,完成了我的第四篇博客。上次写博客是在3月8号,其实一直想写多一点博客,但是一直觉得要么不写,要写就要写质量高的博客,虽然需要耗费许久的时间,因为我写文章都追求清晰明了,通俗易懂,不让读者觉得一头雾水,追求质量而不是数量。写博客也要有好的题材,想到哪个知识点比较让读者感兴趣和可以帮助到读者日常的开发,此次写这个NumberRunningTextView是在做项目的时候,在显示用户余额的时候,根据需求需要数字滚动的效果,所以就上网找了思路,自己写了这样一个View,觉得应该很多人都会需要用到,希望可以帮助到大家。