1. 程式人生 > >Android自定義View-自定義元件

Android自定義View-自定義元件

Android自定義元件

  • android自定義元件一般有三種實現方式:
  • 一、組合控制元件:組合控制元件,顧名思義就是將一些小的控制元件組合起來形成一個新的控制元件,這些小的控制元件多是系統自帶的控制元件。
  • 二、自繪控制元件: 何為自繪控制元件,就是完全用Paint和canvas畫出來的,就是在onDraw()方法裡面繪畫,在onMeasure()方法裡面進行測量,如果是容器在onLayout()方法中定位每個子元件。
  • 三、繼承控制元件: 就是繼承已有的控制元件,建立新控制元件,保留繼承的父控制元件的特性,並且還可以引入新特性。

自繪控制元件也分兩種,自定義元件和自定義容器,自定義元件是繼承View類,自定義容器時繼承ViewGrounp;今天主要分析下自定義元件;還是舉個例子來的實際些,假如我們要畫一個最簡單的TextView,首先想到的就是canvas.drawText()方法,怎麼畫了?還是得一步一步來:


(1) )寫一個MyTextView類繼承View,重寫三個構造方法,當然還有onDraw()和onMeasure()方法,如下程式碼:

public class MyTextView extends View{
/**
 * 文字顏色
 */
private int textColor;
/**
 * 字型大小
 */
private float textSize;
/**
 * 文字
 */
private String text;

/**
 * 繪製時控制
 */
private Paint mPaint;
private Rect mBoud;

public MyTextView(Context context) {
    this
(context, null); } public MyTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); } @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }

(2) 接下來就要想既然是TextView,那肯定是有text,color,textSize等屬性。在values目錄下的attrs.xml中定義好這些屬性,以便在xml中引入MyTextView能夠直接操作這些值,然後在有三個引數的構造方法裡面將這些屬性與控制元件關聯,如下:

attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyTextView">
    <attr name="textColor" format="color|reference"/>
    <attr name="textSize" format="dimension|reference"/>
    <attr name="text" format="string|reference"/>
</declare-styleable>
</resources>
public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, 0, 0);
    textColor = typedArray.getColor(R.styleable.MyTextView_textColor, 0);
    textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 0);
    text = typedArray.getString(R.styleable.MyTextView_text);
    typedArray.recycle();
}

這裡解釋下三個構造方法,一般一個引數的呼叫兩個引數的,兩個引數呼叫三個引數的,屬性與控制元件的關聯寫在三個引數的構造方法裡面即可。

(3) 已經定義好了屬性,那接下來在構造方法裡用Paint畫筆設定好這些屬性,然後用onDraw方法裡面的canvas畫一個textview,如下:

public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyTextView, 0, 0);
        textColor = typedArray.getColor(R.styleable.MyTextView_textColor, 0);
        textSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 0);
        text = typedArray.getString(R.styleable.MyTextView_text);
        typedArray.recycle();

        mPaint = new Paint();
        // 設定畫筆為抗鋸齒
        mPaint.setAntiAlias(true);
        mPaint.setColor(textColor);
        mPaint.setTextSize(textSize);
        //獲取繪製文字的寬和高
        mBoud = new Rect();
        mPaint.getTextBounds(text, 0, text.length(), mBoud);
    }

@Override
protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //canvas.drawText(text, x, y, paint); //第一個引數是文字,第四個引數是畫筆,第二個引數x預設是字串的左邊在螢幕的位置, 第三個引數y是這個字元文字baseline基線在螢幕上的位置,不是這個字元的中心在螢幕的位置
        Log.v("MyTextView", "getWidth:"+getWidth()/2);
        Log.v("MyTextView", "mBoud.width:"+mBoud.width()/2);
        Log.v("MyTextView", "getHeight"+getHeight()/2);
        Log.v("MyTextView", "mBoud.height:"+mBoud.height()/2);
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        int baseline = (getMeasuredHeight() / 2) - ((fontMetrics.bottom + fontMetrics.top) / 2);
        Log.v("MyTextView", "baseline:"+baseline);
        Log.v("MyTextView", "getMeasuredHeight:"+getMeasuredHeight());
        Log.v("MyTextView", "fontMetrics.top:"+fontMetrics.top);
        Log.v("MyTextView", "fontMetrics.bottom:"+fontMetrics.bottom);
        Log.v("MyTextView", "fontMetrics.ascent:"+fontMetrics.ascent);
        Log.v("MyTextView", "fontMetrics.descent:"+fontMetrics.descent);
        canvas.drawText(text, getWidth()/2 - mBoud.width()/2, baseline, mPaint);
    }

**onDraw方法裡面的baseline是基線,在Android中,文字的繪製都是從Baseline處開始的,所以第三個引數y不是這個文字中心在螢幕的位置,而是文字基線在螢幕的位置,感興趣的可移至BaseLine

到現在其實我們的畫的MyTextView已經可以用了,額?不是還有一個onMeasure方法什麼都沒有寫嗎,不急我們先在xml中引入試試看效果**

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:id="@+id/activity_mtext_view"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context="com.yilei.ownerdraw.activity.MTextViewActivity">

    <com.yilei.ownerdraw.view.MTextView
        android:layout_width="wrap_parent"
        android:layout_height="wrap_content"
        app:texts="Android自定義TextView"
        app:text_colors="@color/chart_green"
        app:text_sizes="14sp"/>
</RelativeLayout>

這裡寫圖片描述

可以看到,我們設定textview的寬高都是wrap_content,但是執行之後都佔滿了螢幕,之前重寫的onMeasure方法就就派上用場了

  • 1.首先要了解測量元件的寬高時,是由元件的父容器呼叫的,首先容器他會遍歷容器內所有的子元件,一個一個的測量寬高,這裡的我們自定義textView的父容器就是最外層RelativeLayout
  • 2.然後要了解MeasureSpec類,這個類為我們提供了兩個方法,getMode(int measureSpec)和getSize(int measureSpec)可以獲得寬和高的尺寸和模式,這個類還為我們提供了三個常量MeasureSpec.EXACTLY、MeasureSpec.AT_MOST、MeasureSpec.UNSPECIFIED;
  • MeasureSpec.EXACTLY:已經為TextView指定確定尺寸或者為math_parent時
  • MeasureSpec.AT_MOST:當TextView為wrap_content時
  • MeasureSpec.UNSPECIFIED:TextView未定義寬高

上面我們設定了高為wrap_content,但是我們沒有測量textview的高,如果指定了為wrap_content,那麼此時的高應該是TextView本身的高,所以需要測量然後返回給父容器,下面我們重寫onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = measureWidth(widthMeasureSpec);
    int height = measureHeight(heightMeasureSpec);
    setMeasuredDimension(width, height);
}

private int measureWidth(int widthMeasureSpec){
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    int size = MeasureSpec.getSize(widthMeasureSpec);
    int width = 0;

    if(mode == MeasureSpec.EXACTLY){
        //如果是math_parent或確定尺寸
        width = size;
    }else if(mode == MeasureSpec.AT_MOST){
        //如果是wrap_parent
        width = getPaddingLeft() + mBound.width() + getPaddingRight();
    }
    return width;
}

private int measureHeight(int heightMeasureSpec){
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    int size = MeasureSpec.getSize(heightMeasureSpec);

    int height = 0;
    if(mode == MeasureSpec.EXACTLY){
        //如果是math_parent或確定尺寸
        height = size;
    }else if(mode == MeasureSpec.AT_MOST){
        //如果是wrap_parent
        height = getPaddingTop() + mBound.height() + getPaddingBottom();
    }
    return height;
}

再來試試效果
這裡寫圖片描述

現在TextView測量完成了,已經可以設定寬高了,寬高設定為wrap_content則是TextView本身的寬高,因為我們還考慮了Padding,所以也可以設定TextView的Padding,如果測量時沒考慮Padding在xml中設定是不起作用的,有興趣的的可以試一試

當然,這只是一個很簡單的自定義TextView元件,僅為簡單的理解自繪View,另外還有自定義容器,自定義容器時就要考慮onLayout方法了,因為要定位每個子元件在容器中位置。想要熟練使用自定義View,必須熟練使用Paint和canvas,這是自定義View的基本。下一篇介紹自定義容器

自定義元件和自定義容器的一些小demo,有興趣的可以移至OwnerDraw