100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Android 自定义UI 实战 02 流式布局

Android 自定义UI 实战 02 流式布局

时间:2022-01-30 22:21:42

相关推荐

Android 自定义UI 实战 02 流式布局

Android 自定义UI 实战 02 流式布局-- 自定义ViewGroup

第二章 自定义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);}@Overrideprotected void onLayout(boolean b, int i, int i1, int i2, int i3) {}}

在Layout 中使用组件,我这里简单的写了一些数据测试

<?xml version="1.0" encoding="utf-8"?><ScrollView xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.traveleasy.leaningui02.FlowLayout xmlns:android="/apk/res/android"xmlns:app="/apk/res-auto"xmlns:tools="/tools"android:id="@+id/mFlowLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_margin="10dp"tools:ignore="MissingClass"><Buttonandroid:layout_width="wrap_content"android:layout_height="55dp"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="match_parent"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="100dp"android:text="一行一个元素生活不止眼前的苟且,还有诗和远方大法师的打分sad发送到发送到发的发送到发送到第三方阿萨德佛挡杀佛但是" /><Buttonandroid:layout_width="wrap_content"android:layout_height="90dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="match_parent"android:text="11一行一个元素生活不止眼前的苟且,还有诗和远方大法师的打分sad发送到发送到发的发送到发送到第三方阿萨德佛挡杀佛但是" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="45dp"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="75dp"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="60dp"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="100dp"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="85dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="65dp"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="100dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="75dp"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="80dp"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="300dp"android:text="生活不止眼前的苟且,还有诗和远方" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="小麻小儿郎呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello hi ..." /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="你是谁呀" /><Buttonandroid:layout_width="wrap_content"android:layout_height="65dp"android:layout_gravity="bottom"android:text="人在他在,塔亡人亡" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="生活不止眼前的苟且,还有诗和远方1" /><Buttonandroid:layout_width="wrap_content"android:layout_height="250dp"android:text="发电房" /><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="这是结束" /></com.traveleasy.leaningui02.FlowLayout></ScrollView>

2、初始化存储

初始化三个数组,用于存储行数据,所有的行数据,行高

// 每一行的viewprivate 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的属性

源码的解释,都在代码中,如下:

@Overrideprotected 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;}// 当前行添加子viewmLineViews.add(child);// 已有行宽,添加当前 子 view 的宽lineWidth += childWidth;// 获取行中最高的子 ViewlineHeight = Math.max(lineHeight, childHeight);}// 保存尺寸给后面使用setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowLayoutWidth,heightNode == MeasureSpec.EXACTLY ? heightSize : flowLayoutHeight);}

4、布局处理

@Overrideprotected 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();// 处理每一行中的viewfor (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();//布局子viewchild.layout(left, top, right, bottom);currX += child.getMeasuredWidth();}currY += lineHeight;currX = 0;}}

5、初步效果图

6、添加 padding margin 约束

到这就完了吗?当然没有,如图所示,没有约束啊,设置的样式无效。我们想要的效果如下图:

修改完之后的代码如下:

public class FlowLayout extends ViewGroup {// 每一行的viewprivate 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<>();}@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MarginLayoutParams(getContext(), attrs);}@Overrideprotected 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;}// 当前行添加子viewmLineViews.add(child);// 已有行宽,添加当前 子 view 的宽lineWidth += childWidth + childMarginLeftAndRight;// 获取行中最高的子 ViewlineHeight = 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);}@Overrideprotected 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();// 处理每一行中的viewfor (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();//布局子viewchild.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.交互

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。