Android实现流式布局的⼏种⽅式和FlexboxLayout的使⽤
_qq_366999。。。
⾃定义流式布局FlowLayout(可指定显⽰⾏数)
FlowLayout
/**
* Cerated by xiaoyehai
* Create date : 2021/1/11 15:02
* description :⾃定义流式布局(可指定显⽰⾏数)
*/
public class FlowLayout extends LinearLayout {
/**
* 默认间距
*/
public static final int DEFAULT_SPACING = AppUtils.dp2px(10);
/**
* 横向间隔html的flex布局
*/
private int mHorizontalSpacing = DEFAULT_SPACING;
/**
* 纵向间隔
*/
private int mVerticalSpacing = DEFAULT_SPACING;
/
**
* 是否需要布局,只⽤于第⼀次
*/
boolean mNeedLayout = true;
/**
* 每⼀⾏是否平分空间:将剩余空间平均分配给每个⼦控件
*/
private boolean isAverageInRow = false;
/**
* 当前⾏已⽤的宽度,由⼦View宽度加上横向间隔
*/
private int mUsedWidth = 0;
/**
* ⾏的集合
*/
private final List<Line> mLines = new ArrayList<>();
/**
* ⾏对象
*/
private Line mLine = null;
/**
* 最⼤的⾏数
*/
private int mMaxLinesCount = Integer.MAX_VALUE;
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/
**
* 设置横向间隔
*
* @param spacing
*/
public void setHorizontalSpacing(int spacing) {
if (mHorizontalSpacing != spacing) {
mHorizontalSpacing = spacing;
requestLayoutInner();
}
}
/
**
* 设置纵向间隔
*
* @param spacing
*/
public void setVerticalSpacing(int spacing) {
if (mVerticalSpacing != spacing) {
mVerticalSpacing = spacing;
requestLayoutInner();
}
}
/
**
* 设置最⼤⾏数
*
* @param count
*/
public void setMaxLines(int count) {
if (mMaxLinesCount != count) {
mMaxLinesCount = count;
requestLayoutInner();
}
}
/
**
* 每⼀⾏是否平分空间
*
* @param isAverageInRow
*/
public void setIsAverageInRow(boolean isAverageInRow) {
if (isAverageInRow != isAverageInRow) {
this.isAverageInRow = isAverageInRow;
requestLayoutInner();
}
}
private void requestLayoutInner() {
AppUtils.runOnUIThread(new Runnable() {
@Override
public void run() {
requestLayout();
}
});
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取⾃定义控件宽度
int sizeWidth = Size(widthMeasureSpec) - getPaddingRight() - getPaddingLeft();
//获取⾃定义控件⾼度
int sizeHeight = Size(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
//获取⾃定义控件的宽⾼测量模式
int modeWidth = Mode(widthMeasureSpec);
int modeHeight = Mode(heightMeasureSpec);
restoreLine();// 还原数据,以便重新记录
//获取⼦控件数量
final int count = getChildCount();
//测量每个⼦控件的⼤⼩,决定什么时候需要换⾏
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (Visibility() == GONE) {
continue;
}
//如果⽗控件是确定模式,⼦控件就包裹内容,否则⼦控件模式和⽗控件⼀样
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(sizeWidth,
modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth);
int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(sizeHeight,
modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight);
//测量⼦控件
/
/如果当前⾏对象为空,初始化⼀个⾏对象
if (mLine == null) {
mLine = new Line();
}
//获取⼦控件宽度
int childWidth = MeasuredWidth();
mUsedWidth += childWidth;//  //当前已使⽤宽度增加⼀个⼦控件
//是否超出边界
if (mUsedWidth <= sizeWidth) {
//没有超出边界
mLine.addView(child);// //给当前⾏添加⼀个⼦控件
mUsedWidth += mHorizontalSpacing;// 加上间隔
if (mUsedWidth >= sizeWidth) {
//增加⽔平间距后,超出边界,需要换⾏
if (!newLine()) {
//创建⾏失败,表⽰已经100⾏,不能在创建了,结束循环,不再添加
break;
}
}
} else {
///超出边界
if (ViewCount() == 0) {
/
/1.当前没有控件,⼀添加控件就超出边界(⼦控件很长)
mLine.addView(child);//强制添加到当前⾏
if (!newLine()) {
// 换⾏
break;
}
} else {
//2.当前有控件,⼀添加控件就超出边界
//先还⾏,再添加
if (!newLine()) {
// 换⾏
break;
}
// 在新的⼀⾏,不管是否超过长度,先加上去,因为这⼀⾏⼀个child都没有,所以必须满⾜每⾏⾄少有⼀个child
mLine.addView(child);
mUsedWidth += childWidth + mHorizontalSpacing;
}
}
}
//保存最后⼀⾏到集合
if (mLine != null && ViewCount() > 0 && !ains(mLine)) {
/
/ 由于前⾯采⽤判断长度是否超过最⼤宽度来决定是否换⾏,则最后⼀⾏可能因为还没达到最⼤宽度,所以需要验证后加⼊集合中
mLines.add(mLine);
}
//控件整体宽度
int totalWidth = Size(widthMeasureSpec);
// 控件整体⾼度
int totalHeight = 0;
final int linesCount = mLines.size();
for (int i = 0; i < linesCount; i++) {
// 加上所有⾏的⾼度
totalHeight += (i).mHeight;
}
totalHeight += mVerticalSpacing * (linesCount - 1);// 加上所有间隔的⾼度
totalHeight += getPaddingTop() + getPaddingBottom();// 加上padding
//根据最新宽⾼测量整体布局的⼤⼩
// 设置布局的宽⾼,宽度直接采⽤⽗view传递过来的最⼤宽度,⽽不⽤考虑⼦view是否填满宽度,因为该布局的特性就是填满⼀⾏后,再换⾏        // ⾼度根据设置的模式来决定采⽤所有⼦View的⾼度之和还是采⽤⽗view传递过来的⾼度
setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (!mNeedLayout || changed) {
// 没有发⽣改变就不重新布局
mNeedLayout = false;
int left = getPaddingLeft();// 获取最初的左上点
int top = getPaddingTop();
//遍历所以⾏对象,设置每⾏位置
final int linesCount = mLines.size();
for (int i = 0; i < linesCount; i++) {
final Line oneLine = (i);
oneLine.layoutView(left, top);// 布局每⼀⾏
top += oneLine.mHeight + mVerticalSpacing;// 更新top值,为下⼀⾏的top赋值
}
}
}
/**
* 还原所有数据
*/
private void restoreLine() {
mLines.clear();
mLine = new Line();
mUsedWidth = 0;
}
/**
* 换⾏⽅法
*/
private boolean newLine() {
mLines.add(mLine); //把上⼀⾏添加到集合
if (mLines.size() < mMaxLinesCount) {
//如果可以继续添加⾏
mLine = new Line();
mUsedWidth = 0; //宽度清零
return true;
}
return false;
}
// ==========================================================================    // Inner/Nested Classes
// ==========================================================================
/**
* 代表着⼀⾏,封装了⼀⾏所占⾼度,该⾏⼦View的集合,以及所有View的宽度总和
*/
class Line {
int mWidth = 0;// 该⾏中所有的⼦View累加的宽度
int mHeight = 0;// 该⾏中所有的⼦View中⾼度最⾼的那个⼦View的⾼度
/**
* ⼀⾏⼦控件的集合
*/
List<View> views = new ArrayList<View>();
/**
* 添加⼀个⼦控件

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。