Android深入淺出自訂控制項(二)

來源:互聯網
上載者:User

標籤:

在我的上篇博文Android深入遷出自訂控制項(一)中介紹了如何自訂View控制項,本篇博文主要介紹如何自訂ViewGroup


什麼是ViewGroup?在Android的樹狀結構圖中,ViewGroup類衍生出我們所熟悉的LinearLayout、RelativeLayout等布局:


簡單來說,ViewGroup其實就相當於所有布局的父親,所以我們可以通過自訂ViewGroup類實現千變萬化的布局。



自訂ViewGroup主要分為以下幾個步驟:1.建立自訂ViewGroup類,繼承於ViewGroup類,重寫ViewGroup的三個構造方法
2.重寫onMeasure方法,設定好ViewGroup及其子View在介面上所顯示的大小
3.添加參數
4.重寫onLayout方法,設定好子View的布局,這個方法也是最為關鍵的,它決定了你所自訂的布局的特性



1.建立自訂ViewGroup類,繼承於ViewGroup類,重寫ViewGroup的三個構造方法

public class FlowLayoutextends ViewGroup{public FlowLayout(Context context) {// TODO Auto-generated constructor stubthis(context,null);}public FlowLayout(Context context, AttributeSet attrs) {// TODO Auto-generated constructor stubthis(context,attrs,0);}public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);// TODO Auto-generated constructor stub}}



2.重寫onMeasure方法,設定好ViewGroup及其子View在介面上所顯示的大小

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubmeasureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}


對這個方法的使用見下文,此處記得添加measureChildren(widthMeasureSpec, heightMeasureSpec);表示由系統自己測量每個子View的大小



3.添加參數,比如我們想要讓子View之間能有間距,則需要手動建立一個內部參數類

public static class FlowLayoutParams extends ViewGroup.MarginLayoutParams{public FlowLayoutParams(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stub}}




4.重寫onLayout方法,設定好子View的布局,這個方法也是最為關鍵的,它決定了你所自訂的布局的特性

@Overrideprotected void onLayout(boolean arg0, int left, int top, int right, int bottom) {// TODO Auto-generated method stub//獲得FlowLayout所測量出來的寬度int mViewGroupWidth = getMeasuredWidth();/*** paintX:繪製每個View時的游標起點的橫座標* paintY:繪製每個View時的游標起點的縱座標*/int paintX = left;int paintY = top;//用於記錄上一行的最大高度int maxlineHeight = 0;int childCount = getChildCount();for(int i=0; i<childCount; i++){View childView = getChildAt(i);//獲得每個子View的margin參數FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();//獲得每個子View所測量出來的寬度和高度int childViewWidth = childView.getMeasuredWidth();int childViewHeight = childView.getMeasuredHeight();//如果繪製的起點橫座標+左間距+子View的寬度+右間距比FlowLayout的寬度還大,就需要進行換行if(paintX + childViewWidth + params.leftMargin + params.rightMargin> mViewGroupWidth){//繪製的起點的橫座標重新移回FlowLayout的橫座標paintX = left;//繪製的起點的縱座標要向下移動一行的高度paintY = paintY + maxlineHeight + params.topMargin + params.bottomMargin;maxlineHeight = 0;}maxlineHeight = Math.max(childViewHeight, maxlineHeight);childView.layout(paintX+params.leftMargin, paintY+params.topMargin, paintX+childViewWidth+params.leftMargin, paintY+childViewHeight+params.topMargin);//每繪製一次,起點游標就要向右移動一次paintX = paintX + childViewWidth + params.leftMargin + params.rightMargin;}}


解析:在onLayout方法中,我們對每個子View進行遍曆並設定好它們應該在的位置,比如是LinearLayout的話,在onLayout中系統會規定它的子View只能按著橫向或者豎向排列下去,也就是說,onLayout裡子View的排布是按著我們自己的想法來決定,到底這個自訂布局會有怎樣的特性?

此處我們demo是自訂FlowLayout,也就是每一行都向右排列下去,直到這一行不夠容納,則子View自動換行,進入下一行,依此類推,從而實現流式布局,為了實現這樣的效果,最關鍵的應該是在換行的時候,需要實現讓我們的子View能夠判斷到底換不換行,代碼思路如下:

1.首先需要記錄FlowLayout的寬度, 作為每一行的寬度上限:

int mViewGroupWidth = getMeasuredWidth();



2.每次繪製一個子View,是通過View.layout()方法來進行,而layout方法需要提供4個參數,即(繪製的起點橫座標,繪製的起點縱座標,子View的寬度,子View的高度),而每一個子View的繪製起點肯定不一樣,所以需要定義兩個變數來記錄:paintX,paintY:

/** * paintX:繪製每個View時的游標起點的橫座標 * paintY:繪製每個View時的游標起點的縱座標 */int paintX = left;int paintY = top;




3.通過for迴圈,遍曆得到每個子View,由於要讓子View之間能夠有間距,所以還需要定義一個margin參數提供給子View:
FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();


以及獲得每個子View的寬高:
int childViewWidth = childView.getMeasuredWidth();int childViewHeight = childView.getMeasuredHeight();




4.判斷如果再添加一個子View,需不需要換行,所以需要將這個View的寬度和當前行的寬度相加,與FlowLayout的寬度(即上限)進行對比,如果超過上限,就進行換行操作:

//繪製的起點的橫座標重新移回FlowLayout的橫座標paintX = left;//繪製的起點的縱座標要向下移動一行的高度paintY = paintY + maxlineHeight + params.topMargin + params.bottomMargin;//由於換行,所以當前行變成了下一行,最大高度自然也就置為當前這個新起行的子View的高度(因為它是下一行的第一個View)maxlineHeight = childViewHeight;


5.繪製當前子View,游標移動至下一個起點:

//每次都要計算當前行的最大高度maxlineHeight = Math.max(childViewHeight, maxlineHeight);childView.layout(paintX+params.leftMargin, paintY+params.topMargin, paintX+childViewWidth+params.leftMargin, paintY+childViewHeight+params.topMargin);//每繪製一次,起點游標就要向右移動一次paintX = paintX + childViewWidth + params.leftMargin + params.rightMargin;



至此,一個基本的FlowLayout布局定義完成,接下來就只需要在布局檔案中來使用它:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="fill_parent"    android:layout_height="fill_parent"     >        <com.example.view.FlowLayout    android:layout_width="fill_parent"    android:layout_height="fill_parent"        >        <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="全部"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="體育"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="連續劇"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="綜藝"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="電影"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="搞笑"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="兒童教育"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="技術教程"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="音樂頻道"    />    <Button         android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="賽事直播"    />    </com.example.view.FlowLayout></RelativeLayout>



運行:




可以看到達到了我們所要的效果,但這裡我們設定的FlowLayout的大小都是fill_parent,如果改為wrap_content會怎樣?
將FlowLayout的layout_height設定為wrap_content,雖然螢幕上仍然呈現的是一樣的效果,可是在預覽視窗中點擊FlowLayout,可以看到其實FlowLayout依舊是fill_parent的大小,而不是貼著子View的邊緣



這不是我們想要的效果,正確的做法應該是:設定為wrap_content時,FlowLayout的邊界是緊緊貼著子View的邊緣的,所以我們應該修改onMeasure方法:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stub//measureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);//得到FlowLayout的模式和寬高int widthMode = MeasureSpec.getMode(widthMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);//記錄wrap_content下的最終寬度和高度int width = 0;int height = 0;//記錄每一行的最大寬度和最大高度int lineWidth = 0;int lineHeight = 0;int childCount = getChildCount();for(int i=0;i<childCount;i++){View childView = getChildAt(i);measureChild(childView, widthMeasureSpec, heightMeasureSpec);FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();int childWidth = params.leftMargin + childView.getMeasuredWidth() + params.rightMargin;int childHeight = params.topMargin + childView.getMeasuredHeight() + params.bottomMargin;//如果是換行,則比較寬度取當前行的最大寬度和下一個子View的寬度,將最大者暫時作為FlowLayout的寬度if(lineWidth + childWidth > widthSize){width = Math.max(lineWidth, childWidth); height = height + lineHeight;lineWidth = childWidth;lineHeight = childHeight;}else              // 否則累加值lineWidth,lineHeight取最大高度          {                  lineWidth += childWidth;                  lineHeight = Math.max(lineHeight, childHeight);          } //如果是最後一個子Viewif (i == childCount - 1){                  width = Math.max(width, lineWidth);                  height += lineHeight;              }  }//根據模式來決定FlowLayout的大小,如果不是EXACTLY,則說明布局檔案中設定的模式應該是wrap_content/*** 如果是wrap_content,則將FlowLayout寬度和高度設定為我們計算出來的最終寬高* 如果是fill_parent或者具體數值,則將FlowLayout寬度和高度設定為一開始getMode和getSize得到的那個寬高*/setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize               : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize               : height); }



再次查看預覽視窗:



關於重寫系統控制項會在以後的博文中整合,希望本文能夠讓大家對自訂ViewGroup有所協助。

Android深入淺出自訂控制項(二)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.