Android自定義View--三部曲之二--布局過程的自定義

從凱哥Blog copy 過來 HenCoder Android UI 部分 2-1 布局基礎(chǔ)

自定義分三部分繪制、布局和觸摸反饋,本篇主要講的布局過程的自定義

布局過程的含義

布局過程,就是程序在運行時利用布局文件的代碼來計算出實際尺寸的過程。

布局過程的工作內(nèi)容

兩個階段:測量階段和布局階段。

測量階段:從上到下遞歸地調(diào)用每個 View 或者 ViewGroup 的 measure() 方法,
測量他們的尺寸并計算它們的位置;

布局階段:從上到下遞歸地調(diào)用每個 View 或者 ViewGroup 的 layout() 方法,
把測得的它們的尺寸和位置賦值給它們。

View 或 ViewGroup 的布局過程

測量階段,measure() 方法被父 View 調(diào)用,在 measure() 中做一些準(zhǔn)備和優(yōu)化工作后,
調(diào)用 onMeasure() 來進(jìn)行實際的自我測量。

onMeasure() 做的事,View 和 ViewGroup 不一樣:

View:View 在 onMeasure() 中會計算出自己的尺寸然后保存;
ViewGroup:ViewGroup 在 onMeasure() 中會調(diào)用所有子 View 的 measure() 讓它們進(jìn)行自我測量,
并根據(jù)子 View 計算出的期望尺寸來計算出它們的實際尺寸和位置(實際上 99.99% 的父 View 都會
使用子 View 給出的期望尺寸來作為實際尺寸,原因在下期或下下期會講到)然后保存。
同時,它也會根據(jù)子 View 的尺寸和位置來計算出自己的尺寸然后保存;

布局階段,layout() 方法被父 View 調(diào)用,在 layout() 中它會保存父 View 傳進(jìn)來的自己的位置和
尺寸,并且調(diào)用 onLayout() 來進(jìn)行實際的內(nèi)部布局。

onLayout() 做的事, View 和 ViewGroup也不一樣:

View:由于沒有子 View,所以 View 的 onLayout() 什么也不做。
ViewGroup:ViewGroup 在 onLayout() 中會調(diào)用自己的所有子 View 的 layout() 方法,
把它們的尺寸和位置傳給它們,讓它們完成自我的內(nèi)部布局。

布局過程自定義的方式

三類:

1.重寫 onMeasure() 來修改已有的 View 的尺寸;
2.重寫 onMeasure() 來全新定制自定義 View 的尺寸;
3.重寫 onMeasure() 和 onLayout() 來全新定制自定義 ViewGroup 的內(nèi)部布局。

第一類自定義--修改已有的 View 的尺寸

重寫 onMeasure() 來修改已有的 View 的尺寸的具體做法:
重寫 onMeasure() 方法,并在里面調(diào)用 super.onMeasure(),觸發(fā)原有的自我測量;

在 super.onMeasure() 的下面用 getMeasuredWidth() 和getMeasuredHeight()
來獲取到之前的測量結(jié)果,

并使用自己的算法,根據(jù)測量結(jié)果計算出新的結(jié)果;
調(diào)用 setMeasuredDimension() 來保存新的結(jié)果。

//重寫onMeasure()方法,自定義正方形ImageView

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        if (measuredWidth > measuredHeight) {
            measuredWidth = measuredHeight;
        } else {
            measuredHeight = measuredWidth;
        }
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

第二類自定義--全新定制自定義 View 的尺寸

全新定制尺寸和修改尺寸的最重要區(qū)別:

1.不需要調(diào)用super.onMeasure(widthMeasureSpec, heightMeasureSpec);
2.需要在計算的同時,保證計算結(jié)果滿足父 View 給出的的尺寸限制

父 View 的尺寸限制

由來:開發(fā)者的要求(布局文件中 layout_ 打頭的屬性)經(jīng)過父 View 處理計算后的更精確的要求;

限制的分類:

UNSPECIFIED:不限制(0)
AT_MOST:限制上限(-2147483648)
EXACTLY:限制固定值(1073741824)

全新定制自定義 View 尺寸的方式:

1.重寫 onMeasure(),并計算出 View 的尺寸;
2.使用 resolveSize() 來讓子 View 的計算結(jié)果符合父 View 的限制(當(dāng)然,如果你想用自己的方式
來滿足父 View 的限制也行。)

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

    public static int resolveSize(int size, int measureSpec) {
        return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
    }

    //View中的靜態(tài)方法
    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

第三類自定義--全新定制 ViewGroup 的內(nèi)部布局

重寫 onMeasure() 和 onLayout() 來定制 Layout 的內(nèi)部布局。

定制 Layout 內(nèi)部布局的方式

1.重寫 onMeasure() 來計算內(nèi)部布局
2.重寫 onLayout() 來擺放子 View

重寫 onMeasure() 的三個步驟:

1.調(diào)用每個子 View 的 measure() 來計算子 View 的尺寸
2.計算子 View 的位置并保存子 View 的位置和尺寸
3.計算自己的尺寸并用 setMeasuredDimension() 保存

計算子 View 尺寸的關(guān)鍵

計算子 View 的尺寸,關(guān)鍵在于 measure() 方法的兩個參數(shù)——也就是子 View 的兩個 MeasureSpec 的計算。

子 View 的 MeasureSpec 的計算方式:

1.結(jié)合開發(fā)者的要求(xml 中 layout_ 打頭的屬性)和自己的可用空間(自己的尺寸上限 - 已用尺寸)

2.尺寸上限根據(jù)自己的 MeasureSpec 中的 mode 而定

2.1 EXACTLY / AT_MOST:尺寸上限為 MeasureSpec 中的 size

2.2 UNSPECIFIED:尺寸無上限
@Override  
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
       //調(diào)用ViewGroup類中測量子類的方法  
       measureChildren(widthMeasureSpec, heightMeasureSpec);  
}  

/** 
 * 遍歷所有的子view去測量(跳過GONE類型View) 
 * @param widthMeasureSpec 父視圖的寬可用空間測量值 
 * @param heightMeasureSpec 父視圖的高可用空間測量值 
 */  
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
    final int size = mChildrenCount;  
    final View[] children = mChildren;  
    for (int i = 0; i < size; ++i) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {  
            measureChild(child, widthMeasureSpec, heightMeasureSpec);  
        }  
    }  
}  

/** 
 * 測量單個子View,將寬高和padding加在一起后交給getChildMeasureSpec去獲得最終的測量值 
 * @param child 需要測量的子視圖 
 * @param parentWidthMeasureSpec 父視圖的寬可用空間測量值 
 * @param parentHeightMeasureSpec 父視圖的高可用空間測量值 
 */  
protected void measureChild(View child, int parentWidthMeasureSpec,  
        int parentHeightMeasureSpec) {  
    // 取得子視圖的布局參數(shù)  
    final LayoutParams lp = child.getLayoutParams();  
    // 通過getChildMeasureSpec獲取最終的寬高詳細(xì)測量值  
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
            mPaddingLeft + mPaddingRight, lp.width);  
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
            mPaddingTop + mPaddingBottom, lp.height);  
  
    // 將計算好的寬高詳細(xì)測量值傳入measure方法,完成最后的測量  
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
} 

重寫 onLayout() 的方式

在 onLayout() 里調(diào)用每個子 View 的 layout() ,讓它們保存自己的位置和尺寸。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0; i < getChildCount(); i++) {
            View childAt = getChildAt(i);
            childAt.layout(childLeft[i], childTop[i], childRight[i], childBottom[i]);
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容