自定義控件—自定義ViewGroup(實現多列RadioGroup)

ViewGroup的基本概念

ViewGroup繪製流程分爲三部分:測量、佈局、繪製。分別對應 onMeasure 、onLayout 、 onDraw 函數。
這三個函數的作用分別如下;

  • onMeasure : 測量控件大小,爲正式佈局提供建議。至於用不用還要看onLayout函數
  • onLayout : 對所有子控件進行佈局
  • onDraw : 根據佈局的位置繪圖。

onMeasure 函數與 MeasureSpec

onMeasure 函數的聲明如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

其中 widthMeasureSpec 和 heightMeasureSpec 是由mode + size組成,其中前兩位代表模式,後30位代表數值。

模式的分類
MeasureSpec.UNSPECIFIED (未指定 ):父元素不對子元素施加任何束縛,子元素可以得到任意想要的大小
MeasureSpec.AT_MOSTwrap_content):根據子控件確定自身大小
MeasureSpec.EXACTLY精確值100dpmatch_parent):子元素至多達到指定的大小

他們的二進制值分別是:
MeasureSpec.UNSPECIFIED:32個0
MeasureSpec.EXACTLY:01+30個0
MeasureSpec.AT_MOST :10+30個0

前面兩位代表模式,他們分別代表十進制的0、1、2

模式提取

widthMeasureSpec、heightMeasureSpec是由模式和大小組成,前兩位代表模式。下面看看如何提取模式和大小;
首先我們想到的是和Mask運算,去掉不需要的部分。

上面是我們自己實現的,實際Android已經爲我們實現了提取模式和大小的實現。
例如:

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

另外,模式的取值爲:

  • MeasureSpec.UNSPECIFIED
  • MeasureSpec.AT_MOST
  • MeasureSpec.EXACTLY

模式的用處及對應關係

實際上在xml中定義的寬高,就是在這些模式中的一個。下面爲模式的對應關係
wrap_content -> MeasureSpec.AT_MOST
match_parent 和 具體的數值 -> MeasureSpec.EXACTLY

當用戶指定值爲:wrap_content 的時候我們需要計算控件的值,否則應該尊重,選用用戶的值。

getMeasuredxxx和getxxx(xxx代表寬高)

兩者主要有如下區別:

  • getMeasuredHeight():在measure過程結束後才能拿到數值。而getHight()需要在layout過程後才能拿到數值
  • getMeasuredHeight():數值通過setMeasuredDimension()指定的。而getHeight是通過layout()指定的

讓子控件支持margin值

如果想要支持子控件的layout_margin屬性,則自定義的ViewGroup必須重寫generateLayoutParams()函數
因爲默認的layoutParams值只會獲取寬高,不會設置margin屬性值。所以,我們需要一個MarginLayoutParams類來讓子控件獲取margin值。詳細請看MarginLayoutParams源碼。
添加如下代碼,讓你的ViewGroup支持margin

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

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

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

計算ViewGroup大小示例:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int height = 0;
        int width = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            //int childHeight = child.getMeasuredHeight();
            //int childWidth = child.getMeasuredWidth();

            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

            height += childHeight;
            width = Math.max(childWidth, width);
        }

        setMeasuredDimension(
                (measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : width,
                (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : height
        );
    }

擺放子控件位置:onLayout

在onLayout中設置子控件的layout屬性,進而控制子控件位置擺放。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int top = 0;
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        //int childHeight = child.getMeasuredHeight();
        //int childWidth = child.getMeasuredWidth();
        MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
        int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

        child.layout(0, top, childWidth, top + childHeight);
        top += childHeight;
    }
}

自定義LinearLayout實現完整代碼:


import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class MyViewGroup extends ViewGroup {
    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

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

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int height = 0;
        int width = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            //int childHeight = child.getMeasuredHeight();
            //int childWidth = child.getMeasuredWidth();

            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

            height += childHeight;
            width = Math.max(childWidth, width);
        }

        setMeasuredDimension(
                (measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : width,
                (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : height
        );
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int top = 0;
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            //int childHeight = child.getMeasuredHeight();
            //int childWidth = child.getMeasuredWidth();
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

            child.layout(0, top, childWidth, top + childHeight);
            top += childHeight;
        }
    }
}

自定義RadioGroup(實現指定列)

本示例實現指定列的佈局測量和擺放方法。
實現思路:
自定義佈局,主要實現的方法有:測量和佈局。
測量高度:測量每行的最大高度值進行累加。
測量寬度:本示例爲實現

佈局行:每隔指定列 top值進行累加。
實現代碼:

public class YfTypeLayout extends RadioGroup {
    private int rowNum = 3;

    public YfTypeLayout(Context context) {
        super(context);
    }

    public YfTypeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

        int height = 0;
        int width = 0;
        int count = getChildCount();
        int rowMaxHeight = 0;

        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;

            rowMaxHeight = Math.max(rowMaxHeight, childHeight);

            if (i % rowNum == rowNum - 1 || i == count - 1) {
                height += rowMaxHeight;
                rowMaxHeight = 0;
            }

            width = Math.max(childWidth, width);
        }

        setMeasuredDimension(
                (measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth : width,
                (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight : height
        );
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int top = 0;
        int left;
        int parentWidth = getMeasuredWidth();
        int count = getChildCount();
        int rowMaxHeight = 0;
        int region = parentWidth / 3;
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            if (i % rowNum == 0) {
                left = region / 2 - childWidth / 2;
                top += rowMaxHeight;
            } else {
                left = region * (i % rowNum) + region / 2 - childWidth / 2;
            }
            rowMaxHeight = rowMaxHeight > childHeight ? rowMaxHeight : childHeight;
            child.layout(left, top, left + childWidth, top + childHeight);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章