安卓自定義View文章數據滾動顯示數值

本文已經在微信公衆號【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檢測並且,解決內存泄漏問題,並講述一下,看過本文的直接點傳送門。

2.v1.2更新內容

2016年11月11號,RandomTextView第一次更新爲v1.1版本吧。
(解決了這樣一個場景,一個抽獎的頁面想滾動30秒,可能maxline加到100行的數字滾動,對此我要對性能進行優化避免過度繪製,在本文最後做出解釋)

Github代碼已經更新爲v1.1

1.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.考入

RandomTextView.java

只有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

—————————————————————————————

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章