簡單的例子了解自定義ViewGroup(一)


在Android中,控件可以分為ViewGroup控件與View控件。自定義View控件,我之前的文章已經說過。這次我們主要說一下自定義ViewGroup控件。ViewGroup是作為父控件可以包含多個View控件,並管理其中包含的View控件。
一般自定義ViewGroup的流程如下:

  • onMeasure()
  • onLayout()
    我們一般不需要像自定義View一樣重寫onDraw(),這里需要多寫一個onLayout來擺放子View的位置。除了onLayout方法之外,我們還需要確定LayoutParams,這個是子View告訴父布局的一些參數信息,比如MarginLayoutParams,則表明該ViewGroup支持margin,當然這個也可以沒有。
    下面我們通過一個例子來說明簡單的自定義ViewGroup

一個簡單的例子

這個例子,我們將寫一個ViewGroup,該ViewGroup中4個button排成一列。這個例子主要說明onMeasure和onLayout的寫法。
首先我們新建一個MyViewGroup繼承ViewGroup,然后重寫onMeasure方法。

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

    }

這個重寫非常的簡單,調用父類的測量方法,然后測量所有的子控件的,只要子控件不是wrap_content都會測量精准。這里為了簡單,沒有去考慮wrap_content的情況,后面我們完善的時候會說道。
然后重寫onLayout()方法

  @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int height = 0;
        int count = getChildCount();
        View child;
        Log.e("ri", count + "");
        for(int i = 0 ;i < count;i++) {
            child = getChildAt(i);
            child.layout(0, height, child.getMeasuredWidth(),height +  child.getMeasuredHeight());
            height += child.getMeasuredHeight();

        }
    }

這里的代碼很好理解,因為我們要實現4個button一列顯示,然后每個子View的寬度是一樣的,並且每個子View的left和right是一樣的。所以每個子View只有top和bottom不一樣。我們首先定義個高度height初始為0,然后得到所有子View的個數,依次設置每個子View的top和bottom。top就是定義的height,bottom則為height加上子View的高度。設置完后height累加。
xml中布局如下:

 <com.example.byhieg.viewpratice.MyViewGroup android:layout_height="match_parent"
        android:layout_width="match_parent">

        <Button
            android:text="1"
            android:layout_width="50dp"
            android:layout_height="80dp" />
        <Button
            android:text="2"
            android:layout_width="50dp"
            android:layout_height="80dp" />
        <Button
            android:text="3"
            android:layout_width="50dp"
            android:layout_height="80dp" />
        <Button
            android:text="4"
            android:layout_width="50dp"
            android:layout_height="80dp" />
    </com.example.byhieg.viewpratice.MyViewGroup>

效果如下:

下面,我們繼續完善這個控件,讓他適應wrap_content。這里,我們主要重寫onMeasure()

   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec,heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);
        //開始處理wrap_content,如果一個子元素都沒有,就設置為0
        if (getChildCount() == 0) {
            setMeasuredDimension(0,0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup,寬,高都是wrap_content,根據我們的需求,寬度是子控件的寬度,高度則是所有子控件的總和
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth, childHeight * getChildCount());
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //ViewGroup的寬度為wrap_content,則高度不需要管,寬度還是自控件的寬度
            View childOne = getChildAt(0);
            int childWidth = childOne.getMeasuredWidth();
            setMeasuredDimension(childWidth,heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup的高度為wrap_content,則寬度不需要管,高度為子View的高度和
            View childOne = getChildAt(0);
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight * getChildCount());
        }
    }

主要通過注釋,就可以很明白wrap_content的情況下,如何計算viewGroup的高度和寬度。在viewGroup的onMeasure,我們是不需要在這里面考慮每一個View的寬高,這個通過measureChildren來通知每一個子View自己測量的。我們只需要考慮viewGroup的寬高在自適應的情況下,該是多大。

LayoutParams

在上面這個簡單的ViewGroup中,我們是沒有設置margin的,也就是說,即使我們在子View中設置了margin也是沒有效的。我們需要修改我們的自定義ViewGroup來適應margin的情況。這里我們為了簡化情況,只設定第一個button有一個android:layout_margin="30dp"的屬性。
這里,我們修改onMeasure方法,讓viewGroup的寬度變為原來的寬度加上margin的寬度,高度也是原來的高度加上margin的高度。代碼如下:

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

        MarginLayoutParams params = null;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        measureChildren(widthMeasureSpec,heightMeasureSpec);

        //開始處理wrap_content,如果一個子元素都沒有,就設置為0
        if (getChildCount() == 0) {
            setMeasuredDimension(0,0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup,寬,高都是wrap_content,根據我們的需求,寬度是子控件的寬度,高度則是所有子控件的總和
            View childOne = getChildAt(0);
            params = (MarginLayoutParams) childOne.getLayoutParams();
            int childWidth = childOne.getMeasuredWidth();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,
                                childHeight * getChildCount() + params.topMargin + params.bottomMargin);

        } else if (widthMode == MeasureSpec.AT_MOST) {
            //ViewGroup的寬度為wrap_content,則高度不需要管,寬度還是自控件的寬度
            View childOne = getChildAt(0);
            params = (MarginLayoutParams) childOne.getLayoutParams();
            int childWidth = childOne.getMeasuredWidth();
            setMeasuredDimension(childWidth + params.leftMargin + params.rightMargin,heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //ViewGroup的高度為wrap_content,則寬度不需要管,高度為子View的高度和
            View childOne = getChildAt(0);
            params = (MarginLayoutParams) childOne.getLayoutParams();
            int childHeight = childOne.getMeasuredHeight();
            setMeasuredDimension(widthSize, childHeight * getChildCount() + params.topMargin + params.bottomMargin);
        }
    }

這里,注意這個語句params = (MarginLayoutParams) childOne.getLayoutParams(); 如果不重寫layoutParams相關的代碼,這樣直接轉換會出現問題。所以,我們需要重寫如下代碼:讓他返回MarginLayoutParams類型的對象

@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(),attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

同樣,我們已經測量得到了viewGroup的寬和高,接下來,需要對添加了margin的view,重新擺放。主要的擺放規則,左邊的坐標為Leftmargin,第一個view的上面的坐標為topMargin,同時,第二個view的上面的坐標要加上bottomMargin。這個只是一個簡單的例子來說明放入margin之后要怎么考慮,一般不會這么具體到只計算第一個view的Margin。代碼看看就好

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int height = 0;
        int count = getChildCount();
        View child;
        Log.e("ri", count + "");
        child = getChildAt(0);
        MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
        int c1 = params.leftMargin;
        int c2 = params.topMargin;
        int c3 = c1 + child.getMeasuredWidth();
        int c4 = c2 + child.getMeasuredHeight();
        child.layout(c1,c2, c3,c4);
        height = c4 + params.bottomMargin;
        for(int i = 1 ;i < count;i++) {
            child = getChildAt(i);
            child.layout(c1, height, c3, height + child.getMeasuredHeight());
            height += child.getMeasuredHeight();
        }
    }

總結

這是自定義ViewGroup的第一篇文章,自定義ViewGroup是比自定義View知識點更多,而且應用也更廣泛。這篇只是簡單的介紹了自定義ViewGroup中需要復寫的方法,但是一般的viewGroup不會是這樣的。上面的東西,用一個LinearLayout布局實現,然后include該布局更方便。一般viewGroup都需要我們來實現view事件的分發以及滑動的處理,接下來的文章,講介紹滑動的多種方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM