自定义ViewGroup

自定义ViewGroup首先需要做什么

	1.要继承ViewGroup
	2.重写四个构造方法
	3.重写 onMeasure(),onLayout()两个方法
	4.在onMeasure()中递归计算子View以及本身的大小
	5.在onLayout()中绘制布局

下面详细讲解每一步骤

继承ViewGroup以及重写构造方法和onMeasure(),onLayout()方法

public class flowlayout extends ViewGroup
{

	//一个参数的构造方法:在new一个实例的时候会调用
    public flowlayout(Context context)
    {
        super(context);

    }

	//两个参数的构造方法:用来解析XML,在运行过程中运用发射进行解析
    public flowlayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);

    }

	//三个参数的构造方法:style主题相关,用来决定你的style主题样式
    public flowlayout(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);

    }

	//四个参数的构造方法:解析XML中的自定义属性 
	public flowlayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);

    }

//这个方法是用来递归测量子View 以及自身的大小的
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
    }

//这个方法是用来绘制子View的位置的
@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
    }
}

怎么递归测量子View以及自己的大小

在这里插入图片描述

一个ViewGroup下面可以有很多View以及ViwGroup,当顶层ViewGroup开始确定自己的大小的时候他会先测量自己的孩子的大小,最终来确定自己的大小(红色表示去测量子View的大小,蓝色表示子View返回给父View自己的结果)

MeasureSpec的组成模式

MeasureSpece是一个整形int值,一共32位,高2位表示测量模式,低30位表示View的大小

	1.高两位表示模式
					UNSPECIFIED:不对View大小做限制,系统使用
					EXACTLY:View的大小是精准大小
					AT_MOST:大小不可超过某个值,入match_Parent,最大不能超过父View
	2.低30位
					表示大小(Size)

进行测量

public class flowlayout extends ViewGroup
{

    private List<List<View>> allLines = new ArrayList<>();//记录所有行的View
    private List<Integer> lineHeightMax = new ArrayList<>();//记录每行的最大高度

    int horizontalSpacing = 10;//两个View之间的空格
    int verticalHeight = 10;//两行之间的空格

//..............
//..............
//省略不重要代码
//..............


private void cleaar()
    {
        allLines.clear();
        lineHeightMax.clear();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        //清理,为什么进行清理呢,因为父View在调用子View的onMeasure()方法的时候可能不止调用一次,所以每次都要清理,记录下来的数据
        cleaar();

        // TODO 整体框架
        // TODO 先测量子View
        // TODO 在测量父View


        List<View> lineViews = new ArrayList<>();//记录当前行所有View
        int lineWidthUsed = 0;//记录当前行使用了多少宽度
        int lineHeight = 0;//记录当前行的高度,当前行所有View中最高的高度,为当前行的高度

        int parentNeedWidth = 0;//父View需要的宽度
        int parentNeedHeight = 0;//父View需要的高度


        //获取父View的高/宽度,以及父View的测量模式
        int widthModel = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightModel = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //获取父View的内边距
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();


        //先测量孩子
        int size = getChildCount();
        for (int i = 0; i < size; i++)
        {
            View childView = getChildAt(i);
            final LayoutParams lp = childView.getLayoutParams();

            //如果子View隐藏且不占用距离,则不计算他的高度和宽度
            if (childView.getVisibility() == View.GONE)
            {
                continue;
            }
            
            /**
             * 这个方法是获得子View的的大小
             * 第一个参数是父View的宽,在父View的基础上我能给子View分配多少宽度
             * 第二个参数是父View的左右内边距,再给子View分配宽度的时候需要去掉父View的内边距
             * 第三个参数是子View的宽度
             */
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height);
           //对孩子进行测量
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            
            //获得子View的高度和宽度
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();


            /**
             * 如果子View的宽度+已使用过得宽度+空格>=父View的宽度----->换行
             *
             */
            if (childWidth + lineWidthUsed + horizontalSpacing >= widthSize)
            {
            	//记录父View需要的宽度和高度
                parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
                parentNeedHeight += lineHeight + verticalHeight;	
                //记录每一行的View和每一行的最大高度
                allLines.add(lineViews);
                lineHeightMax.add(lineHeight);

				//对数据进行清零
                lineViews = new ArrayList<>();
                lineWidthUsed = 0;
                lineHeight = 0;
            }

			//不换行
            lineViews.add(childView);//记住当前行所有的View
            lineWidthUsed += childWidth + horizontalSpacing;//已经使用的宽度,
            lineHeight = Math.max(lineHeight, childHeight); //获取当前行View最大的高度

        }
        //把最后一行加入记录中
        parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
        parentNeedHeight += lineHeight + verticalHeight;
        allLines.add(lineViews);
        lineHeightMax.add(lineHeight);

        //TODO 测量父View自己
        //根据父View自己的模式来判断父View的宽和高
        //如果父View的宽和高都是精准的值,则是他们本身的大小
        //否则就是根据子View的大小进行确定
        int realWidth = widthModel == MeasureSpec.EXACTLY ? widthSize : parentNeedWidth;
        int realHeight = heightModel == MeasureSpec.EXACTLY ? heightSize : parentNeedHeight;

        setMeasuredDimension(realWidth, realHeight);

    }
//..............
//..............
//省略不重要代码
//..............
}

onLayout() 绘制View

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

        int left = getPaddingLeft();
        int top = getPaddingTop();
        int right = 0;
        int bottom = 0;

        int lineCount = allLines.size();
        for (int i = 0; i < lineCount; i++)
        {
            //如果父View有gravity属性,则left,top,right,bottom还需要考虑gravity的属性
            int currentHeight = lineHeightMax.get(i);
            List<View> lineView = allLines.get(i);
            int childCount = lineView.size();
            for (int j = 0; j < childCount; j++)
            {
                View view = lineView.get(j);
                right = left + view.getMeasuredWidth();
                bottom = top + currentHeight;
                view.layout(left, top, right, bottom);

                left = right + horizontalSpacing;
            }
            top += currentHeight + verticalHeight;
            left = getPaddingLeft();
        }
    }

请大家多多指教

完整代码

package com.json.viewgroup;

import android.content.Context;

import android.util.AttributeSet;

import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;


public class flowlayout extends ViewGroup
{

    private List<List<View>> allLines = new ArrayList<>();
    private List<Integer> lineHeightMax = new ArrayList<>();

    int horizontalSpacing = 10;//两个View之间的空格
    int verticalHeight = 10;//两行之间的空格

    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);

    }

    public flowlayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
    {
        super(context, attrs, defStyleAttr, defStyleRes);

    }


    private void cleaar()
    {
        allLines.clear();
        lineHeightMax.clear();

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        //清理,为什么进行清理呢,因为父View在调用子View的onMeasure()方法的时候可能不止调用一次,所以每次都要清理,记录下来的数据
        cleaar();
        // TODO 整体框架
        // TODO 先测量子View
        // TODO 在测量父View
        List<View> lineViews = new ArrayList<>();//记录当前行所有View
        int lineWidthUsed = 0;//记录当前行使用了多少宽度
        int lineHeight = 0;//记录当前行的高度

        int parentNeedWidth = 0;//父View需要的宽度
        int parentNeedHeight = 0;//父View需要的高度


        //获取父View的高/宽度,以及父View的测量模式
        int widthModel = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightModel = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //获取父View的内边距
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();


        //先度量孩子
        int size = getChildCount();
        for (int i = 0; i < size; i++)
        {
            View childView = getChildAt(i);
            final LayoutParams lp = childView.getLayoutParams();

            //如果子View隐藏且不占用距离,则不计算他的高度和宽度
            if (childView.getVisibility() == View.GONE)
            {
                continue;
            }


            /**
             * 这个方法是获得子View的的大小
             * 第一个参数是父View的宽,在父View的基础上我能给子View分配多少宽度
             * 第二个参数是父View的左右内边距,再给子View分配宽度的时候需要去掉父View的内边距
             * 第三个参数是子View的宽度
             */
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height);
            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);


            //获得子View的高度和宽度
            int childWidth = childView.getMeasuredWidth();
            int childHeight = childView.getMeasuredHeight();


            /**
             * 如果子View的宽度+已使用过得宽度+空格>=父View的宽度----->换行
             *
             */
            if (childWidth + lineWidthUsed + horizontalSpacing >= widthSize)
            {
                parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
                parentNeedHeight += lineHeight + verticalHeight;


                allLines.add(lineViews);
                lineHeightMax.add(lineHeight);

                lineViews = new ArrayList<>();
                lineWidthUsed = 0;
                lineHeight = 0;


            }

            lineViews.add(childView);
            lineWidthUsed += childWidth + horizontalSpacing;
            lineHeight = Math.max(lineHeight, childHeight); //获取当前行最大的高度

        }
        parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
        parentNeedHeight += lineHeight + verticalHeight;
        allLines.add(lineViews);
        lineHeightMax.add(lineHeight);

        //TODO 测量父View自己
        //根据父View自己的模式来判断父View的宽和高
        //如果父View的宽和高都是精准的值,则是他们本身的大小
        //否则就是根据子View的大小进行确定
        int realWidth = widthModel == MeasureSpec.EXACTLY ? widthSize : parentNeedWidth;
        int realHeight = heightModel == MeasureSpec.EXACTLY ? heightSize : parentNeedHeight;

        setMeasuredDimension(realWidth, realHeight);

    }


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

        int left = getPaddingLeft();
        int top = getPaddingTop();
        int right = 0;
        int bottom = 0;

        int lineCount = allLines.size();
        for (int i = 0; i < lineCount; i++)
        {


            //如果父View有gravity属性,则left,top,right,bottom还需要考虑gravity的属性

            int currentHeight = lineHeightMax.get(i);
            List<View> lineView = allLines.get(i);
            int childCount = lineView.size();
            for (int j = 0; j < childCount; j++)
            {
                View view = lineView.get(j);
                right = left + view.getMeasuredWidth();
                bottom = top + currentHeight;
                view.layout(left, top, right, bottom);

                left = right + horizontalSpacing;
            }
            top += currentHeight + verticalHeight;
            left = getPaddingLeft();

        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

~搬~运~工~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值