1. 程式人生 > >安卓自定義View文章資料滾動顯示數值

安卓自定義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