第二章 自定义ViewGroup 流式布局
文章目录 Android 自定义UI 实战 02 流式布局-- 自定义ViewGroup前言1、自定义控件,并在XML 使用2、初始化存储3、测量4、布局处理5、初步效果图6、添加 padding margin 约束 总结
前言
使用纯代码 加 注释的方式,可以更快的理解源码
如果你喜欢,请点个赞,后期会不断的深入讲解
1、自定义控件,并在XML 使用
自定一个Layout 继承 ViewGroup
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
}
}
在Layout 中使用组件,我这里简单的写了一些数据测试
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.traveleasy.leaningui02.FlowLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mFlowLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
tools:ignore="MissingClass">
<Button
android:layout_width="wrap_content"
android:layout_height="55dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="一行一个元素生活不止眼前的苟且,还有诗和远方大法师的打分sad发送到发送到发的发送到发送到第三方阿萨德佛挡杀佛但是" />
<Button
android:layout_width="wrap_content"
android:layout_height="90dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="11一行一个元素生活不止眼前的苟且,还有诗和远方大法师的打分sad发送到发送到发的发送到发送到第三方阿萨德佛挡杀佛但是" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="45dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="75dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="85dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="65dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="75dp"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="80dp"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="300dp"
android:text="生活不止眼前的苟且,还有诗和远方" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小麻小儿郎呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello hi ..." />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你是谁呀" />
<Button
android:layout_width="wrap_content"
android:layout_height="65dp"
android:layout_gravity="bottom"
android:text="人在他在,塔亡人亡" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="生活不止眼前的苟且,还有诗和远方1" />
<Button
android:layout_width="wrap_content"
android:layout_height="250dp"
android:text="发电房" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是结束" />
</com.traveleasy.leaningui02.FlowLayout>
</ScrollView>
2、初始化存储
初始化三个数组,用于存储行数据,所有的行数据,行高
// 每一行的view
private List<View> mLineView;
// 所有的行,一行一行的存储
private List<List<View>> mViews;
// 每一行的高度
private List<Integer> mHeights;
private void init() {
mLineView = new ArrayList<>();
mViews = new ArrayList<>();
mHeights = new ArrayList<>();
}
3、测量
重新 onMeasure()
方法,测量view 的属性
源码的解释,都在代码中,如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取限制的值
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightNode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// 记录当前行的宽度和高度
int lineWidth = 0; //宽度是当前行子view 宽度的和
int lineHeight = 0; //高度是当前行所有子view 中高度的最大值
// 整个流式布局的宽度和高度
int flowLayoutWidth = 0; // 所有行中宽度的最大值
int flowLayoutHeight = 0; // 所有行的高度的累加
init();
// 获取到当前的所有child 数量
int childCount = this.getChildCount();
// 先测量子View,再根据子View 尺寸, 计算自己的
for (int i = 0; i < childCount; i++) {
View child = this.getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 获取到当前子View 的测量高度 和 宽度
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 已经放入的子 view 的宽度 + 准备放入的子 view 的宽度 大于 总宽度,就换行
if (lineWidth + childWidth > widthSize) {
mViews.add(mLineViews);
// 创建 新的一行
mLineViews = new ArrayList<>();
// 所有行中,最宽的一行,作为流式布局的宽
flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);
// 流式布局的高度,为所有行的高度相加
flowLayoutHeight += lineHeight;
mHeights.add(lineHeight);
lineHeight = 0;
lineWidth = 0;
}
// 当前行添加子view
mLineViews.add(child);
// 已有行宽,添加当前 子 view 的宽
lineWidth += childWidth;
// 获取行中最高的子 View
lineHeight = Math.max(lineHeight, childHeight);
}
// 保存尺寸给后面使用
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowLayoutWidth,
heightNode == MeasureSpec.EXACTLY ? heightSize : flowLayoutHeight);
}
4、布局处理
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int currX = 0;
int currY = 0;
int lineCount = mViews.size();
// 处理每一行
for (int i = 0; i < lineCount; i++) {
List<View> lineViews = mViews.get(i);
int lineHeight = mHeights.get(i);
int size = lineViews.size();
// 处理每一行中的view
for (int j = 0; j < size; j++) {
View child = lineViews.get(j);
// 子view 的左上右下
int left = currX;
int top = currY;
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
// 布局子view
child.layout(left, top, right, bottom);
currX += child.getMeasuredWidth();
}
currY += lineHeight;
currX = 0;
}
}
5、初步效果图
6、添加 padding margin 约束
到这就完了吗?当然没有,如图所示,没有约束啊,设置的样式无效。我们想要的效果如下图:
修改完之后的代码如下:
public class FlowLayout extends ViewGroup {
// 每一行的view
private List<View> mLineViews;
// 所有的行,一行一行的存储
private List<List<View>> mViews;
// 每一行的高度
private List<Integer> mHeights;
public FlowLayout(Context context) {
this(context, null);
}
public FlowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mLineViews = new ArrayList<>();
mViews = new ArrayList<>();
mHeights = new ArrayList<>();
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 获取限制的值
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightNode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int paddingLeftAndRight = getPaddingLeft() + getPaddingRight();
int paddingTopAndBottom = getPaddingTop() + getPaddingTop();
// 解决子 view match_parent 无效的问题
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
// 记录当前行的宽度和高度
int lineWidth = 0; //宽度是当前行子view 宽度的和
int lineHeight = 0; //高度是当前行所有子view 中高度的最大值
// 整个流式布局的宽度和高度
int flowLayoutWidth = 0; // 所有行中宽度的最大值
int flowLayoutHeight = 0; // 所有行的高度的累加
init();
// 获取到当前的所有child 数量
int childCount = this.getChildCount();
// 先测量子View,再根据子View 尺寸, 计算自己的
for (int i = 0; i < childCount; i++) {
View child = this.getChildAt(i);
// measureChild(child, widthMeasureSpec, heightMeasureSpec);
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int childMarginLeftAndRight = lp.leftMargin + lp.rightMargin;
int childMarginTopAndBottom = lp.topMargin + lp.bottomMargin;
// 获取到当前子View 的测量高度 和 宽度
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
// 已经放入的子 view 的宽度 + 准备放入的子 view 的宽度 大于 总宽度,就换行
if (lineWidth + childWidth + childMarginLeftAndRight + paddingLeftAndRight > widthSize) {
mViews.add(mLineViews);
// 创建 新的一行
mLineViews = new ArrayList<>();
// 所有行中,最宽的一行,作为流式布局的宽
flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);
// 流式布局的高度,为所有行的高度相加
flowLayoutHeight += lineHeight;
mHeights.add(lineHeight);
lineHeight = 0;
lineWidth = 0;
}
// 当前行添加子view
mLineViews.add(child);
// 已有行宽,添加当前 子 view 的宽
lineWidth += childWidth + childMarginLeftAndRight;
// 获取行中最高的子 View
lineHeight = Math.max(lineHeight, childHeight + childMarginTopAndBottom);
// 处理最后一行的显示
if (i == childCount - 1){
flowLayoutHeight += lineHeight;
flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);
mHeights.add(lineHeight);
mViews.add(mLineViews);
}
}
flowLayoutWidth += paddingLeftAndRight;
flowLayoutHeight += paddingTopAndBottom;
// 保存尺寸给后面使用
setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowLayoutWidth,
heightNode == MeasureSpec.EXACTLY ? heightSize : flowLayoutHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int currX = getPaddingLeft();
int currY = getPaddingTop();
int lineCount = mViews.size();
// 处理每一行
for (int i = 0; i < lineCount; i++) {
List<View> lineViews = mViews.get(i);
int lineHeight = mHeights.get(i);
int size = lineViews.size();
// 处理每一行中的view
for (int j = 0; j < size; j++) {
View child = lineViews.get(j);
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 子view 的左上右下
int left = currX + lp.leftMargin;
int top = currY + lp.topMargin;
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
// 布局子view
child.layout(left, top, right, bottom);
currX += right + lp.rightMargin;
}
currY += lineHeight;
currX = getPaddingLeft();
}
}
}
总结
流式布局 – 自定义ViewGroup
1.自定义属性,以及xml中使用
2.测量 — 先测量子View,再根据子View尺寸,计算自己的,保存尺寸给后面用
3.布局 onLayout — 根据自己的规则确定child 的位置
4.绘制 – onDraw(正常不会调用) 重写dispatchDraw – 一般不会用
5.交互
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)