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