【自定義】ViewGroup初探

一、簡述

ViewGroup:視圖容器,顧名思義用於盛放視圖的。

Android的6大布局,都是自定義ViewGroup,因爲都繼承了ViewGroup。

此文,先不介紹自定義ViewGroup,先觀察一下6大布局是怎麼重寫ViewGroup的。

打開LinearLayout,ctrl+o操作,看一下類裏面的方法和結構,除了有很多方法,還包含LayoutParams類。

依次打開其他佈局,發現都有這個LayoutParams:佈局參數


二、ViewGroup中的LayoutParams(佈局參數)

個人理解:存儲子View的屬性參數,描述自己多寬、多高等等,加入到ViewGroup。

佈局參數,最基本的就是寬,高,當然也可以有其他參數,如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.wgl.share.share.MainActivity">
</LinearLayout>


LinearLayout在Android的attrs文件裏必定有一個聲明屬性的標籤,進入attrs.xml

<declare-styleable name="LinearLayout_Layout">
        <attr name="layout_width" />
        <attr name="layout_height" />
        <attr name="layout_weight" format="float" />
        <attr name="layout_gravity" />
    </declare-styleable>


RelativeLayout得到這些屬性值,是靠TypeArray,進入RelativeLayout:

public class LinearLayout extends ViewGroup {
    //略......
    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            TypedArray a = c.obtainStyledAttributes(attrs,
                    com.android.internal.R.styleable.RelativeLayout_Layout);
    //略......
  }

    public LayoutParams(int w, int h) {
            super(w, h);
        }

       
        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }

  }
}


觀察上面代碼,我發現這個方法LayoutParams(int w, int h),動態加載View時,設置寬高的,項目中很常見。

LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(10,10);
View view = new View(this);
view.setLayoutParams(params);
//設置圖片
view.setBackgroundResouce(R.mipmap.a)
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.linear);
linearLayout.addView(view);

注意:如果你給LinearLayout動態添加的View,就用LinearLayout.LayoutParams,根據佈局而定。

想找一個完整的動態添加View,可以搜索:頁面指示器。



三、自定義ViewGroup中的方法


1.onMeasure(...):View中的方法

測量方法,如果不重寫,只會測量ViewGroup的寬和高,子View的寬高都爲0。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
這樣會引起什麼呢?子View無法顯示,具體請往下看。


2.onLayout(...):ViewGroup的方法,給子View安排位置,如下

protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int left = 0;int top = 0; int right = 0; int bottom = 0;
        int count = getChildCount();
        for(int i = 0; i < count; i++){
            View child = getChildAt(i);
            right = left + child.getMeasuredWidth();
            bottom = top + child.getMeasuredHeight();
            child.layout(left, top, right, bottom);//給子view定位
        }
    }
請注意child.getMeasuredWith()和child.getMeasuredHeight(),如果onMeasure(...)方法沒有測量子View的寬高,那麼子View的左邊距、上邊距、右邊距、下邊距,都是0,肯定看不見的。

怎麼解決?回到onMeasure(...)方法,修改如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int count = getChildCount();
//        int childMeasuredWidth = 0;
//        int childMeasuredHeight = 0;
//        int childWidthMeasureSpec = 0;
//        int childHeightMeasureSpec = 0;
        for(int i = 0; i < count; i++){
            View child = getChildAt(i);
            //選擇一種測量就行
            //系統推薦測量
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            //自己測量
//             childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
//             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
//             child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }


兩種測量方式的區別是什麼?

a.系統測量,會根據佈局的寬高設置模式,測量子View。

佈局寬高設置了明確值或者macth_parent或者fill_parent,對應MeasureSpec.EXACTLY模式

佈局寬高設置wrap_content,對應MeasureSpec.AT_MOST模式

MeasureSpec.UNSPECIFIED模式(很少用)

b.自己測量,makeMeasureSpec(int size, int mode)返回一個尺寸和模式的合成值(這個合成值是32位的,int類型。前2位表示mode,後30位表示size。),然後再用measure(寬的合成值,高的合成值)測量子view大小。


3.根據以上兩點介紹,我們總結出:只有重寫onMeasure,才能得到子View寬高,才能正常顯示。可見測量方法,至關重要,還有一點,onMeasure方法,不是開始的時候就執行,onCreate,onStart,onResume執行完畢之後,他纔會執行。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章