自定义控件那些事儿 ----- 四【布局】

本文详细介绍自定义ViewGroup的onLayout方法实现,通过实例演示如何创建一个多子View的自定义布局控件,并考虑margin值的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、onLayout布局出洞


在上一篇文章自定义控件那些事儿 ------ 三【量测】中,为实现图片和文字组合控件的正确显示,就已经引入了onLayout实现的整体过程。

onLayout具体实现中,主要执行在layout()方法上。分别为left,top,right,bottom。实则是确定了当前控件的左上角和右下角,从而确定了当前控件展示的区域。所以,对一个View强制使用layout()方法,可以修改当前view所在的位置。在之后的属性动画实现中,就应用了这一特点。

达到理解整个布局过程作用,重写ViewGroup作为布局文件,其中内部子View均采用已有控件。从而避免较多的实现onMeasure()方法,导致整个过程的复杂。


二、初步实现


1,布局控件ViewGroup的实现


public class LayoutView extends ViewGroup {
    public LayoutView(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onLayout(boolean b, int left, int top, int right, int bottom) {
        int tempHeight = 0;
        int mViewGroupWidth = getMeasuredWidth();  //当前ViewGroup的总宽度

        int mPainterPosX = left;  //当前绘图光标横坐标位置
        int mPainterPosY = top;  //当前绘图光标纵坐标位置

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {

            View childView = getChildAt(i);

            int width = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();

            if (height > tempHeight) {
                tempHeight = height;
            }
            //如果剩余的空间不够,则移到下一行开始位置
            if (mPainterPosX + width > mViewGroupWidth) {
                mPainterPosX = left;
                mPainterPosY += tempHeight;
            }

            //执行ChildView的绘制
            childView.layout(mPainterPosX, mPainterPosY, mPainterPosX + width, mPainterPosY + height);

            //记录当前已经绘制到的横坐标位置
            mPainterPosX += width;
        }
    }

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

模仿LinearLayout的实现,做一个内部包含多个控件的控件。从左向右开始排列。当行距不够时,切换下一行显示。
注意点:
在类中使用tempHeight变量,用于记录当前行View的最高高度。换行以后的View只能在这个View之下,防止View之间的叠加。在换行中,要将tempHeight归零,第二行的高度不再以第一行的高度为最高值。
布局中使用:


<?xml version="1.0" encoding="utf-8"?>
<com.future.layoutdemo.view.LayoutView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.future.layoutdemo.MainActivity">

    <View
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/colorAccent" />

    <View
        android:layout_width="180dp"
        android:layout_height="130dp"
        android:background="@color/colorPrimaryDark" />


    <View
        android:layout_width="30dp"
        android:layout_height="80dp"
        android:background="@color/colorPrimary" />


    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@color/colorAccent" />

    <View
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:background="@color/colorPrimary" />

</com.future.layoutdemo.view.LayoutView>

展示效果:



三、内容完善


在以上的实现中,添加margin值是没有效果的,当然不符合使用要求了!


1,修改控件实现,添加布局LayoutParams
在布局中优化布局计算,添加margin值影响。


public class Layout2View extends ViewGroup {
    public Layout2View(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onLayout(boolean b, int left, int top, int right, int bottom) {
        int tempHeight = 0;

        int mViewGroupWidth = getMeasuredWidth();  //当前ViewGroup的总宽度
        int mViewGroupHeight = getMeasuredHeight(); //当前ViewGroup的总高度

        int mPainterPosX = left; //当前绘图光标横坐标位置
        int mPainterPosY = top;  //当前绘图光标纵坐标位置

        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {

            View childView = getChildAt(i);

            int width = childView.getMeasuredWidth();
            int height = childView.getMeasuredHeight();

            if (height > tempHeight) {
                tempHeight = height;
            }

            Layout2View.LayoutParams margins = (Layout2View.LayoutParams) (childView.getLayoutParams());

            //ChildView占用的width  = width+leftMargin+rightMargin
            //ChildView占用的height = height+topMargin+bottomMargin
            //如果剩余的空间不够,则移到下一行开始位置
            if (mPainterPosX + width + margins.leftMargin + margins.rightMargin > mViewGroupWidth) {
                mPainterPosX = left;
//                mPainterPosY += height + margins.topMargin + margins.bottomMargin;
                mPainterPosY += tempHeight + margins.topMargin + margins.bottomMargin;
            }

            //执行ChildView的绘制
            childView.layout(mPainterPosX + margins.leftMargin, mPainterPosY + margins.topMargin, mPainterPosX + margins.leftMargin + width, mPainterPosY + margins.topMargin + height);

            mPainterPosX += width + margins.leftMargin + margins.rightMargin;
        }
    }

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


    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }
    }

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

2,使用修改


<?xml version="1.0" encoding="utf-8"?>
<com.future.layoutdemo.view.Layout2View xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.future.layoutdemo.MainActivity">

    <View
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_margin="20dp"
        android:background="@color/colorAccent" />

    <View
        android:layout_width="180dp"
        android:layout_height="130dp"
        android:background="@color/colorPrimaryDark" />


    <View
        android:layout_width="30dp"
        android:layout_height="80dp"
        android:background="@color/colorPrimary" />


    <View
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="10dp"
        android:background="@color/colorAccent" />

    <View
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:background="@color/colorPrimary" />

</com.future.layoutdemo.view.Layout2View>



布局中添加margin值,展示效果如下:




源码传送门




慢慢的都会成长起来的,不管经历什么,不管有多少伤痛,你都能习惯的展示云淡风轻的微笑。
天塌下来,塞满食物的嘴里,再加一点鸡腿!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

壹叁零壹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值