最近在掘金这个干货平台上发了几篇博文,这里进入我的掘金主页。
看掘金APP中文章数据的数字滚动起来很动感,效果很棒,
于是决定把它通过自定义View编写出来,方便自己和大家调用。
本文Github代码链接
https://github.com/AndroidMsky/RandomTextView
先看看掘金的效果:
我们自己实现的效果:
接下来介绍一下我的自定义View RandomTextView的用法和原理
用法
考入
只有200行绝对轻量方便。
xml中定义:
<com.example.liangmutian.randomtextview.view.RandomTextView android:id="@+id/rtv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:padding="0px" android:text="123456" android:textSize="28sp"/>
很开心的事,RandomTextView继承自TextView所以可以使用TextView的所有方法。color,size等等直接去定义就OK啦。
所有位数相同速度滚动:
mRandomTextView.setText("876543"); mRandomTextView.setPianyilian(RandomTextView.ALL); mRandomTextView.start();
从左到右侧由快到慢滚动:
mRandomTextView.setText("12313288"); mRandomTextView.setPianyilian(RandomTextView.FIRSTF_FIRST); mRandomTextView.start();
从左到右侧由慢到快滚动:
mRandomTextView.setText("9078111123"); mRandomTextView.setPianyilian(RandomTextView.FIRSTF_LAST); mRandomTextView.start();
自定义每位数字的速度滚动(每帧滚动的像素):
mRandomTextView.setText("909878"); pianyiliang[0] = 7; pianyiliang[1] = 6; pianyiliang[2] = 12; pianyiliang[3] = 8; pianyiliang[4] = 18; pianyiliang[5] = 10; mRandomTextView.setPianyilian(pianyiliang); mRandomTextView.start();
自定义滚动行数(默认10行):
mRandomTextView.setMaxLine(20);
原理
用TextView去绘制10(maxLine可设置)行文字,调用canvas.drawText去绘制出来,在绘制的Y坐标不断增加便宜量,去改变绘制的高度,通过handler.postDelayed(this, 20);不断增加便宜量,并且不断判断所有位数字最后一行绘制完毕的时候,结束handler的循环调用。
需要的变量:
OnDraw方法:
@Override protected void onDraw(Canvas canvas) { if (firstIn) { firstIn = false; super.onDraw(canvas); p = getPaint(); Paint.FontMetricsInt fontMetrics = p.getFontMetricsInt(); baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top; float[] widths = new float[4]; p.getTextWidths("9999", widths); f0 = widths[0]; invalidate(); } drawNumber(canvas);
自一次进入onDraw方法时,做了如下几件事情:
1.去获取当前正确的画笔p = getPaint();从而保证xml中配置的大小颜色等有效。
2.通过当前画笔去计算正确的drawText基准线。
baseline = (getMeasuredHeight() - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top;
3.等到数字的宽度。方便横向绘制。
p.getTextWidths(“9999”, widths);f0 = widths[0];
4.直接通知view重绘。
invalidate();
我们自己的绘制drawNumber方法:
private void drawNumber(Canvas canvas) { for (int j = 0; j < numLength; j++) { for (int i = 1; i < maxLine; i++) { if (i == maxLine - 1 && i * baseline + pianyiliangSum[j] <= baseline) { pianyilianglist[j] = 0; overLine[j] = 1; int auto = 0; for (int k = 0; k < numLength; k++) { auto += overLine[k]; } if (auto == numLength * 2 - 1) { this.auto = false; handler.removeCallbacks(task); invalidate(); } } if (overLine[j] == 0) canvas.drawText(setBack(arrayListText.get(j), maxLine - i - 1) + "", 0 + f0 * j, i * baseline + pianyiliangSum[j], p); else { //定位后画一次就好啦 if (overLine[j] == 1) { overLine[j]++; canvas.drawText(arrayListText.get(j) + "", 0 + f0 * j, baseline, p); } //break; }} }}
这里逻辑想对复杂时间复杂度达到了O(绘制行数*字符串位数),是个双重循环的绘制。
第一层我们称之为J循环,J循环每次循环的内容是绘制一列。
第二层循环称之为I循环,I循环负责绘制每行的每一个字符。
每次进入I循环的第一件事情是检查当前字符位,是不是最后一个
f (i == maxLine - 1 && i * baseline + pianyiliangSum[j] <= baseline)
如果是,则归零便宜量,修改标志位
pianyilianglist[j] = 0; overLine[j] = 1;
之后去判段所有字符位是否全部绘制到最后一个:
int auto = 0; for (int k = 0; k < numLength; k++) { auto += overLine[k];} if (auto == numLength * 2 - 1) { this.auto = false; handler.removeCallbacks(task); invalidate();}
如果是则讲自动循环刷新的方法取消掉,并且通知view进行最后一次定位绘制。
以上就是进入i循环先对是否绘制结束的判断。
如果没有结束那么继续绘制:
if (overLine[j] == 0) canvas.drawText(setBack(arrayListText.get(j), maxLine - i - 1) + "", 0 + f0 * j,i * baseline +pianyiliangSum[j], p); else { if (overLine[j] == 1) { //定位后画一次就好啦 overLine[j]++; canvas.drawText(arrayListText.get(j) + "", 0 + f0 * j, baseline, p); } }
overLine[j]中的值的意思为:0表示还没绘制到最后一行,1表示为绘制到最后一行没有进行最后的定位绘制,2表示已经进行了定位绘制。
可能对于初学者最难的就是drawText的坐标问题,x坐标比较简单
就是字符的宽度并且随着循环去变化:
0 + f0 * j
Y坐标就是当前行的基准值+上当前便宜量:
i * baseline + pianyiliangSum[j]
每隔20毫秒去计算当前便宜量并通知刷新view:
private final Runnable task = new Runnable() { public void run() { // TODO Auto-generated method stub if (auto) { handler.postDelayed(this, 20); for (int j = 0; j < numLength; j++) { pianyiliangSum[j] -= pianyilianglist[j]; } invalidate(); } } };
帮助计算9上面的是几。8上面是几
//设置上方数字0-9递减 private int setBack(int c, int back) { if (back == 0) return c; back = back % 10; int re = c - back; if (re < 0) re = re + 10; return re; }
讲字符串转换为INT数组:
private ArrayList<Integer> getList(String s) { ArrayList<Integer> arrayList = new ArrayList<Integer>(); for (int i = 0; i < s.length(); i++) { String ss = s.substring(i, i + 1); int a = Integer.parseInt(ss); arrayList.add(a); } return arrayList; }
回顾
在自定义view的时候如果你的view是像本文一样,循环去绘制不断刷新的话,就意味着onDraw方法会随着你view的帧数不断的被调用,一秒可能被执行几十次,所以写在这里的方法,一定要小心为妙,比如一些无需每次都初始化的变量切记不可以定义在onDraw方法里,比如本文的getText();方法去获取当前TextView的内容,就要写在外面。但是可能有些方法你必须在super.onDraw(canvas),以后才可以获取的比如getPaint();那么我们就可以加个布尔值firstIn来控制只有第一次进入onDraw方法才去执行,或者其它的只做一次的事情都可以这样去控制。
循环绘制动画效果我们一定要理清两条线,一条是每一帧绘制什么,另一条是动画结束你都绘制了什么。
第一条线应该注意你绘制的只是一个瞬间,是个不断重复执行的线。
第二条线就是无数个第一条线加上时间点共同组成的,主要就是控制每次的不同,比如本文中增加的偏移量,是数据(本文中每一个字符的坐标)的变化,去影响onDraw方法,绘制出不通的东西呈现在屏幕上。第二条线还要控制好什么时候结束所有的第一条线,也就是整个动画结束的条件,本文中的例子讲是一旦所有字符的最后一行都超过或者等于TextView的基准线,那么整个动画结束。
绘制原理的逻辑就讲完啦,RandomTextView可以投入使用啦,自定义view并不难,只要你知道安卓API能让你能干什么,你想干什么,你可能马上就知道你应该怎么做啦。
欢迎关注作者。欢迎评论讨论。欢迎拍砖。
如果觉得这篇文章对你有帮助 欢迎打赏,
欢迎star,Fork我的github。
喜欢作者的也可以Follow。也算对作者的一种支持。
本文Github代码链接
https://github.com/AndroidMsky/RandomTextView
作者:AndroidMsky