本文已經在微信公衆號【Android羣英傳】發表。
未經允許不得轉載。
轉載請註明作者AndroidMsky及原文鏈接
http://blog.csdn.net/androidmsky/article/details/53009886
本文Github代碼鏈接
https://github.com/AndroidMsky/RandomTextView
Github代碼已經更新爲v1.3
2017年6月13日,我們加入了對view狀態的監聽。Activity退出,view自動銷燬。不用重寫onestroy了
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
destroy();
}
2016年11-30號,一位熱心同學私信我反映會出現內存泄漏問題。特別推出v1.2檢測並且,解決內存泄漏問題,並講述一下,看過本文的直接點傳送門。
2016年11月11號,RandomTextView第一次更新爲v1.1版本吧。
(解決了這樣一個場景,一個抽獎的頁面想滾動30秒,可能maxline加到100行的數字滾動,對此我要對性能進行優化避免過度繪製,在本文最後做出解釋)
Github代碼已經更新爲v1.1
先看看X金APP的效果:
我們自己實現的效果:
接下來介紹一下我的自定義View RandomTextView的用法和原理
用法
1.倉庫
Add it in your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
Step 2. Add the dependency
dependencies {
compile 'com.github.AndroidMsky:RandomTextView:v1.3'
}
2.考入
只有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);
放置泄漏
@Override
protected void onDestroy() {
super.onDestroy();
mRandomTextView.destroy();
}
原理
用TextView去繪製10(maxLine可設置)行文字,調用canvas.drawText去繪製出來,在繪製的Y座標不斷增加便宜量,去改變繪製的高度,通過handler.postDelayed(this, 20);不斷增加偏移量,並且不斷判斷所有位數字最後一行繪製完畢的時候,結束handler的循環調用。
需要的變量:
//高位快
public static final int FIRSTF_FIRST = 0;
//高位慢
public static final int FIRSTF_LAST = 1;
//速度相同
public static final int ALL = 2;
//用戶自定義速度
public static final int USER = 3;
//偏移速度類型
private int pianyiliangTpye;
// 滾動總行數 可設置
private int maxLine = 10;
// 當前字符串長度
private int numLength = 0;
// 當前text
private String text;
//滾動速度數組
private int[] pianyilianglist;
//總滾動距離數組
private int[] pianyiliangSum;
//滾動完成判斷
private int[] overLine;
private Paint p;
//第一次繪製
private boolean firstIn = true;
//滾動中
private boolean auto = true;
//text int值列表
private ArrayList<Integer> arrayListText;
//字體寬度
private float f0;
//基準線
private int baseline;
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循環的第一件事情是檢查當前字符位,是不是最後一個
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();}
如果是則講自動循環刷新的方法取消掉,並且通知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;
}
v1.2更新內容
v1.2更新內容:
解決內存泄漏問題,
看到泄可能有點手抖,不過面對現實。
上圖:
如果反覆選擇屏幕讓Activty重新創建,就會出現內存泄漏,安利給大家內存泄漏檢測工具:leakcanary:https://github.com/square/leakcanary
配置十分簡單先是引用:(2016.11.30版本)
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
然後:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
如果檢測activity的泄漏問題,可以開啓旋轉屏幕一旋轉就重新創建activity了,這樣就反反覆覆創建activity。如上圖泄漏問題就會被推送出來,而且明確告訴你是什麼樣一個引用鏈導致的泄漏。工具很強大有麼有。本文框架的問題就是,如果RandomTextview的動畫沒有停止,那麼activity就不會被釋放掉,這樣就造成了泄漏,所以在activity中寫入:
@Override
protected void onDestroy() {
super.onDestroy();
mRandomTextView.destroy();
}
並且提供destroy方法:
public void destroy (){
auto=false;
handler.removeCallbacks(task);
}
歡迎大家提出各種問題,讓控件越來越好用謝謝。
2016.11.30 Androidmsky
v1.1更新內容
v1.1更新內容:
之前我們的思路是按照maxLine畫出每一行,但是我們最多看見2行內容,這樣是不科學的,完全中了過度繪製的圈套呀,再想如下一個場景,一個抽獎的頁面想滾動30秒,可能maxline加到100行的數字滾動,那每幀都要繪製100行的text這顯然會出現性能問題,造成掉幀的影響,所以我們隊drawtext方法進行一下攔截,新建一個drawText方法:
private void drawText(Canvas mCanvas,String text,float x,float y,Paint p){
if (y>=-measuredHeight&&y<=2*measuredHeight)
mCanvas.drawText(text + "", x,
y, p);
else return;
}
我們對y座標進行判斷,如果在textView上下各一個textView大小內,我們進行繪製,如果超出這個範圍我們直接return,不做任何處理,這樣既不影響我們的繪製邏輯又解決了過渡繪製問題。
講原來的drawText方法替換:
drawText(canvas,arrayListText.get(j) + "", 0 + f0 * j,
baseline, p);
// canvas.drawText(arrayListText.get(j) + "", 0 + f0 * j,
// baseline, p);
作者將持續維護該框架,也希望大家star,fork,issue。
共同做出一個更好的RandomTextView
2016.11.11 Androidmsky
回顧
在自定義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
歡迎加作者自營安卓開發交流羣:308372687
博主原創未經允許不許轉載。
—————————————————————————————
作者推薦:
安卓自定義view滾動數據顯示
http://blog.csdn.net/androidmsky/article/details/53009886
RecyclerView下拉刷新分頁加載性能優化和Gilde配合加載三部曲
http://blog.csdn.net/androidmsky/article/details/53115818
打造企業級網絡請求框架集合retrofit+gson+mvp
http://blog.csdn.net/androidmsky/article/details/52882722
安卓手機自動接起QQ視頻秒變攝像頭
http://blog.csdn.net/androidmsky/article/details/53066441
—————————————————————————————