1. 程式人生 > >自定義View知識總結

自定義View知識總結

參考鴻洋大神的文章

自定義View要做的幾件事:

首先參照官方文件

1.自定義View的屬性,在xml佈局檔案中使用該屬性

2.在構造方法裡面獲得我們自定義的屬性(兩個引數的)

3.[optional]重寫onMeasure方法(原因後面講)

4.重寫onDraw()

一步一步來:

1.自定義屬性:

在values資料夾下面建立attrs.xml檔案,像這樣:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomTitleView">
        <attr name="mtitleText" format="string"></attr>
        <attr name="mtitleTextColor" format="color"></attr>
        <attr name="mtitleTextSize" format="dimension"></attr>
    </declare-styleable>
</resources>
這樣就聲明瞭三個自定義屬性,可以在xml佈局檔案裡使用了。這裡說一下,declared-styleable的name最好設定為自定義View的name,為什麼,接下來給這個自定義的View的建構函式裡面找TypedArray時候好找啊。

ps:使用自定義屬性的時候,注意xmlns後面新增的是包名,當然AS中改為auto了。

現在xml佈局檔案可以這樣寫了。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:Custom="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <com.example.harris.basement_1115.MyCustonView
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        Custom:mtitleTextSize="40sp"
        Custom:mtitleText = "Hello"
        Custom:mtitleTextColor="#ff03ff"
        />
</RelativeLayout>
自定義的屬性可以以XXX開頭。接下來考慮在View的java程式碼裡讀取自定義的屬性。

開始第二部,在構造方法裡面獲得我們自定義的屬性;

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

一般先用alt+insert快捷鍵插入構造方法,有三個要重寫:

public class SomeView extends View {
    public SomeView(Context context) {
        super(context);
    }

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

    public SomeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
然後我們重寫三個引數的構造方法,第一個第二個分別去呼叫三個引數的構造方法。

三個引數的建構函式長這樣:

 public MyCustonView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context,attrs,defStyleAttr);
        TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomTitleView,0, 0);
        try {
            mTitleTextSize = a.getDimensionPixelSize(R.styleable.CustomTitleView_mtitleTextSize,
                    (int) TypedValue.applyDimension
                            (TypedValue.COMPLEX_UNIT_SP, 16, getResources().getDisplayMetrics()));
            mTitleText = a.getString(R.styleable.CustomTitleView_mtitleText);
            mTitleTextColor = a.getColor(R.styleable.CustomTitleView_mtitleTextColor, Color.GREEN);
        }
        finally {
            a.recycle();
        }

        mPaint = new Paint();
        mPaint.setTextSize(mTitleTextSize);
        // mPaint.setColor(mTitleTextColor);
        mBound = new Rect();
        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);

        this.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mTitleText = randomText();
                postInvalidate();
            }
        });

    }

生成一個TypedArray物件,呼叫該物件的getString,getColor以及getDimensionPixelSize等方法獲得我們寫在xml檔案裡的自定義屬性,賦值給成員變數。

注意這些getXXX方法大部分需要傳兩個引數,一個是R.styleable.CustomTextView_XXX(注意這個下劃線是android自動生成的,根據鴻洋大神的分析,也就是說R.java裡面現在有styleable[]這麼一個int型別的陣列了,數組裡面裝著CustomTextView_mTextTitleSize、CustomTextView_mTextTitleColor等,final型別的int常量,styleable[CustomTextView_mTextTitleSize]位置具體裝的值就是比如說這種東西0x0101014f了,應該是記憶體地址吧)

不使用declared-styleable的話,你得自己編寫一大堆int常量作為陣列下標給getString,getDimensionPixel等方法去使用,有點多。。。

,另一個是預設值,如果找不到就返回預設值。

接下來建立繪製物件,官方文件給出了要在建構函式裡這麼做的原因:

剛開始就建立物件是一個重要的優化技巧。Views會被頻繁的重新繪製,初始化許多繪製物件需要花費昂貴的代價。在onDraw方法裡面建立繪製物件會嚴重影響到效能並使得你的UI顯得卡頓。

需要建立的有兩類:

繪製什麼:由Canvas處理

如何繪製:由Paint處理

Simply put, Canvas defines shapes that you can draw on the screen, while Paint defines the color, style, font, and so forth of each shape you draw.

這裡我們建立了一個Paint物件,這是必須的,Rect物件是要在onDraw(Canvas canvas)裡面交給canvas去使用的。

最後,建構函式裡面還添加了監聽器。。。。

3.重寫onMeasure方法

昨天折騰了一個下午的自定義ViewGroup,onMeasure就不多囉嗦了,

直接上程式碼

/**針對xml檔案中該View可能的measure模式準備了兩手:
     * 60dp,match_parent(EXACTLY)的時候就使用MeasureSpec去取size
     * wrap_content(AT_MOST)的時候就不用MeasureSpec裡面的size,重新算
     * 最後呼叫setMeasureDimension完成measure工作。
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;
        if (widthMode == MeasureSpec.EXACTLY) {
            width = widthSize;
        } else {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);
            float textWidth = mBound.width();
            int desired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width =desired;
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            height = heightSize;
        } else {
            mPaint.setTextSize(mTitleTextSize);
            mPaint.getTextBounds(mTitleText,0,mTitleText.length(),mBound);
            float texxHeight = mBound.height();
            int desired = (int) (getPaddingLeft() + texxHeight + getPaddingRight());
            height =desired;
        }

        setMeasuredDimension(width,height);
    }
重寫就是為了針對不同的xml裡的不同情況多做幾手準備。

4.重寫onDraw(Canvas canvas)方法
鑑於本人目前對於Canvas、BitMap、Rect一竅不通,直接借用別人的程式碼

 protected void onDraw(Canvas canvas)
    {
        mPaint.setColor(Color.GRAY);
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

        mPaint.setColor(mTitleTextColor);
        canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    }
總之就是畫了個圖。。。。。

引用官方的說法:

What to draw, handled by Canvas
How to draw, handled by Paint.

先這樣。