安卓自定義View文章資料滾動顯示數值
本文已經在微信公眾號【Android群英傳】發表。
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。
歡迎加作者自營安卓開發交流群:308372687
博主原創未經允許不許轉載。
—————————————————————————————
作者推薦:
—————————————————————————————
相關推薦
安卓自定義View文章資料滾動顯示數值
本文已經在微信公眾號【Android群英傳】發表。 Github程式碼已經更新為v1.3 2017年6月13日,我們加入了對view狀態的監聽。Activity退出,view自動銷燬。不用重寫onestroy了 @Override prote
安卓自定義view之打造滾動的通知欄
閒談: 這段時間一直忙著做畢業設計,沒什麼時間寫blog,但是秉著分享的原則,還是要和小夥伴們分享所學的東西,如果可以的話,也希望我的部落格能夠幫助到需要的人。(打個廣告吧,有興趣的同學可以加一下我的安卓QQ群279031247,一起討論安卓開發遇到的問題)。 前言: 最近
安卓自定義View進階-手勢檢測(GestureDecetor)
Android 手勢檢測,主要是 GestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,部分內容會涉及到之前文章提及過的知識點,如果你沒看過之前的文章,可以到 自定義 View 系列 來檢視這些內容。 在開發 Android 手機應用過程中,可
安卓自定義View進階-多點觸控詳解
Android 多點觸控詳解,在前面的幾篇文章中我們大致瞭解了 Android 中的事件處理流程和一些簡單的處理方案,本次帶大家瞭解 Android 多點觸控相關的一些知識。 多點觸控 ( Multitouch,也稱 Multi-touch ),即同時接受螢幕上多個點的人機互動
安卓自定義View進階-特殊控制元件的事件處理方案
本文帶大家瞭解 Android 特殊形狀控制元件的事件處理方式,主要是利用了 Region 和 Matrix 的一些方法,超級實用的事件處理方案,相信看完本篇之後,任何奇葩控制元件的事件處理都會變得十分簡單。 不得不說,Android 對事件體系封裝的非常棒,即便對事件體系不太
安卓自定義View進階-MotionEvent詳解
Android MotionEvent 詳解,之前用了兩篇文章 事件分發機制原理 和 事件分發機制詳解 來講解事件分發,而作為事件分發主角之一的 MotionEvent 並沒有過多的說明,本文就帶大家瞭解 MotionEvent 的相關內容,簡要介紹觸控事件,主要包括 單點觸控、多點
安卓自定義View進階-事件分發機制詳解
Android 事件分發機制詳解,在上一篇文章 事件分發機制原理 中簡要分析了一下事件分發機制的原理,原理是十分簡單的,一句話就能總結:責任鏈模式,事件層層傳遞,直到被消費。 雖然原理簡單,但是隨著 Android 不斷的發展,實際運用場景也越來越複雜,所以想要徹底玩轉事件分發機制還
安卓自定義View進階-Matrix Camera
本篇依舊屬於Matrix,主要講解Camera,Android下有很多相機應用,其中的美顏相機更是不少,不過今天這個Camera可不是我們平時拍照的那個相機,而是graphic包下的Camera,專業給View拍照的相機,不過既然是相機,作用都是類似的,主要是將3D的內容拍扁變成2D
安卓自定義View進階-Matrix詳解
這應該是目前最詳細的一篇講解Matrix的中文文章了,在上一篇文章Matrix原理中,我們對Matrix做了一個簡單的瞭解,偏向理論,在本文中則會詳細的講解Matrix的具體用法,以及與Matrix相關的一些實用技巧。 ⚠️ 警告:測試本文章示例之前請關閉硬體加速。
安卓自定義View進階-Matrix原理
本文內容偏向理論,和 畫布操作 有重疊的部分,本文會讓你更加深入的瞭解其中的原理。 本篇的主角Matrix,是一個一直在後臺默默工作的勞動模範,雖然我們所有看到View背後都有著Matrix的功勞,但我們卻很少見到它,本篇我們就看看它是何方神聖吧。 由於Goog
安卓自定義View進階-PathMeasure
可以看到,在經過 Path之基本操作 Path之貝塞爾曲線 和 Path之完結篇 後, Path中各類方法基本上都講完了,表格中還沒有講解到到方法就是矩陣變換了,難道本篇終於要講矩陣了? 非也,矩陣這一部分仍在後面單獨講解,本篇主要講解 PathMeasure 這個類與 Path 的
安卓自定義View進階-Path之貝塞爾曲線
在上一篇文章Path之基本操作中我們瞭解了Path的基本使用方法,本次瞭解Path中非常非常非常重要的內容-貝塞爾曲線。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除了在API21(即安卓版本5.0)以上
安卓自定義View進階-Path之基本操作
在上一篇Canvas之圖片文字中我們瞭解瞭如何使用Canvas中繪製圖片文字,結合前幾篇文章,Canvas的基本操作已經差不多完結了,然而Canvas不僅僅具有這些基本的操作,還可以更加炫酷,本次會了解到path(路徑)這個Canvas中的神器,有了這個神器,就能創造出更多炫(zhu
安卓自定義View進階-Canvas之圖片文字
在上一篇文章Canvas之畫布操作中我們瞭解了畫布的一些基本操作方法,本次瞭解一些繪製圖片文字相關的內容。如果你對前幾篇文章講述的內容熟練掌握的話,那麼恭喜你,本篇結束之後,大部分的自定義View已經難不倒你了,當然了,這並不是終點,接下來還會有更加炫酷的技能。 一.Canva
安卓自定義View進階-分類與流程
本章節為什麼要叫進階篇?(雖然講的是基礎內容),因為從本篇開始,將會逐漸揭開自定義View的神祕面紗,每一篇都將比上一篇內容更加深入,利用所學的知識能夠製作更加炫酷自定義View,就像在臺階上一樣,每一篇都更上一層,幫助大家一步步走向人生巔峰,出任CEO,迎娶白富美。 誤
安卓自定義View進階-Path之完結篇
經歷過前兩篇 Path之基本操作 和 Path之貝塞爾曲線 的講解,本篇終於進入Path的收尾篇,本篇結束後Path的大部分相關方法都已經講解完了,但Path還有一些更有意思的玩法,應該會在後續的文章中出現。 一.Path常用方法表 為了相容性(偷懶) 本表格中去除
安卓自定義View進階-縮放手勢檢測(ScaleGestureDecetor)
0. 前言 Android 縮放手勢檢測,ScaleGestureDetector 相關內容的用法和注意事項,本文依舊屬於事件處理這一體系,在大多數的情況下,縮放手勢都不是單獨存在的,需要配合其它的手勢來使用,所以推薦配合 手勢檢測(GestureDetector) 一
安卓自定義View進階-Canvas之畫布操作
Canvas之畫布操作 上一篇Canvas之繪製基本形狀中我們瞭解瞭如何使用Canvas繪製基本圖形,本次瞭解一些基本的畫布操作。 本來想把畫布操作放到後面部分的,但是發現很多圖形繪製都離不開畫布操作,於是先講解一下畫布的基本操作方法。
安卓自定義View基礎-繪製點、線、矩形、圓形等
為什麼要自定義View?因為我們在開發中,經常有各種各樣的需求,但是原生的控制元件畢竟只能滿足我們常用的需求,所以我們需要根據自身當前的需求來定製我們的View,話不多說,一步一步來吧。 1.建立類: 建立一個類,暫且將這個類命名為CustomV
安卓自定義View實現簡單折線圖
自定義View實現折線圖: 執行效果: 少說廢話,實現起來還是比較簡單的,無非就是使用canvas進行繪圖,以及座標的計算,下面直接貼程式碼: ChartView.java /** * Created by wangke on 2017/2/2