前一篇文章主要講了自定義View為什么要重載onMeasure()方法,那么,自定義ViewGroup又都有哪些方法需要重載或者實(shí)現(xiàn)呢 ?
我們提供的服務(wù)有:成都網(wǎng)站制作、成都做網(wǎng)站、微信公眾號(hào)開(kāi)發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、通許ssl等。為成百上千家企事業(yè)單位解決了網(wǎng)站和推廣的問(wèn)題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的通許網(wǎng)站制作公司
Android開(kāi)發(fā)中,對(duì)于自定義View,分為兩種,一種是自定義控件(繼承View類),另一種是自定義布局容器(繼承ViewGroup)。如果是自定義控件,則一般需要重載兩個(gè)方法,一個(gè)是onMeasure(),用來(lái)測(cè)量控件尺寸,另一個(gè)是onDraw(),用來(lái)繪制控件的UI。而自定義布局容器,則一般需要實(shí)現(xiàn)/重載三個(gè)方法,一個(gè)是onMeasure(),也是用來(lái)測(cè)量尺寸;一個(gè)是onLayout(),用來(lái)布局子控件;還有一個(gè)是dispatchDraw(),用來(lái)繪制UI。
本文主要分析自定義ViewGroup的onLayout()方法的實(shí)現(xiàn)。
ViewGroup類的onLayout()函數(shù)是abstract型,繼承者必須實(shí)現(xiàn),由于ViewGroup的定位就是一個(gè)容器,用來(lái)盛放子控件的,所以就必須定義要以什么的方式來(lái)盛放,比如LinearLayout就是以橫向或者縱向順序存放,而RelativeLayout則以相對(duì)位置來(lái)擺放子控件,同樣,我們的自定義ViewGroup也必須給出我們期望的布局方式,而這個(gè)定義就通過(guò)onLayout()函數(shù)來(lái)實(shí)現(xiàn)。
我們通過(guò)實(shí)現(xiàn)一個(gè)水平優(yōu)先布局的視圖容器來(lái)更加深入地了解onLayout()的實(shí)現(xiàn)吧,效果如圖所示(黑色方塊為子控件,白色部分為自定義布局容器)。該容器的布局方式是,首先水平方向上擺放子控件,水平方向放不下了,則另起一行繼續(xù)水平擺放。
1. 自定義ViewGroup的派生類
第一步,則是自定ViewGroup的派生類,繼承默認(rèn)的構(gòu)造函數(shù)。
public class CustomViewGroup extends ViewGroup { public CustomViewGroup(Context context) { super(context); } public CustomViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } public CustomViewGroup(Context context, AttributeSet attrs, intdefStyle) { super(context, attrs, defStyle); } }
2. 重載onMeasure()方法
為什么要重載onMeasure()方法這里就不贅述了,上一篇文章已經(jīng)講過(guò),這里需要注意的是,自定義ViewGroup的onMeasure()方法中,除了計(jì)算自身的尺寸外,還需要調(diào)用measureChildren()函數(shù)來(lái)計(jì)算子控件的尺寸。
onMeasure()的定義不是本文的討論重點(diǎn),因此這里我直接使用默認(rèn)的onMeasure()定義,當(dāng)然measureChildren()是必須得加的。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); }
3. 實(shí)現(xiàn)onLayout()方法
onLayout()函數(shù)的原型如下:
//@param changed 該參數(shù)指出當(dāng)前ViewGroup的尺寸或者位置是否發(fā)生了改變 //@param left top right bottom 當(dāng)前ViewGroup相對(duì)于其父控件的坐標(biāo)位置 protected void onLayout(boolean changed,int left, int top, int right, int bottom);
由于我們希望優(yōu)先橫向布局子控件,那么,首先,我們知道總寬度是多少,這個(gè)值可以通過(guò)getMeasuredWidth()來(lái)得到,當(dāng)然子控件的寬度也可以通過(guò)子控件對(duì)象的getMeasuredWidth()來(lái)得到。
這樣,就不復(fù)雜了,具體的實(shí)現(xiàn)代碼如下所示:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int mViewGroupWidth = getMeasuredWidth(); //當(dāng)前ViewGroup的總寬度 int mPainterPosX = left; //當(dāng)前繪圖光標(biāo)橫坐標(biāo)位置 int mPainterPosY = top; //當(dāng)前繪圖光標(biāo)縱坐標(biāo)位置 int childCount = getChildCount(); for ( int i = 0; i < childCount; i++ ) { View childView = getChildAt(i); int width = childView.getMeasuredWidth(); int height = childView.getMeasuredHeight(); //如果剩余的空間不夠,則移到下一行開(kāi)始位置 if( mPainterPosX + width > mViewGroupWidth ) { mPainterPosX = left; mPainterPosY += height; } //執(zhí)行ChildView的繪制 childView.layout(mPainterPosX,mPainterPosY,mPainterPosX+width, mPainterPosY+height); //記錄當(dāng)前已經(jīng)繪制到的橫坐標(biāo)位置 mPainterPosX += width; } }
4. 布局文件測(cè)試
下面我們就嘗試寫一個(gè)簡(jiǎn)單的xml文件,來(lái)測(cè)試一下我們的自定義ViewGroup,我們把子View的背景顏色都設(shè)置為黑色,方便我們辨識(shí)。
<com.titcktick.customview.CustomViewGroup xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <View android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" android:background="@android:color/black"/> <View android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" android:background="@android:color/black"/> <View android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" android:background="@android:color/black"/> <View android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" android:background="@android:color/black"/> </com.titcktick.customview.CustomViewGroup>
5. 添加layout_margin
為了讓核心邏輯更加清晰,上面的onLayout()實(shí)現(xiàn)我隱去了margin的計(jì)算,這樣就會(huì)導(dǎo)致子控件的layout_margin不起效果,所以上述效果是子控件一個(gè)個(gè)緊挨著排列,中間沒(méi)有空隙。那么,下面我們來(lái)研究下如何添加margin效果。
其實(shí),如果要自定義ViewGroup支持子控件的layout_margin參數(shù),則自定義的ViewGroup類必須重載generateLayoutParams()函數(shù),并且在該函數(shù)中返回一個(gè)ViewGroup.MarginLayoutParams派生類對(duì)象,這樣才能使用margin參數(shù)。
ViewGroup.MarginLayoutParams的定義關(guān)鍵部分如下,它記錄了子控件的layout_margin值:
public static class MarginLayoutParams extends ViewGroup.LayoutParams { public int leftMargin; public int topMargin; public int rightMargin; public int bottomMargin; }
你可以跟蹤源碼看看,其實(shí)XML文件中View的layout_xxx參數(shù)都是被傳遞到了各種自定義ViewGroup.LayoutParams派生類對(duì)象中。例如LinearLayout的LayoutParams定義的關(guān)鍵部分如下:
public class LinearLayout extends ViewGroup { public static class LayoutParams extends ViewGroup.MarginLayoutParams { public float weight; public int gravity = -1; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); TypedArray a = c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout); weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0); gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1); a.recycle(); } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LinearLayout.LayoutParams(getContext(), attrs); } }
這樣你大概就可以理解為什么LinearLayout的子控件支持weight和gravity的設(shè)置了吧,當(dāng)然我們也可以這樣自定義一些屬于我們ViewGroup特有的params,這里就不詳細(xì)討論了,我們只繼承MarginLayoutParams來(lái)獲取子控件的margin值。
public class CustomViewGroup extends ViewGroup { public static class LayoutParams extends ViewGroup.MarginLayoutParams { public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new CustomViewGroup.LayoutParams(getContext(), attrs); } }
這樣修改之后,我們就可以在onLayout()函數(shù)中獲取子控件的layout_margin值了,添加了layout_margin的onLayout()函數(shù)實(shí)現(xiàn)如下所示:
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int mViewGroupWidth = getMeasuredWidth(); //當(dāng)前ViewGroup的總寬度 int mViewGroupHeight = getMeasuredHeight(); //當(dāng)前ViewGroup的總高度 int mPainterPosX = left; //當(dāng)前繪圖光標(biāo)橫坐標(biāo)位置 int mPainterPosY = top; //當(dāng)前繪圖光標(biāo)縱坐標(biāo)位置 int childCount = getChildCount(); for ( int i = 0; i < childCount; i++ ) { View childView = getChildAt(i); int width = childView.getMeasuredWidth(); int height = childView.getMeasuredHeight(); CustomViewGroup.LayoutParams margins = (CustomViewGroup.LayoutParams)(childView.getLayoutParams()); //ChildView占用的width = width+leftMargin+rightMargin //ChildView占用的height = height+topMargin+bottomMargin //如果剩余的空間不夠,則移到下一行開(kāi)始位置 if( mPainterPosX + width + margins.leftMargin + margins.rightMargin > mViewGroupWidth ) { mPainterPosX = left; mPainterPosY += height + margins.topMargin + margins.bottomMargin; } //執(zhí)行ChildView的繪制 childView.layout(mPainterPosX+margins.leftMargin, mPainterPosY+margins.topMargin,mPainterPosX+margins.leftMargin+width, mPainterPosY+margins.topMargin+height); mPainterPosX += width + margins.leftMargin + margins.rightMargin; } }
6. 總結(jié)
費(fèi)了好大勁,終于算是把自定義ViewGroup的onLayout()相關(guān)知識(shí)點(diǎn)講清楚了,如果有任何疑問(wèn)歡迎留言或者來(lái)信lujun.hust@gmail.com交流,,或者關(guān)注我的新浪微博 @盧_俊 獲取最新的文章和資訊。
本文名稱:Android開(kāi)發(fā)實(shí)踐:自定義ViewGroup的onLayout()分析
鏈接URL:http://chinadenli.net/article16/gijodg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App設(shè)計(jì)、網(wǎng)站收錄、ChatGPT、Google、全網(wǎng)營(yíng)銷推廣、定制開(kāi)發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)