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的成員方法擴充套件功能這就是我說的另一種思路。

相關推薦

Android——定義View擴充套件系統控制元件思路實現漸變文字動畫TextView

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

Android——定義View組合系統控制元件實現水珠形狀的ItemView

引言 相信大家在專案開發的過程中一定會有不少需要在上方顯示一張圖片,而在其下方顯示提示標題的效果,作為一個介面的功能按鈕或者單純作為一個列表的item項,尤其是當這個item還需要顯示一些動畫效果時候,此時更應該當成一個整體,否則動畫效果就會需要額外的調整,否

Android——定義View繼承TextView巧用DrawableLeft實現自己的CheckableTextView

引言 Android自帶的許多控制元件已經十分強大,甚至很多功能都已經有現成的控制元件去使用了,不過介面效果是肯定會打折扣的,幸好android控制元件自身的擴充套件性十分優秀,很多時候我們只需要簡單繼承下現有控制元件擴充套件些許功能就能得到一個全新的控制元件

Android——定義ViewView的繪製流程及實現onMeasure完全攻略

引言 Android實際專案開發中,自定義View不可或缺,而作為自定義View的一種重要實現方式——繼承View重繪尤其重要,前面很多文章基本總結了繼承View的基本流程:自定義屬性和繼承View重寫onDraw方法——>實現構造方法並完成相關初始化操

android學習5#--定義View座標系統

近期在學習android過程中,看到有人在伯樂在線上分享如何開發自定義View的一系列文章。我覺得對於初學者,很有必要掌握它,因為今後很多時候系統自帶的元件不足以滿足我們的功能需求,那麼我們就要根據需求來定義一個能滿足我們需求的View元件。 但是我並不會按照

定義View驗證碼控制元件

先上圖鎮貼 最近專案中有需要驗證碼,不過Android端的驗證碼一般都是手機簡訊,點選獲取驗證碼,將請求向後臺以及向簡訊驗證碼平臺傳送。簡訊平臺將驗證碼傳送到前端及後臺,讓前端輸入驗證碼和後臺進行比較。驗證碼的存在就是提高安全性,即使拿到賬戶和密碼,也無法登陸。因為驗證碼是隨機性的,唯

HenCoder Android 開發: 定義 View 1-1 繪製基礎

自定義繪製概述 二話不說,我反手就是一個視訊:(視訊掛了,先直接點到優酷去看吧:優酷連結) 首先總結一下視訊中的關鍵點: 自定義繪製的方式是重寫繪製方法,其中最常用的是 onDraw() 繪製的關鍵是 Canvas 的使用 Canvas 的繪製類方法: drawXX

Android開發——定義View的使用及其原理探索

  在Android開發中,系統提供給我們的UI控制元件是有限的,當我們需要使用一些特殊的控制元件的時候,只靠系統提供的控制元件,可能無法達到我們想要的效果,這時,就需要我們自定義一些控制元件,來完成我們想要的效果了。下面,我就來講講自定義控制元件的那些事。   首先,我來講講Android的控制元件架構。

Android繪圖:定義View——矩形進度條、圓環進度條、填充型進度條、時鐘

主函式 這幾種進度條的主函式都是類似的,所以下面我只給出了一個填充型進度條的主函式,其他幾個主函式只是在這基礎上改動一下按鈕id(即與各自佈局裡面的id相同即可),還有改動一下相對應的類即可。 public class MainActivity

Android 定義 ViewGroup 定義佈局

前言 在我們的實際應用中, 經常需要用到自定義控制元件,比如自定義圓形頭像,自定義計步器等等。但有時我們不僅需要自定義控制元件,舉個例子,FloatingActionButton 大家都很常用,所以大家也很經常會有一種需求,點選某個 FloatingActionButton 彈出更多 FloatingActi

Android定義View圓盤滑動控制元件(已適配多種解析度)

好久沒寫部落格了,最近在寫一個專案時需要一個可以調節檔位的圓盤, 首先實現這個圓盤自定義View,首先在構造方法中定義畫筆,重寫onDraw(Canvas canvas)方法,進行繪製,首先繪製一個大圓,然後我這個大圓周圍的錶盤顯示一共有9個檔位,為了美觀,每個

Android系列:八、定義View音頻抖動動效

final rim 而是 開始 next draw img 點擊 syn 自定義動畫效果——音頻抖動效果 1.繪制一個矩形: 想要繪制一個矩形,繼承View,並重寫onDraw方法即可。復雜一點還可以重寫onMeasure方法和onLayout方法進行大小測量和位置測量。但

Android:九、定義View手寫Loading動效

應該 ima outer list 初始 ffffff tar implement 旋轉角度 這是一個很簡單的動畫效果,使用屬性動畫即可實現,希望對讀者學習動畫能達到拋磚引玉的效果 一.自定義動畫效果——Loading效果 如上是我們需要做的一個Loading動畫。Loa

Android定義View分貝儀

一、說明        最近在整理自定義View方面的知識,偶爾看到meizu工具箱的分貝儀效果,感覺它的實效效果還挺好的,遂想自己模擬實現練練手,廢話不多說,直接開擼。 二、效果圖 首先看一下效果圖: 看效果還挺炫酷

Android定義ViewCanvas

https://www.jianshu.com/p/fb18c28d6627 用繼承View的方式來自定義View,我們就需要重寫onDraw方法,也就是得咱自己來畫圖了。畫圖就得用到畫筆和畫布,也就是Paint和Canvas。我們來了解下Canvas。 Canvas Canvas我們可

Android 定義ViewCanvas詳解

自定義View的相關文章: Android 實現一個簡單的自定義View Android 自定義View步驟 Android Paint詳解 Android 自定義View之Canvas相關方法說明 Android 自定義View例項之 “京東跑”

Android輸入框自動提示--定義佈局

發現Android的有兩種方法AutoCompleteTextView和MultiAutoCompleteTextView提示出來的提示框只是純文字而且是單條資料,要是想實現加一個圖片或者是每一條資料展示兩個資料呢,這就需要重寫介面卡設定佈局了 重寫介面卡: packag

Android : 定義View流式佈局

寫了一個很簡單的佈局 這是周圍圓框的drawable <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android">

android定義View仿通訊錄側邊欄滑動,實現A-Z字母檢索

我們的手機通訊錄一般都有這樣的效果,如下圖: OK,這種效果大家都見得多了,基本上所有的android手機通訊錄都有這樣的效果。那我們今天就來看看這個效果該怎麼實現。 一.概述 1.頁面功能分析 整體上來說,左邊是一個ListView,右邊是一個自定義View,但

Android定義ViewgetTextBounds()

在Android自定義View的過程中一定會用到Paint,而paint屬性中有一個方法getTextBounds(String text,int start,int end,Rext bounds),它的中文解釋是:返回一個包含中文的矩形邊界,位置為(start,end) 英文解釋:Retur