Android中的流式布局也就是常说的瀑布流很是常见,不仅在很多项目中都能见到,而且面试中也有很多面试官问道,那么什么是流式布局呢?简单来说就是如果当前行的剩余宽度不足以摆放下一个控件的时候,则自动将控件填充到下一行中,如一些关键字的标签,热搜词列表等位置出现的比较多。而且控件的类型可以摆放不同的,如一行中可以放置TextView,或者是ImageView,布局应该根据行中最大宽度来自动确定自己的每一行的宽度。

  本次的Demo我们要实现如下的效果:

  其实思路还是比较简单容易理顺的,看代码的前几遍可能会感觉到懵逼,但是看过几遍就会有种"哦,就是这么回事。"的感觉了。先来说下我的思路吧:

  因为是流式布局么,布局布局,当然要继承自ViewGroup了,而且本次Demo中没有用到什么自定义属性,所以简单了不少。老样子,自定义View,肯定先是要自定义一个类,我们起名为FlowLayout,名字是不是很贴切?按照惯例,因为要在xml文件中使用,所以重写其一个参数的和两个参数的构造函数。然后:

  重写onMeasure(),在onMeasure()中计算出父布局和子控件的尺寸(还要计算出什么时候一行摆放不下的时候该换行,这部分是关键部分)

  我们定义了两个个全局变量List<List<View>> mAllChildViews和List<Integer> mLineHeight用来在onLayout中使用,前者是用来存放所有的子控件的集合,里层的List集合用来存放每一行的子控件;后者是用来存放每一行中的最大高度的那一个子控件。

  重写onLayout()方法,在onLayout()方法中先要根据控件的宽高和是否有外边距等来确定每行的childView和每行的最大行高,并将其填充到集合中,然后再遍历mAllChildViews集合,绘制每行的childView,这时候就不需要在判断是否需要换行了!因为之前的操作中我们已经确定了哪些childView是属于一行中的。最后,我们可能会在xml文件中使用margin属性,所以要重写三个generateLayoutParams方法,并返回一个MarginLayoutParams对象。下面我们来看一下关键代码:

  

 package com.example.customviewgroup;

 import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup; import java.util.ArrayList;
import java.util.List; /**
* 流式布局
*/
public class FlowLayout extends ViewGroup {
//存储所有行的view视图 按行记录view 每行的view存储到list集合中
private List<List<View>> mAllChildViews = new ArrayList<>();
//记录每行的最大高度
private List<Integer> mLineHeight = new ArrayList<>(); public FlowLayout(Context context) {
super(context);
} public FlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} /**
* 负责子控件的测量模式和大小 根据所有的子控件的设置确定ViewGroup自己的宽度和高度
* 首先得到父容器传入的测量模式和宽高的计算值 循环所有的子view 调用measureChild对
* 所有的子view进行测量 然后根据所有的子view测量得出宽度和高度 如果ViewGroup设置为
* warp_content时 ViewGroup的宽度和子view的总宽度一致 高度和子view的最大的高度一致
* <p/>
* 如果viewgroup设置为match_parent时直接根据父viewgroup传入的宽度和高度进行测量设置
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
// Log.d("tag", "widthSize: "+widthSize);
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //定义一个不断累加的总高度 当布局中高度的设置为warp_content时使用该高度
int totalHeight = 0;
//定义不断累加的变量 存放当前行控件的宽度总和
int lineWidth = 0;
//获取当前行控件中最高的控件的高度总和
int lineHeight = 0;
//获取viewGroup中子控件总个数遍历
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
//测量子控件的宽度和高度
// (因为每个子控件的margin可能不同,所以使用measureChild()方法测量每个子控件
// 若没有特定的属性,使用measureChildren()方法即可)
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
//获取每个子控件的布局参数
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
//获取子控件的实际宽度和高度
int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int chileHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; //如果加入当前的childView超出最大的宽度 到目前为止最大的宽度给width 累加高度 开启新行
if (lineWidth + childWidth > widthSize) {
//开启新行(相当于直接将当前控件摆放到下一行,所以行宽从此childView的宽度开始)
lineWidth = childWidth;
//累加高度
totalHeight += lineHeight;
lineHeight = chileHeight;//开启记录下一行的高度
} else {
//累加lineWidth 取得最大的lineHeight
lineWidth += childWidth;
lineHeight = Math.max(lineHeight, chileHeight);
} // 如果绘制最后一个,因为totalHeight是从0的位置开始的,所以要再加上一个行高
if (i == childCount - 1) {
totalHeight += lineHeight;
} //宽度就设置为与屏幕一致,高度用一个三目运算符,若是高度模式为MeasureSpec.EXACTLY,则与屏幕一致,否则为我们得到的总高度
setMeasuredDimension(widthSize,
(heightMode == MeasureSpec.EXACTLY) ? heightSize : totalHeight);
}
} @Override
protected LayoutParams generateDefaultLayoutParams() {
return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
} @Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
} @Override
protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
} /**
* 完成对viewgroup中所有childview的位置的指定
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//先把集合中的内容全部清除掉,然后再加载本次绘制内容(这个地方不太清楚理解的对不对)
mAllChildViews.clear();
mLineHeight.clear();
Log.d("onLayout", "mAllChildViews: " + mAllChildViews);
Log.d("onLayout", "mLineHeight: "+mLineHeight);
//获取当前view的宽度(也就是父布局宽度)
int layoutWidth = getWidth();
// Log.d("tag", "layoutWidth: "+layoutWidth);
//声明变量 定义每行的宽度和高度
int lineWidth = 0;
int lineHeight = 0;
//声明list集合存储每一行中所有的childView
List<View> lineViews = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
//如果需要换行
if (lineWidth + childWidth > layoutWidth) {
//记录这一行中所有的view以及最大高度
mLineHeight.add(lineHeight);
//将每一行的childview视图存储到集合中 然后开启新的arraylist记录下一行
mAllChildViews.add(lineViews);
//重置行宽度(这里说行宽其实有点不准确,lineWidth其实指的是绘制childView的左上角的点的横坐标)
lineWidth = 0;
//开启新的一行,则重新new一个来记录行View的集合
lineViews = new ArrayList<>();
}
//如果不需要换行 则累加行宽,找出本行最大行高
lineWidth += childWidth;
//lineHeight其实指的是childView的左上角的点的纵坐标
lineHeight = Math.max(lineHeight, childHeight);
//不换行则添加childView到行集合中
lineViews.add(childView);
} mLineHeight.add(lineHeight);
mAllChildViews.add(lineViews); //声明绘制的起点坐标
int mLeft = 0;
int mTop = 0;
int lineNums = mAllChildViews.size();//获取总行数
for (int i = 0; i < lineNums; i++) {
lineViews = mAllChildViews.get(i);//获取每一行中所有的view
lineHeight = mLineHeight.get(i);//当前行的最大高度
for (int j = 0; j < lineViews.size(); j++) {
View childView = lineViews.get(j);
MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
//计算childView中的left top right bottom
int left_child = mLeft + lp.leftMargin;
int top_child = mTop + lp.topMargin;
int right_child = left_child + childView.getMeasuredWidth();
int botton_child = top_child + childView.getMeasuredHeight();
childView.layout(left_child, top_child, right_child, botton_child);
//指定每个子控件的起始位置
mLeft += childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
}
//下一行开始,重置行宽(x),累加行高(y)
mLeft = 0;
mTop += lineHeight;
}
}
}

其实Android5.0之后可以使用RecycleView来实现,不用这么麻烦。

自定义View(三)--实现一个简单地流式布局的更多相关文章

  1. jq超简单的流式布局,代码简单,容易修改

    1.看看效果吧! 2.html代码index.html <!DOCTYPE html> <html lang="en"> <head> < ...

  2. Android 自定义View修炼-Android中常见的热门标签的流式布局的实现

    一.概述:在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出哈) 类似的 ...

  3. 【Android - 自定义View】之自定义可滚动的流式布局

    首先来介绍一下这个自定义View: (1)这个自定义View的名称叫做 FlowLayout ,继承自ViewGroup类: (2)在这个自定义View中,用户可以放入所有继承自View类的视图,这个 ...

  4. ios开发UI篇—使用纯代码自定义UItableviewcell实现一个简单的微博界面布局

    本文转自 :http://www.cnblogs.com/wendingding/p/3761730.html ios开发UI篇—使用纯代码自定义UItableviewcell实现一个简单的微博界面布 ...

  5. 28 自定义View流式布局

    流式布局每行的行高以本行中最高的元素作为高,如果一个元素放不下到一行时直接到第二行 FlowLayoutView package com.qf.sxy.customview05.widget; imp ...

  6. 响应式编程笔记三:一个简单的HTTP服务器

    # 响应式编程笔记三:一个简单的HTTP服务器 本文我们将继续前面的学习,但将更多的注意力放在用例和编写实际能用的代码上面,而非基本的APIs学习. 我们会看到Reactive是一个有用的抽象 - 对 ...

  7. Android控件进阶-自定义流式布局和热门标签控件

    技术:Android+java   概述 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧,类 ...

  8. 自定义ViewGroup 流式布局

    使用 public class MainActivity extends Activity {     @Override     protected void onCreate(Bundle sav ...

  9. 自定义流式布局:ViewGroup的测量与布局

    目录 1.View生命周期以及View层级 1.1.View生命周期 1.2.View层级 2.View测量与MeasureSpec类 2.1.MeasureSpec类 2.2.父View的限制 :测 ...

随机推荐

  1. java开发--JavaScript

    http://www.cnblogs.com/hongten/archive/2011/03/21/1990121.html JavaScript表单验证电话号码,判断一个输入量是否为电话号码,通过正 ...

  2. JLINK固件,JLINK驱动和JLINK硬件版本之间的关系,以及固件升级方法

    初学者容易在这几个问题上面犯迷糊,这里简单的说说.   1. JLINK硬件版本首先说JLINK的硬件版本有V7,V8和V9,相信这一点大家应该都没问题,那怎么看自己手头的JLINK是哪个硬件版本呢, ...

  3. [iOS]提交App报错ERROR ITMS -90207

    前几天上传项目N多次,都跳出这个问题 甚是头痛,于是乎各种搜索 1. 第三方的info.plist里面Executable file这个要删除(自己的不能删哦) 2.检查一下用来做跳转到第三方应用的设 ...

  4. Linux任务前后台的切换

    Shell支持作用控制,有以下命令实现前后台切换: 1. command& 让进程在后台运行 2. jobs 查看后台运行的进程 3. fg %n 让后台运行的进程n到前台来 4. bg %n ...

  5. HDU4675【GCD of scequence】【组合数学、费马小定理、取模】

    看题解一开始还有地方不理解,果然是我的组合数学思维比较差 然后理解了之后自己敲了一个果断TLE.... 我以后果然还得多练啊 好巧妙的思路啊 知识1: 对于除法取模还需要用到费马小定理: a ^ (p ...

  6. 《Linux内核设计与实现》读书笔记(十一)- 定时器和时间管理【转】

    转自:http://www.cnblogs.com/wang_yb/archive/2013/05/10/3070373.html 系统中有很多与时间相关的程序(比如定期执行的任务,某一时间执行的任务 ...

  7. Linux-0.00运行环境搭建【转】

    转自:http://blog.csdn.net/rosetta/article/details/8933240 这里的Linux-0.00由Linus Torvalds写的Linux最初版本,只是打印 ...

  8. 命令 tar & zip

    安装zip yum install -y unzip zip: tar-c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解 ...

  9. 南阳理工ACM Skiing问题

    描述 Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.Michael想知道载一个区域中最长底 ...

  10. HDU 4638 Group(分组)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4638 题意:给出一个数列,若干询问.每次询问区间[L,R]的最少有多少段?每一段是连续的一段且这段内的 ...