自定义控件—自定义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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值