Android學習筆記---自定義View#02

上次我們研究了View的構造函數(shù),自定義View最重要的步驟就是完成我們View的繪制.我們本篇就來好好的研究一下它.
我們都知道重寫onDraw()方法便可對View進行各種繪制操作,但是在繪制之前我們還需要對View的布局進行處理,因為我們的自定義View可能需要在多種不同的布局環(huán)境下展現(xiàn),如果不對其進行布局的處理,可能會使View的繪制得不到想要的結果.

onSizeChanged()

處理布局無非就是要知道View在屏幕展現(xiàn)時的大小和位置,在繪制前計算得到這些值以及其它與View的大小相關的值,我們便能將View正確的繪制到屏幕上.
而最簡單的方法就是重寫onSizeChanged()函數(shù),這個函數(shù)會在View第一次分配大小時,以及當View的大小發(fā)生變化時被調用.因為當View的大小發(fā)生改變時才會對其布局造成影響,所以我們只需在onSizeChanged()函數(shù)中重新計算View的大小,位置,以及其他與View大小相關的值即可.這樣我們可以避免在每次進行繪制時都在onDraw()函數(shù)中進行計算,可以提高效率.
下面是一段示例代碼,使用的還是上次的相關代碼:

    private float mWidth, mHeight;

    /**
     * 在View大小發(fā)生變化是會被調用.可以在此函數(shù)進行View的大小,位置以及其他相關值的計算
     *
     * @param w    當前View的寬度
     * @param h    當前View的高度
     * @param oldw View改變前的寬度
     * @param oldh View改變前的高度
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        float xpad = (float)(getPaddingLeft()+getPaddingRight());
        float ypad = (float)(getPaddingTop()+getPaddingBottom());

        mWidth = w-xpad;
        mHeight = h-ypad;

    }

上面代碼中我們定義了mWidth,mHeight兩個成員屬性, 我們的自定義View是繪制一個矩形.所以在onSizeChanged()中需要計算矩形展現(xiàn)的大小.這里需要將padding的值計算在內,因為padding是需要View在布局時處理的.

onMeasure()

上面的onSizeChanged()函數(shù)可以簡單進行布局的處理,如果想要對View的布局參數(shù)進行細致的處理,這就需要重寫onMeasure()函數(shù).onMeasure()函數(shù)是由measure(int widthMeasureSpec, int heightMeasureSpec)函數(shù)所調用的,measure()用于獲取一個View應該在布局中的大小,其中兩個參數(shù)int widthMeasureSpec, int heightMeasureSpec由父布局提供,表示View的寬高的限制.但是具體實際的測量工作是在onMeasure()中完成的.

所以void onMeasure (int widthMeasureSpec,int heightMeasureSpec)需要對View和它的內容進行測量,為View的內容提供準確有效的測量.先來研究一下傳進來的兩個參數(shù)int widthMeasureSpec,int heightMeasureSpec.這兩個參數(shù)是表示當前View在布局時測量的寬高的限制,它們是經過View.MeasureSpec編碼處理的32位int值.它表示布局寬度或高度,并包含著sizemode兩個信息.高兩位表示mode,其余的30位表示size,它是通過位運算將它們結合在一起的.這兩個參數(shù)是由View的父控件傳遞過來的.我們在xml布局文件或在代碼中為View設置的寬高具體值或寬高的填充模式就對應著這兩個參數(shù)的mode的3種形式:

  • UNSPECIFIED
    表明parent沒有對child強加任何限制,child可以是它想要的任何尺寸
  • EXACTLY
    parentchild一個具體的值,child需要設置為該大小
    對應具體的寬高值或match_parent
  • AT_MOST
    child可以是任意的大小,但不能超過限制的值
    對應wrap_content

這些mode都表示View在測量時的限制.清楚了兩個參數(shù)的意義就可以對View進行測量了.但需要注意的是,View的寬高尺寸,只有在測量之后(measure()被調用后)才能得到.下面是onMeasure()的代碼:

    /**
     * 對View的內容進行測量
     *
     * @param widthMeasureSpec  parent對View寬度的限制信息
     * @param heightMeasureSpec parent對View高度的限制信息
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        float xpad = (float) (getPaddingLeft() + getPaddingRight());
        float ypad = (float) (getPaddingTop() + getPaddingBottom());
        mWidth = MeasureSpec.getSize(widthMeasureSpec) - xpad;
        mHeight = MeasureSpec.getSize(heightMeasureSpec) - ypad;
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

之前已經提到過了,我們需要對View的padding進行處理,這是View的職責.當重寫onMeasure()方法時必須調用void setMeasuredDimension(int measuredWidth, int measuredHeight)這個函數(shù)來存儲經過測量的寬高,否則會觸發(fā)IllegalStateException類型的異常,調用父類的onMeasure()函數(shù)也是一種方法,因為super.onMeasure()函數(shù)中調用了setMeasuredDimension(),上面的代碼我就直接調用了父類的onMeasure()方法.

我們可以進入super.onMeasure()當中去查看一下源代碼,它的實現(xiàn)如下:

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

果然如我們所說的,它調用了setMeasuredDimension(),代碼中還有調用了另外三個函數(shù)getDefaultSize(),getSuggestedMinimumWidth(),getSuggestedMinimumHeight().
我們先跳到getDefaultSize(),看一下這函數(shù)是干什么的.

    /**
     * Utility to return a default size. Uses the supplied size if the
     * MeasureSpec imposed no constraints. Will get larger if allowed
     * by the MeasureSpec.
     *
     * @param size Default size for this view
     * @param measureSpec Constraints imposed by the parent
     * @return The size this view should be.
     */
    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED: 
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

從注釋中可以看出,這個函數(shù)是根據傳進的參數(shù)獲取View的布局大小,具體過程就是根據測量模式的不同,返回不同的大小.getSuggestedMinimumHeight()和getSuggestedMinimumWidth()兩個函數(shù)就是獲取View的最小建議值,這就可以保證View的測量值是最少是建議的值(View的最小值或(View的最小值和背景最小值中的較大值)),下面就是具體的源碼.

protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
    }

onDraw()

測量完畢后,我們就可以進行View的繪制了.我們通過CanvasPaint進行繪制,Canvas決定繪制什么,而Paint決定怎么繪制.由于CanvasonDraw(Canvas canvas)傳入的參數(shù),但Paint卻需要我們自己創(chuàng)建和初始化,如果選擇在onDraw()里執(zhí)行這項工作,則在每次的View繪制時都會進行.所以為了提高效率,我們可以將Paint的創(chuàng)建和初始化放在View的構造函數(shù)中執(zhí)行.

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
    private void init() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
        mPaint.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + mWidth, getPaddingTop() + mHeight, mPaint);
    }

這里我們只簡單的繪制一個矩形.一個自定義的View基本上就完成了.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容