1. 程式人生 > >Android進階——自定義View之擴充套件系統控制元件的另一種思路實現漸變文字動畫的TextView

Android進階——自定義View之擴充套件系統控制元件的另一種思路實現漸變文字動畫的TextView

引言

前面幾篇文章

繼承或組合系統現有控制元件實現新控制元件,擴充套件新功能都是在對應的構造方法中去擴充套件的,但千萬不要把思路侷限於只能在構造方法中去擴充套件,這篇就簡單地分享另一種思路,通過重寫對應的週期方法實現擴充套件。

一、View中幾種重要的方法

  • onFinishInflate:從XML載入了控制元件之後自動回撥的

  • onSizeChanged:元件大小改變時回撥,當第一次完成控制元件的測量也會觸發回撥

  • onMeasure:通過回撥該方法來進行測量,具體參見我的另一篇文章

  • onLayout:通過回撥該方法來確定顯示的位置,即佈局

  • onTouchEvent:當控制元件監聽到觸控事件的時候回撥

  • onDraw:控制元件最終呈現的效果都是由onDraw決定的

這裡寫圖片描述
雖然我們在自定義View的時候不一定什麼情況下都必須重寫這所有的方法,我們克根據各自的業務來重寫對應的方法,同時這也是Android控制元件架構強大靈活性的體現。

二、dp、sp和px的互相轉換

  • dp(dip): device independent pixels(裝置獨立畫素).
    不同裝置有不同的顯示效果,這個和裝置硬體有關,一般我們為了支援WVGA、HVGA和QVGA 推薦使用這個,不依賴畫素。如果設定表示長度、高度等屬性時可以使用dp。dp是與密度無關,sp除了與密度無關外,還與scale無關。如果螢幕密度為160,這時dp和sp和px是一
    樣的。1dp=1sp=1px,但如果使用px作單位,如果螢幕大小不變(假設還是3.2寸),而螢幕密度變成了320。那麼原來TextView的寬度設成160px,在密度為320的3.2寸螢幕裡看要比在密度為160的3.2寸螢幕上看短了一半。但如果設定成160dp或160sp的話。系統會自動將width屬性值設定成320px的。也就是160 * 320 / 160。其中320 /160可稱為密度比例因子。也就是說,如果使用dp和sp,系統會根據螢幕密度的變化自動進行轉換。
  • px: pixels(畫素)。不同裝置顯示效果相同,一般我們HVGA代表320x480畫素,這個用的比較多。
  • pt: point,是一個標準的長度單位,1pt=1/72英寸,用於印刷業,非常簡單易用;
  • sp: scaled pixels(放大畫素).
    主要用於字型顯示best for textsize。

以下附一個轉換工具類

public class ScreenUtil {
    /**
     * 將px值轉換為dip或dp值,保證尺寸大小不變
     *
     * @param pxValue
     * @param scale   (DisplayMetrics類中屬性density)
     * @return
*/
public static int px2dip(Context context, float pxValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / scale + 0.5f); } /** * 將dip或dp值轉換為px值,保證尺寸大小不變 * * @param dipValue * @param scale (DisplayMetrics類中屬性density) * @return */ public static int dip2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } /** * 將px值轉換為sp值,保證文字大小不變 * * @param pxValue * @param fontScale (DisplayMetrics類中屬性scaledDensity) * @return */ public static int px2sp(Context context, float pxValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValue / fontScale + 0.5f); } /** * 將sp值轉換為px值,保證文字大小不變 * * @param spValue * @param fontScale (DisplayMetrics類中屬性scaledDensity) * @return */ public static int sp2px(Context context, float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } }

二、重寫系統控制元件的成員方法擴充套件新的功能

重寫系統控制元件的成員方法擴充套件新的功能這也是一種自定義View的重要思想,很多時候原生的控制元件的功能對於我們來說只是欠缺了一點點,此時我們就可以把大部分的工作交給原生控制元件,然後再在原生的基礎上進行再開發,而不一定要事必躬親,完全繼承View去繪製。以下直接通過兩個簡單的例子來說明。

1、實現自帶邊框和背景顏色圖層的TextView

這裡寫圖片描述
如上圖所示,外邊框為紅色內部背景為綠色的簡單TextView,可能利用Span家族也可以實現,但那不是重點,舉這個例子僅僅是分享一種思想。首先,瞭解View的繪製流程的話,應該都知道onDraw是繪製UI效果的,那麼TextView裡的onDraw自然充當的是繪製要顯示的文字的職責,這裡順道補充下常常在重寫系統的一些方法的時候,預設地實現會去呼叫父類對應的方法,並不是所有的父類方法都需要手動去呼叫的,也不是所有的父類方法都可以不呼叫的,比如說這個小例子中的onDraw

   @Override
    protected void onDraw(Canvas canvas) {

        drawOutsideRec(canvas);
        drawInsideRec(canvas);
        canvas.save();
        Log.e("onDraw2","10px:"+ScreenUtil.px2dip(getContext(),30));
       // canvas.translate(ScreenUtil.px2dip(getContext(),10),0);
        canvas.translate(10,0);
        //在回撥父類方法之前,實現對應的邏輯,此例指的是在文字繪製之前
        super.onDraw(canvas);
        //在回撥父類方法之後,實現對應的邏輯,此例指的是在文字繪製之後
        canvas.restore();
    }

總之,繼承系統控制元件時候父類成員方法的呼叫與否,呈現的UI效果也不盡相同。接下來就實現這個帶邊框和帶背景的TextView

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/5/5 10:20
 * Summary:
 */
public class ColorfulTextView extends TextView {
    private Paint paintInside,paintOutside;
    public ColorfulTextView(Context context) {
        super(context);
        init();
    }

    public ColorfulTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ColorfulTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        paintInside=new Paint();
        paintInside.setColor(getResources().getColor(android.R.color.holo_green_dark));
        paintInside.setStyle(Paint.Style.FILL);
        paintInside.setAntiAlias(true);
        paintInside.setDither(true);

        paintOutside=new Paint();
        paintOutside.setColor(getResources().getColor(android.R.color.holo_red_light));
        paintOutside.setStyle(Paint.Style.STROKE);
        paintOutside.setAntiAlias(true);
        paintOutside.setDither(true);
    }

    private void drawInsideRec(Canvas canvas){
        //canvas.drawRect(ScreenUtil.px2dip(getContext(),10),ScreenUtil.px2dip(getContext(),10),getMeasuredWidth()+ScreenUtil.px2dip(getContext(),10),getMeasuredHeight(),paintInside);
        canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,paintInside);
    }

    private void drawOutsideRec(Canvas canvas){
       // canvas.drawRect(0,0,ScreenUtil.px2dip(getContext(),getMeasuredWidth()),ScreenUtil.px2dip(getContext(),getMeasuredHeight()+20),paintOutside);
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paintOutside);
        Log.e("onDraw2",ScreenUtil.px2dip(getContext(),getMeasuredWidth())+"measureWidth"+getMeasuredWidth()+"measureHeight"+getMeasuredHeight()+20);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        drawOutsideRec(canvas);
        drawInsideRec(canvas);
        canvas.save();
        Log.e("onDraw2","10px:"+ScreenUtil.px2dip(getContext(),30));
       // canvas.translate(ScreenUtil.px2dip(getContext(),10),0);
        canvas.translate(10,0);
        super.onDraw(canvas);
        canvas.restore();
    }
}

2、實現文字閃爍動畫的TextView

這裡寫圖片描述
原理很簡單,主要就是重寫onSizeChanged方法中利用在getPaint()方法並拿到當前的Paint物件,再給Paint物件設定相應的Shader產生漸變效果,接著利用矩陣Matrix實現平移的動畫效果,最後在onDraw裡不斷手動重新整理即可。

package crazymo.training.colorfultextview.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/5/5 10:20
 * Summary:
 */
public class ColorfulTextView extends TextView {
    private Paint shineyPaint;
    private LinearGradient linearGradient;
    private Matrix shinerMatrix;
    private int width,translate;
    public ColorfulTextView(Context context) {
        super(context);
        Log.e("ViewRun","構造方法");
    }

    public ColorfulTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.e("ViewRun","構造方法");
    }

    public ColorfulTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.e("ViewRun","構造方法");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.e("ViewRun","onLayout"+changed+"left:"+left+"top:"+ top+"right:"+ right+"bottom"+ bottom);
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.e("ViewRun","onMeasure");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onFinishInflate() {
        Log.e("ViewRun","onFinishInflate");
        super.onFinishInflate();
    }



    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.e("ViewRun","onSizeChanged"+"w"+w+"h"+h+"oldw"+oldw+"oldh"+oldh);
        super.onSizeChanged(w, h, oldw, oldh);
        if(width==0){//在View第一次呈現前初始化漸變物件,並拿到當前的Paint
            width=getMeasuredWidth();
            if(width>0){
                shineyPaint=getPaint();//獲取當前的Paint物件
                linearGradient=new LinearGradient(0,0,width,0,new int[]{Color.GREEN,0xffffffff,Color.GREEN},null, Shader.TileMode.CLAMP);//構造和TextView一樣寬度的漸變物件
                shineyPaint.setShader(linearGradient);
                shinerMatrix=new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.e("ViewRun","onDraw");
        super.onDraw(canvas);
        if(shinerMatrix!=null){
            translate+=width/5;
            if(translate>width*2){
                translate=-width;
            }
            shinerMatrix.setTranslate(translate,0);
            linearGradient.setLocalMatrix(shinerMatrix);
            postInvalidateDelayed(100);
        }
    }
}

小結

以上例子雖然很簡單,主要是分享一種思路,在自定義View的時候不要只拘泥於重寫構造方法,甚至是拘泥於onDraw方法,理論上任何成員方法都可以是被我們再開發的,前提是你得懂得基本的邏輯,重寫View的成員方法擴充套件功能這就是我說的另一種思路。

相關推薦

no