自定义View---FlowLayout

2021-07-20  本文已影响0人  剧透下

import android.content.Context;

import android.content.res.Resources;

import android.util.AttributeSet;

import android.util.TypedValue;

import android.view.View;

import android.view.ViewGroup;

import java.util.ArrayList;

import java.util.List;

/**

* @ProjectName: MyKotlinSample

* @Package: com.example.mykotlinsample

* @ClassName: TestFlowLayout

* @Description: java类作用描述

* @Author: Zcb

* @CreateDate: 2021/7/19 16:34

* @UpdateRemark: 更新说明

*/

public class TestFlowLayoutextends ViewGroup {

private List>allLines =new ArrayList<>(); // 记录所有的行,一行一行的存储,用于layout

    ListlineHeights =new ArrayList<>(); // 记录每一行的行高,用于layout

    private int mHorizontalSpacing =dp2px(16); //每个item横向间距

    private int mVerticalSpacing =dp2px(8); //每个item横向间距

    public TestFlowLayout(Context context) {

super(context);

    }

public TestFlowLayout(Context context, AttributeSet attrs) {

super(context, attrs);

    }

public TestFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

    }

/**

    * 对UI布局进行测量

    *

    * @param widthMeasureSpec  是当前类TestFlowLayout的父布局所测量后的数值(xml中 TestFlowLayout 的父布局)

    * @param heightMeasureSpec 同理

    */

    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

allLines.clear();

        lineHeights.clear();

        //todo  首先测量子View,然后将子View的宽高交由父类ViewGroup,最后在测量父类View本身

        // 第一步 先测量子View本身

        //获取在ViewGroup中的子View

        int childCount = getChildCount();

        int paddingLeft = getPaddingLeft();

        int paddingRight = getPaddingRight();

        int paddingTop = getPaddingTop();

        int paddingBottom = getPaddingBottom();

        List lineViews =new ArrayList<>(); //保存一行中的所有的view

        int lineWidthUsed =0; //记录当前行已经使用了多宽的size

        int lineHeightUsed =0; // 记录当前一行的行高

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);  //ViewGroup解析的父亲给我的宽度

        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父亲给我的高度

        int parentNeededWidth =0;  // measure过程中,子View要求的父ViewGroup的宽

        int parentNeededHeight =0; // measure过程中,子View要求的父ViewGroup的高

        //遍历所有子view

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

View childView = getChildAt(i);//获取具体的某个子View

            LayoutParams chideLp = childView.getLayoutParams();//获取子View的属性

            int height = chideLp.height;

            int width = chideLp.width;

            /**

            * 根据不同的模式来设置MeasureSpec

*

            * 想要获取子View的宽度,必须在父类中进行,在由不同的模式下specMode来对子类进行一个测量具体值并返回MeasureSpec,

            *

            * 可查看源码 了解子View的宽高属性的由来

            */

            int childWidthMeasureSpec =getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, width);//得到一个MeasureSpec宽

            int childHeightMeasureSpec =getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, height);//得到一个MeasureSpec高

            //再由子View对宽高进行一个测量得出具体的数值, - 相当于保存当前子View信息,并不是真正意义上子View的宽高

            //该值为父类提供宽度和高度参数中的约束信息 //源码解释

            //得到宽高后,确定在父容器的位置?

            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

            lineViews.add(childView);// childView 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局

            //获取子view的度量宽高

            int childMesauredWidth = childView.getMeasuredWidth();

            int childMesauredHeight = childView.getMeasuredHeight();

            //此时已经得到每个具体的子View ,则要进行计算一行最多能放几个View

            lineWidthUsed = childMesauredWidth + lineWidthUsed +mHorizontalSpacing;

            lineHeightUsed = Math.max(lineHeightUsed, childMesauredHeight);//获取该行中 最高的子View

            //问题来了,怎么换行呢?

            //如果需要换行, ----> 将当前的子View与已占用的空间,再加上间距 == 一行的宽度, 但是不能大于父容器的宽度,so...

            if (childMesauredWidth + lineWidthUsed +mHorizontalSpacing > selfWidth) {

//一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来

                allLines.add(lineViews);

                lineHeights.add(lineHeightUsed);

                parentNeededHeight = parentNeededHeight + lineHeightUsed +mVerticalSpacing;

                parentNeededWidth = parentNeededWidth + lineWidthUsed +mHorizontalSpacing;

                //换行后,将记录的 View宽高 都重置

                lineViews =new ArrayList<>();

                lineWidthUsed =0;

                lineHeightUsed =0;

            }

//处理最后一行数据

            if (i == childCount -1) {

allLines.add(lineViews);

                lineHeights.add(lineHeightUsed);

                parentNeededHeight = parentNeededHeight + lineHeightUsed +mVerticalSpacing;

                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed +mHorizontalSpacing);

            }

}

//TODO 测量完子View本身后, 测量父类自己Viewgroup

        //再度量自己,保存

        //根据子View的度量结果,来重新度量自己ViewGroup

        // 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //MeasureSpec.EXACTLY 得到的是精准的数值

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;

        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;

        setMeasuredDimension(realWidth, realHeight);

    }

/**

    * 对子View进行布局

    *

    * @param changed

    * @param l

    * @param t

    * @param r

    * @param b

    */

    @Override

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

int lineCount =allLines.size();//获取所有行数

        int curL = getPaddingLeft();//获取子View距离该子View背景左边的距离

        int curT = getPaddingTop();//同理

        //todo 双for循环遍历 外层for循环总行数,内层for获取每一行的子View

        for (int i =0; i < lineCount; i++) {//遍历每一行

            List lineViews =allLines.get(i);//获取 记录每一行的子View

            int lineHeight =lineHeights.get(i);//获取记录每一行的高度

            for (int j =0; j < lineViews.size(); j++) {//遍历每一行的子view

                View view = lineViews.get(j);//得到具体的子View

                int left = curL;

                int top = curT;

//                int right = left + view.getWidth();//此方法是在onLayout方法之后赋值

//                int bottom = top + view.getHeight();

                /**

                * view.getMeasuredWidth() 则是获取当前子View的具体宽度

                *

                * 此方法是在Measured方法之后赋值

                *

*/

                int right = left + view.getMeasuredWidth();

                int bottom = top + view.getMeasuredHeight();

                view.layout(left, top, right, bottom);//确定子View的位置

                curL = right +mHorizontalSpacing;// 下一个子View的位置

            }

curT = curT + lineHeight +mVerticalSpacing;

            curL = getPaddingLeft();

        }

}

public static int dp2px(int dp) {

return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, Resources.getSystem().getDisplayMetrics());

    }

上一篇下一篇

猜你喜欢

热点阅读