关于onMeasure()、onLayout()方法

在写继承ViewGroup的自定义View时通常会重写onMeasure()、onLayout()方法,下面分别说下这2个方法。

1、 onMeasure():

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
}

此方法用于测量自身显示在页面上的大小。参数widthMeasureSpec为父类传过来的宽度的"建议值",参数heightMeasureSpec为父类传过来的高度的"建议值"。这2个参数不是简单的整数类型,而是2位整数(模式类型UNSPECIFIED|EXACTLY|AT_MOST)和30位整数(实际尺寸) 的组合值。

UNSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小。对应十进制的值为0。
EXACTLY:父元素决定子元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小。对应十进制的值为1。
AT_MOST:子元素至多达到指定大小的值。对应十进制的值为2。

获取宽高模式int值方法:

int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);  
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);  

获取宽高尺寸int值方法:

int measureWidth = MeasureSpec.getSize(widthMeasureSpec);  
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);  

2、 onLayout():

 protected void onLayout(boolean changed, int l, int t, int r, int b){
}

只要是继承ViewGroup的类都必须重写该方法,严格来说应该是有子view的ViewGroup要重写此方法。此方法是用来实现该控件内部子控件的布局情况。

案例1:
这里写一个自定义类继承ViewGroup实现LinearLayout垂直排列的效果(忽略margin和pading设置):

/**
 * Created by shengqf
 * Email : shengqf@bsoft.com.cn
 * date : 2019/2/25
 * describe : 垂直方向的线性布局(忽略margin和pading设置)
 */
public class VerticalLinearLayout extends ViewGroup {

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

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

    public VerticalLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @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 childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            int childHeight = childView.getMeasuredHeight();
            int childWidth = childView.getMeasuredWidth();
            height += childHeight;
            width = Math.max(childWidth, width);
        }

        //设置VerticalLinearLayout的宽高
        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 childView = getChildAt(i);
            int childHeight = childView.getMeasuredHeight();
            int childWidth = childView.getMeasuredWidth();
            //以父容器左上角为原点,垂直往下摆放子view
            childView.layout(0, top, childWidth, top + childHeight);
            top += childHeight;
        }
    }
}

布局:

<?xml version="1.0" encoding="utf-8"?>
<com.shengqf.view.flowlayout.VerticalLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#FF0">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

</com.shengqf.view.flowlayout.VerticalLinearLayout>

看下效果:
在这里插入图片描述

案例2:
为加深对onMeasure()、onLayout()方法的理解,这里再写一个线性自动换行布局(简称流式布局),:
参考:https://github.com/hongyangAndroid/FlowLayout

/**
 * Created by shengqf
 * Email : shengqf@bsoft.com.cn
 * date : 2019/2/25
 * describe : 线性自动换行布局(流式布局)
 * 参考:https://github.com/hongyangAndroid/FlowLayout
 */
public class FlowLayout extends ViewGroup {

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

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

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

        /**
         * 以下代码是FlowLayout的宽或高设置为wrap_content的情况
         * 如果FlowLayout的宽高均为确定值时,最后的测量直接写成:
         * setMeasuredDimension(measureWidth,measureHeight);
         */

        /**
         * FlowLayout的宽度
         * a、当FlowLayout的宽设置为wrap_content时,width为FlowLayout里面所有行中最宽一行的宽度
         * b、当FlowLayout的宽设置为确定值时,width为measure的宽度measureWidth
         */
        int width = 0;

        /**
         * FlowLayout的高度
         * a、当FlowLayout的高设置为wrap_content时,height为FlowLayout里面所有行高的总合
         * b、当FlowLayout的宽设置为确定值时,height为measure的高度measureHeight
         */
        int height = 0;

        int lineWidth = 0;//记录FlowLayout里面每一行子view的宽度
        int lineHeight = 0;//记录FlowLayout里面每一行子view的高度

        int count = getChildCount();

        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);

            if (childView.getVisibility() == View.GONE) {
                if (i == count - 1) {
                    width = Math.max(lineWidth, width);
                    height += lineHeight;
                }
                continue;
            }

            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams marginParams = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth()
                    + marginParams.leftMargin + marginParams.rightMargin;
            int childHeight = childView.getMeasuredHeight()
                    + marginParams.topMargin + marginParams.bottomMargin;

            if (lineWidth + childWidth > measureWidth - getPaddingLeft() - getPaddingRight()) {
                width = Math.max(width, lineWidth);
                lineWidth = childWidth;
                height += lineHeight;
                lineHeight = childHeight;
            } else {//一行没填满,继续往后添加子view
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }

            if (i == count - 1) {
                width = Math.max(lineWidth, width);
                height += lineHeight;
            }
        }
        int tempWidth = measureWidthMode == MeasureSpec.EXACTLY ?
                measureWidth : width + getPaddingLeft() + getPaddingRight();
        int tempHeight = measureHeightMode == MeasureSpec.EXACTLY ?
                measureHeight : height + getPaddingTop() + getPaddingBottom();
        setMeasuredDimension(tempWidth, tempHeight);
    }

    private List<View> lineViews = new ArrayList<>();//某一行子view的集合
    protected List<List<View>> mAllViews = new ArrayList<>();//所有行的集合
    protected List<Integer> mLineHeight = new ArrayList<>();//所有行中每一行子view的最大的高度的集合
    protected List<Integer> mLineWidth = new ArrayList<>();//所有行中每一行子view的宽度和的集合

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllViews.clear();
        mLineHeight.clear();
        mLineWidth.clear();
        lineViews.clear();

        int width = getWidth();//当宽设置为wrap_content时,值为屏幕宽度

        int lineWidth = 0;
        int lineHeight = 0;

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View childView = getChildAt(i);
            if (childView.getVisibility() == View.GONE) {
                continue;
            }
            MarginLayoutParams marginParams = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth()
                    + marginParams.leftMargin + marginParams.rightMargin;
            int childHeight = childView.getMeasuredHeight()
                    + marginParams.topMargin + marginParams.bottomMargin;

            if (childWidth + lineWidth > width - getPaddingLeft() - getPaddingRight()) {
                mLineHeight.add(lineHeight);
                mLineWidth.add(lineWidth);
                mAllViews.add(lineViews);

                lineWidth = 0;
                lineHeight = childHeight;
                lineViews = new ArrayList<>();
            }
            lineWidth += childWidth;
            lineHeight = Math.max(lineHeight, childHeight);
            lineViews.add(childView);
        }
        mLineHeight.add(lineHeight);
        mLineWidth.add(lineWidth);
        mAllViews.add(lineViews);

        int left;
        int top = getPaddingTop();

        int lineNum = mAllViews.size();//行数

        for (int i = 0; i < lineNum; i++) {//遍历所有行
            lineViews = mAllViews.get(i);
            lineHeight = mLineHeight.get(i);

            //左对齐显示
            left = getPaddingLeft();
            //居中显示
            //left = (width - mLineWidth.get(i)) / 2 + getPaddingLeft();
            //右对齐显示
            //left = width - mLineWidth.get(i) + getPaddingLeft();

            for (int j = 0; j < lineViews.size(); j++) {//遍历某一行中的所有子view
                View childView = lineViews.get(j);
                if (childView.getVisibility() == View.GONE) {
                    continue;
                }

                MarginLayoutParams marginParams = (MarginLayoutParams) childView.getLayoutParams();
                int lc = left + marginParams.leftMargin;
                int tc = top + marginParams.topMargin;
                int rc = lc + childView.getMeasuredWidth();
                int bc = tc + childView.getMeasuredHeight();

                //摆放子view
                childView.layout(lc, tc, rc, bc);

                left += childView.getMeasuredWidth()
                        + marginParams.leftMargin + marginParams.rightMargin;
            }
            top += lineHeight;
        }

    }

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

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

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

布局:

<?xml version="1.0" encoding="utf-8"?>
<com.shengqf.view.flowlayout.FlowLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/flow_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp"
    android:background="#FF0">

</com.shengqf.view.flowlayout.FlowLayout>

使用:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_vertical_linear_layout);

        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add("士大夫士大夫");
            list.add("蒙多");
            list.add("司法所地方");
            list.add("是否");
            list.add("大幅度");
            list.add("的撒范德的");
            list.add("的撒范德萨的撒范德");
            list.add("士大");
            list.add("野第三方的撒范德萨佛挡杀佛");
            list.add("订单");
            list.add("都是所得税");
            list.add("所得税");
        }

        FlowLayout flowLayout = findViewById(R.id.flow_layout);
        for (int j = 0; j < 10; j++) {
            Button button = new Button(this);
            button.setText(list.get(j));
            ViewGroup.MarginLayoutParams params = new ViewGroup.MarginLayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
            params.setMargins(10,10,0,0);
            button.setLayoutParams(params);
            flowLayout.addView(button);
        }
    }

运行:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值