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,觉得应该很多人都会需要用到,希望可以帮助到大家。