Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 自定義View-圖片文字變色,實現酷炫LoadingView或者進度條

Android 自定義View-圖片文字變色,實現酷炫LoadingView或者進度條

編輯:關於Android編程

我要自定義的控件是一個蓋世英雄,

它不僅僅是一個Loading控件,同時還支持進度條 (ProgressBar)功能 。
它會在你需要的時候出現,
它支持 lefttoprightbottom 四個方向加載(變色),最重要的是,它可以是 文字,也可以是 圖片,能夠滿足開發者一切需求。

如果你想用它來做LoadingView(圖片文字都可以,下面用圖片演示)

這裡寫圖片描述

這是你想要的效果嗎?限制太死?不會!你可以:

設置重新從底部加載,而不是從頂部折返 設置動畫時間 設置是否重復執行

如果你想做進度條ProgressBar(圖片文字都可以,下面用文字演示)

這裡寫圖片描述

怎麼樣?看完效果是不是覺得還不錯呢?

那麼這樣一個實用又酷炫的自定義控件到底有多難呢?

實現RLoadView

代碼寫到一半的時候我忽然理解了鴻洋的那個32秒。這樣的控件,簡單到爆。

原理:其實就是使用兩種不同的顏色繪制兩遍文字,通過裁剪畫布控制兩種顏色的展示

那麼重點就是一個方法啦,裁剪畫布 canvas.clipRect。知道這個方法的人簡直小菜一碟。難怪鴻洋32秒搞定。

不過有一點還是值得注意的:繪制居中文字

繪制居中文字

凡事由簡單到復雜。先實現文字類型的功能,之後再加一個圖片功能,就幾行代碼的事情。

為什麼說文字居中需要注意呢?以前學習的第一個自定義View應該就是從繪制文字開始,但是大家可能都沒有注意到,網上使用的方式繪制居中文字是有問題的。在寬高設置為 wrap_content,並且不設置 padding 的情況下,文本是不能完整繪制的。

 

博客中詳細的描述,測試,對比了文本居中繪制的各種情況,為了節省時間,簡單總結為以下幾點:

寬度測量使用:

int width=mPaint.measureText(mText);

mPaint.measureText(mText)精確度高於mBound.width()

高度測量使用:

FontMetrics fontMetrics = mPaint.getFontMetrics();
int height=Math.abs((fontMetrics.bottom - fontMetrics.top));

垂直居中方式:

FontMetricsInt fm = mPaint.getFontMetricsInt();
int startY = getHeight() / 2 - fm.descent + (fm.bottom - fm.top) / 2;

如果不明白的可以看上面那篇文章,很詳細說明。

自定義控件

其實一切沒多難,孰能生巧,現在寫這個自定控件的步驟都膩死了。還是簡單帶過

自定義View的屬性 在View的構造方法中獲得我們自定義的屬性 # 重寫onMesure # 重寫onDraw

1. 自定義View的屬性

在 /value/attrs.xml 中,




    
    
    
    
    
    
    
        
        
        
        
    
    
        
        
    

    
    
        
        
        
        
        
        
        
        
    

text:文字,文字大小,文字默認顏色,文字高亮顏色
bitmap:默認圖片,高亮圖片
direction:圖片/文字變色的方向,左到右,右到左,上到下,下到上
load_style:加載方式,圖片或者文字

2. 在View的構造方法中獲得我們自定義的屬性

    // 最大值
    private static final float MAX = 100;
    // 系統默認:文字正常時顏色
    private static final int TEXT_COLOR_NORMAL = Color.parseColor("#000000");
    // 系統默認:文字高亮顏色
    private static final int TEXT_COLOR_HIGHLIGHT = Color.parseColor("#FF0000");
    // 繪制方向
    private static final int LEFT = 1, TOP = 2, RIGHT = 3, BOTTOM = 4;
    // 文字樣式
    private static final int STYLE_TEXT = 1;
    // 圖片樣式
    private static final int STYLE_BITMAP = 2;
    // 順序繪制
    private static final int LOAD_ASC = 0;
    // 反向/降序繪制
    private static final int LOAD_DESC = 1;

    /**
     * 畫筆
     */
    private Paint mPaint;

    /**
     * 繪制的范圍
     */
    private Rect mBound;

    /**
     * 控件繪制位置起始的X,Y坐標值
     */
    private int mStartX = 0, mStartY = 0;

    /**
     * 文字大小
     */
    private int mTextSize = 16;

    /**
     * 文字正常顏色
     */
    private int mTextColorNormal = TEXT_COLOR_NORMAL;

    /**
     * 文字高亮顏色
     */
    private int mTextColorHighLight = TEXT_COLOR_HIGHLIGHT;

    /**
     * 文字
     */
    private String mText;

    /**
     * 繪制方向
     */
    private int mDirection = LEFT;

    /**
     * 控件風格
     */
    private int mLoadStyle = STYLE_TEXT;

    /**
     * bitmap正常/默認
     */
    private Bitmap mBitmapNormal;

    /**
     * bitmap高亮
     */
    private Bitmap mBitmapHighLight;

    /**
     * loading刻度
     */
    private float mProgress = 0;

    /**
     * 是否正在加載,避免開啟多個線程繪圖
     */
    private boolean mIsLoading = false;

    /**
     * 是否終止線程運行
     */
    private boolean mCanRun = true;

    /**
     * 加載方式{順序,反向}
     */
    private int mLoadMode = LOAD_ASC;

    public RLoadView(Context context) {
        this(context, null);
    }

    public RLoadView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.RLoadView);

        mText = a.getString(R.styleable.RLoadView_text);
        mTextColorNormal = a.getColor(R.styleable.RLoadView_text_color_normal,
                TEXT_COLOR_NORMAL);
        mTextColorHighLight = a.getColor(
                R.styleable.RLoadView_text_color_hightlight,
                TEXT_COLOR_HIGHLIGHT);
        mTextSize = a
                .getDimensionPixelSize(R.styleable.RLoadView_text_size, 16);
        mDirection = a.getInt(R.styleable.RLoadView_direction, LEFT);
        mLoadStyle = a.getInt(R.styleable.RLoadView_load_style, STYLE_TEXT);

        // 獲取bitmap
        mBitmapNormal = getBitmap(a, R.styleable.RLoadView_bitmap_src_normal);
        mBitmapHighLight = getBitmap(a,
                R.styleable.RLoadView_bitmap_src_hightlight);

        a.recycle();

        /**
         * 初始化畫筆
         */
        mBound = new Rect();
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Style.FILL);

        if (mLoadStyle == STYLE_TEXT) {
            mPaint.setTextSize(mTextSize);
            mPaint.getTextBounds(mText, 0, mText.length(), mBound);
        } else if (mLoadStyle == STYLE_BITMAP) {
            mBound = new Rect(0, 0, mBitmapNormal.getWidth(),
                    mBitmapNormal.getHeight());
        }

    }

代碼很簡單,一眼帶過就可以
注意一下文字和圖片不同的繪制范圍控制(mBound)
在獲取圖片的時候要考慮到 點9 圖的情況,分兩種情況獲取

3. # 重寫onMesure

重寫onMesure 方法,重新測量控件的寬高

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = onMeasureR(0, widthMeasureSpec);
        int height = onMeasureR(1, heightMeasureSpec);
        setMeasuredDimension(width, height);

    }
    /**
     * 計算控件寬高
     * 
     * @param attr屬性
     *            [0寬,1高]
     * @param oldMeasure
     * @author Ruffian
     */
    public int onMeasureR(int attr, int oldMeasure) {
        int newSize = 0;
        int mode = MeasureSpec.getMode(oldMeasure);
        int oldSize = MeasureSpec.getSize(oldMeasure);

        switch (mode) {
        case MeasureSpec.EXACTLY:
            newSize = oldSize;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.UNSPECIFIED:

            float value = 0;

            if (attr == 0) {

                if (mLoadStyle == STYLE_TEXT) {

                    value = mPaint.measureText(mText);

                } else if (mLoadStyle == STYLE_BITMAP) {

                    value = mBound.width();

                }
                // newSize
                newSize = (int) (getPaddingLeft() + value + getPaddingRight());

            } else if (attr == 1) {

                if (mLoadStyle == STYLE_TEXT) {

                    FontMetrics fontMetrics = mPaint.getFontMetrics();
                    value = Math.abs((fontMetrics.bottom - fontMetrics.top));

                } else if (mLoadStyle == STYLE_BITMAP) {

                    value = mBound.height();

                }
                // newSize
                newSize = (int) (getPaddingTop() + value + getPaddingBottom());

            }

            break;
        }

        return newSize;
    }

文字和圖片的寬高獲取方式不同,需要判斷獲取。
可以先按照類型為文字的情況一路看下來思路比較清晰,理解了文字類型的繪制,再看圖片的更容易接受。

4. 重寫onDraw

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        /**
         * X,Y控件居中繪制 

         * 對於文本居中繪制

         * 1.mPaint.measureText(mText)精確度高於mBound.width()
         * 2.文字高度測量:Math.abs((fontMetrics.bottom - fontMetrics.top))
         * 3.http://blog.csdn.net/u014702653/article/details/51985821
         */

        if (mLoadStyle == STYLE_TEXT) {

            // 控件高度/2 + 文字高度/2,繪制文字從文字左下角開始,因此"+"

            FontMetricsInt fm = mPaint.getFontMetricsInt();
            mStartY = getMeasuredHeight() / 2 - fm.descent
                    + (fm.bottom - fm.top) / 2;

            mStartX = (int) (getMeasuredWidth() / 2 - mPaint.measureText(mText) / 2);

        } else if (mLoadStyle == STYLE_BITMAP) {

            mStartX = getMeasuredWidth() / 2 - mBound.width() / 2;
            mStartY = getMeasuredHeight() / 2 - mBound.height() / 2;
        }

        onDrawR(canvas);
    }

這段代碼說明一下 mStartXmStartY
mStartX:開始繪制文字/圖片的X軸起始坐標值,這裡要求水平居中
mStartY:開始繪制文字/圖片的Y軸起始坐標值,這裡要求垂直居中
特別注意:Android中文本會中Y軸是從文字底部開始繪制,
text:mStartY=控件高度/2 + 文字高度/2,繪制文字從文字左下角開始,因此”+”

    /**
     * 繪制文字或者圖片
     * 
     * @param canvas
     * @param normalOrHightLight
     *            [0:正常模式,1:高亮模式]
     * @param start
     * @param end
     * @author Ruffian
     */
    protected void onDrawTextOrBitmap(Canvas canvas, int normalOrHightLight,
            int start, int end) {

        canvas.save(Canvas.CLIP_SAVE_FLAG);

        switch (mDirection) {
        case LEFT:
        case RIGHT:

            // X軸畫圖
            canvas.clipRect(start, 0, end, getMeasuredHeight());

            break;
        case TOP:
        case BOTTOM:

            // Y軸畫圖
            canvas.clipRect(0, start, getMeasuredWidth(), end);

            break;
        }

        if (mLoadStyle == STYLE_TEXT) {

            // 繪制文字
            if (normalOrHightLight == 0) {
                mPaint.setColor(mTextColorNormal);
            } else {
                mPaint.setColor(mTextColorHighLight);
            }
            canvas.drawText(mText, mStartX, mStartY, mPaint);

        } else if (mLoadStyle == STYLE_BITMAP) {

            // 繪制圖片
            if (normalOrHightLight == 0) {
                canvas.drawBitmap(mBitmapNormal, mStartX, mStartY, mPaint);
            } else {
                canvas.drawBitmap(mBitmapHighLight, mStartX, mStartY, mPaint);
            }

        }

        canvas.restore();

    }

繪制文字或者圖片的方法,保存當前畫布之後進行裁剪畫布在繪制文字。
比如說現在需要繪制“一串文字”的後半部分,就把前半部分裁減掉,然後繪制,當恢復畫布之後效果就是前面一半是空白,後面一半是文字

    /**
     * 控件繪制
     * 
     * @param canvas
     * @author Ruffian
     */
    public void onDrawR(Canvas canvas) {

        /**
         * 主要思想:繪制兩遍文字/圖像,通過裁剪畫布拼接兩部分文字/圖像,實現進度繪制的效果
         */

        // 需要變色的寬高總值(長度)
        int drawTotalWidth = 0;
        int drawTotalHeight = 0;

        // X,Y變色的進度實時值
        int spliteXPro = 0;
        int spliteYPro = 0;

        // X,Y變色的最大值(坐標)
        int spliteXMax = 0;
        int spliteYMax = 0;

        // 開始變色的X,Y起始坐標值
        int spliteYStart = 0;
        int spliteXStart = 0;

        FontMetricsInt fm = mPaint.getFontMetricsInt();

        if (mLoadStyle == STYLE_TEXT) {

            drawTotalWidth = (int) mPaint.measureText(mText);
            drawTotalHeight = Math.abs(fm.ascent);

            spliteYStart = (fm.descent - fm.top) - Math.abs(fm.ascent)
                    + getPaddingTop();
            // 開始裁剪的Y坐標值:(http://img.blog.csdn.net/20160721172427552)圖中descent位置+paddingTop

            spliteYMax = Math.abs(fm.top) + (fm.descent);
            // Y變色(裁剪)的進度最大值(坐標):(http://img.blog.csdn.net/20160721172427552)看圖

        } else if (mLoadStyle == STYLE_BITMAP) {

            drawTotalWidth = mBound.width();
            drawTotalHeight = mBound.height();

            spliteYStart = mStartY;// 開始裁剪的Y坐標值:圖片開始繪制的地方
            spliteYMax = mStartY + drawTotalHeight;
            // Y變色(裁剪)的進度最大值(坐標):圖片開始繪制的地方+需要變色(裁剪)的高總值(長度)
        }

        spliteXPro = (int) ((mProgress / MAX) * drawTotalWidth);
        spliteYPro = (int) ((mProgress / MAX) * drawTotalHeight);

        spliteXStart = mStartX;// 開始裁剪的X坐標值:文字開始繪畫的地方
        spliteXMax = mStartX + drawTotalWidth;
        // X變色(裁剪)的進度最大值(坐標):X變色(裁剪)起始位置+需要變色(裁剪)的寬總值(長度)

        switch (mDirection) {
        case TOP:

            // 從上到下,分界線上邊是高亮顏色,下邊是原始默認顏色
            onDrawTextOrBitmap(canvas, 1, spliteYStart, spliteYStart
                    + spliteYPro);
            onDrawTextOrBitmap(canvas, 0, spliteYStart + spliteYPro, spliteYMax);

            break;
        case BOTTOM:

            // 從下到上,分界線下邊是默認顏色 ,上邊是高亮顏色

            onDrawTextOrBitmap(canvas, 0, spliteYStart, spliteYMax - spliteYPro);
            onDrawTextOrBitmap(canvas, 1, spliteYMax - spliteYPro, spliteYMax);

            break;
        case LEFT:

            // 從左到右,分界線左邊是高亮顏色,右邊是原始默認顏色
            onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart
                    + spliteXPro);
            onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);

            break;
        case RIGHT:

            // 從右到左,分界線左邊是默認顏色 ,右邊是高亮顏色
            onDrawTextOrBitmap(canvas, 0, spliteXStart, spliteXMax - spliteXPro);
            onDrawTextOrBitmap(canvas, 1, spliteXMax - spliteXPro, spliteXMax);

            break;
        }

    }

這個方法是控制裁剪起始和終止坐標值的關鍵方法。

 

可以結合這篇博客理解

說一下思路,思路很關鍵啊,拿筆記好啦。哈哈

文章開頭就說了,文字/圖片變色原理是使用兩種不同的顏色(或者兩張圖片)繪制兩遍文字/圖片

先看幾個變量

        // 需要變色的寬高總值(長度)
        int drawTotalWidth = 0;
        int drawTotalHeight = 0;

        // X,Y變色的進度實時值
        int spliteXPro = 0;
        int spliteYPro = 0;

        // X,Y變色的最大值(坐標)
        int spliteXMax = 0;
        int spliteYMax = 0;

        // 開始變色的X,Y起始坐標值
        int spliteYStart = 0;
        int spliteXStart = 0;
需要變色的寬度總值(drawTotalWidth ):這裡就是文字或者圖片自身的寬度 X變色的進度實時值(spliteXPro ):表示變色進度的實時值。
比如變色到50%的時候 spliteXPro = (int) (( 50 / MAX) * drawTotalWidth); X開始變色的起始坐標值(spliteXStart ):記住這是一個坐標值,從哪裡開始變色。 X變色的最大值(spliteXMax ):表示最多能變色到什麼位置 ,記住這是一個坐標值,所以需要加上起始值。
spliteXMax = spliteXStart + drawTotalWidth;

好好理解這幾個概念,接下來就是繪制控件了。

繪制的原理都是一樣的,只要理解了,從哪個方向開始變色都是一樣的,無非是起始值和結束值的計算。計算之前一定要搞清楚上面的幾個概念,不然,呵呵。說句不炫耀的話,寫代碼的時候,我差點把自己弄暈。

#以從左到右變色為例講解變色原理

// 從左到右,分界線左邊是高亮顏色,右邊是原始默認顏色
onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart + spliteXPro);
onDrawTextOrBitmap(canvas, 0, spliteXStart + spliteXPro, spliteXMax);

onDrawTextOrBitmap(canvas, 1, spliteXStart, spliteXStart + spliteXPro);
第一個參數表示畫布,
第二個參數表示文本類型(1:高亮,0:默認),
第三個參數變色開始的X值,
第四個參數變色結束X值

先繪制一遍高亮顏色的文字,再繪制一遍默認顏色的。

開始變色 ↓- - - - - - - - - - 變色最大值

高亮顏色從 開始變色的地方,繪制到,某個值停止
默認顏色從 高亮顏色停止的位置,繪制到,變色最大值位置
從progress=0,到progress=100,隨著progress增加 高亮顏色慢慢向右遞增,默認顏色慢慢遞減,形成變色效果
其實就是畫布裁剪出一部分來繪制 高亮顏色,然後再裁剪出一部分顏色來繪制 默認顏色。
如果進度停在50%,就會看到一半高亮,一半默認

OK,看看這個自定義類裡面都有哪些方法

RLoadView:構造方法,獲取基本屬性 #onMeasure:重寫onMeasure方法計算控件寬高 onMeasureR():自定義方法,計算控件寬高 #onDraw:重寫onDraw方法,繪制控件 onDrawR():自定義方法,繪制控件之前計算,處理 onDrawTextOrBitmap:自定義方法,繪制文本/圖片邏輯 start:自定義方法,開始執行文本/圖片變色 stop:自定義方法,結束文本/圖片變色 getProgress:獲取進度值 setProgress:設置進度值 getBitmap:獲取圖片屬性,區分 點9圖 #onSaveInstanceState:重寫方法,保存信息(進度值等) #onRestoreInstanceState:重寫方法,重新設置信息(進度值等)

其中對外提供的方法:startstopgetProgresssetProgress

start

public void start(final long duration, final boolean isRepeat, final boolean isReverse)

start 開始執行loading,使用在 LoadingView 情形中
第一參數:執行時間,設置變色執行的時間
第二個參數:是否循環重新變色,true循環變色,並且重頭開始變色
第三個參數:是否反向褪色,true則表示變色完成之後反向褪色
可以自由組合第二第三個參數,實現不同的效果。

stop

public void stop()

停止變色。配合 start 使用,由於start開啟子線程實現變色,通過stop停止線程執行,停止變色。

setProgress

public void setProgress(float progress)

設置進度值。使用在ProgressBar 情形中。
在下載文件情形下,實時設置progress,方法會重繪控件,更新進度條。

getProgress

public float getProgress()

獲取進度值。更適合使用在 ProgressBar 情形中。
在 LoadingView 情形中獲取進度值沒有意義,會在0-100之間不斷變化。

在xml布局文件中使用控件

    

Activity中代碼調用

//獲取自定義控件
RLoadView mLoadView = (RLoadView) findViewById(R.id.id_loadView);


/**
 * 使用情形1:LoadingView
 */
//變色時間,變色模式,開始LoadingView
mLoadView.start(1500, true, false);
//停止Loading
mLoadView.stop();



/**
 * 使用情形2:ProgressBar
 */
//設置進度值,模擬下載設置下載進度值
mLoadView.setProgress(mProgress);
//獲取進度值,模擬下載獲取已下載進度值
mLoadView.getProgress();

使用起來從未如此簡單,方便。兩行代碼搞定一切的既視感!關鍵是效果酷炫!

還在等什麼?趕快下載體驗吧

Github:https://github.com/RuffianZhong/RLoadView


這是一條華麗的分割線


順便說一下另外一個自定義控件,簡單實用的ViewPageIndicator,RVPIndicator

1.什麼是 RVPIndicator

簡單實用的ViewPageIndicator,支持item自身滾動

高仿MIUI但更勝於MIUI,提供多種指示器類型{下滑線,三角形,全背景}

這裡寫圖片描述

覺得這不滿足你的需求?沒問題,RVPIndicator 還支持使用圖片作為指示器。一張圖實現你的願望

這裡寫圖片描述

不會作圖?你想自定義?OK,添加兩三行代碼就可以增加新的指示器樣式

2. RVPIndicator 使用


    

    

2.1 自定義屬性解釋

        rvp:indicator_color="#f29b76"               //指示器顏色
        rvp:indicator_src="@drawable/heart_love"    //指示器圖片{指示器類型為bitmap時需要}
        rvp:indicator_style="triangle"              //指示器類型
                                                   //{bitmap:圖片;line:下劃線;square:方形全背景;triangle:三角形}

        rvp:item_count="4"                          //item展示個數
        rvp:text_color_hightlight="#FF0000"         //item文字高亮顏色
        rvp:text_color_normal="#fb9090"             //item文字正常顏色

2.2 代碼調用

        // 設置Tab上的標題
        mIndicator.setTabItemTitles(mDatas);
        // 設置關聯的ViewPager
        mIndicator.setViewPager(mViewPager, 0);

Github:https://github.com/RuffianZhong/RVPIndicator

敢不敢留個言,點個贊,證明真的有人在看,哈哈

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved