自定义布局之流式布局

时间:2021-11-06 17:02:57

定义

什么是流式布局?就是当一行的末尾不能容纳新的子控件时,就另起一行。适用的场景包括关键字标签,搜索热词等。

实现

1.理解android View的3种测量模式

1)EXACTLY:表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY;
2)AT_MOST:表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST;
3)UNSPECIFIED:表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。

2.继承ViewGroup,重写三个回调函数。

 (1)/**
* 为自定义布局设置一系列布局属性,比如layout_grayvity,layout_weight等
* 在这里设置的布局属性将反馈在view.getLayoutParams()函数的返回结果中。
* 在这个自定义的流式布局里只需要用到MarginLayoutParams
* @param attrs
* @return
*/

@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
 (2)/**
* 计算所有childView的宽度和高度,然后根据ChildView的计算结果,设置自己的宽和高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
}
(3) /**
* 在这个函数中设置子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) {
}

具体代码

package com.example.chen.flowlayoutexample.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

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

/**
* Created by Administrator on 2015/8/14 0014.
*/

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

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

/**
* 为自定义布局设置一系列布局属性,比如layout_grayvity,layout_weight等
* 在这里设置的布局属性将反馈在view.getLayoutParams()函数的返回结果中。
* 在这个自定义的流式布局里只需要用到MarginLayoutParams
* @param attrs
* @return
*/

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

/**
* 计算所有childView的宽度和高度,然后根据ChildView的计算结果,设置自己的宽和高
* @param widthMeasureSpec
* @param heightMeasureSpec
*/

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获得上级容器为其推荐的宽和高
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

//如果是wrap_content情况下,要重新计算宽,高
int width = 0;
int height = 0;

//设置一个变量获取每一行的宽度,高度
int lineWidth = 0;
int lineHeight = 0;
int count = getChildCount();

//遍历每一个子元素
for(int i =0; i < count; i++){
View child = getChildAt(i);
//测量每一个子view的宽和高
measureChild(child,widthMeasureSpec,heightMeasureSpec);
//得到child的布局参数(由generateLayoutParams生成)
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
//计算当前子view实际占据的宽度和高度
int childWidth = child.getMeasuredWidth() + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
int childHeight = child.getMeasuredHeight() + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;

//判断如果加入当前view超过目前给的最大宽度,则进行换行
if(lineWidth + childWidth >sizeWidth){
//将之前计算得的宽与当前行宽进行比较,取最大值
width = Math.max(width,lineWidth);
//当前行宽重新赋值为下一行的第一个子view的宽度
lineWidth = childWidth;
height += lineHeight;
lineHeight = childHeight;
}else{
lineWidth += childWidth;
lineHeight = Math.max(height,childHeight);
}

if(i == count - 1){
width = Math.max(width,lineWidth);
height += childHeight;
}
}

//最后判断测量模式
int measuredWidth = (modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width;
int measuredHeight = (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height;
//设置布局宽和高
setMeasuredDimension(measuredWidth, measuredHeight);
}

//记录多有的子view
private List<List<View>> mAllViews = new ArrayList<>();
//记录每一行的最大高度
private List<Integer> mLineHeight = new ArrayList<>();

/**
* 在这个函数中设置子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) {
mAllViews.clear();

//获取布局的实际宽度
int width = getWidth();

int lineWdith = 0;
int lineHeight = 0;

//存储当前行的views
List<View> lineViews = new ArrayList<>();

for(int i =0; i < getChildCount(); i++){
View child = getChildAt(i);
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();

int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();

//判断需不需要换行
if(childWidth + marginLayoutParams.leftMargin + marginLayoutParams.leftMargin + lineWdith > width){
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);
lineWdith = 0;
lineViews = new ArrayList<>();
}

lineWdith += childWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
lineHeight = Math.max(lineHeight,childHeight + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin);
lineViews.add(child);
}

//记录最后一行
mLineHeight.add(lineHeight);
mAllViews.add(lineViews);

int left = 0;
int top = 0;

//得到总的行数
int lineNums = mAllViews.size();

for(int i = 0 ; i < lineNums; i++) {
//取出每一行的view集合以及行高
lineViews = mAllViews.get(i);
lineHeight = mLineHeight.get(i);

//遍历每一行的所有的view
for (int j = 0; j < lineViews.size(); j++) {
View child = lineViews.get(j);
if (child.getVisibility() == View.GONE) {
continue;
}
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

int lc = left + lp.leftMargin;
int tc = top + lp.topMargin;
int rc = lc + child.getMeasuredWidth();
int bc = tc + child.getMeasuredHeight();

//进行子view的布局
child.layout(lc, tc, rc, bc);

left += lp.leftMargin + child.getMeasuredWidth() + lp.rightMargin;
}
left = 0;
top += lineHeight;
}
}
}

参考

以上实现参考以下链接,加入一点点自己的理解。
http://blog.csdn.net/lmj623565791/article/details/38352503