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());
}
网友评论