首页 > 代码库 > 自定义流式布局
自定义流式布局
1、概述
何为FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行。有点所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局。Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图:
这些都特别适合使用FlowLayout
2、简单的分析
1、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams.
2、onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可)
3、onLayout中对所有的childView进行布局。
1 package com.gcp; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.util.Log; 6 import android.view.View; 7 import android.view.ViewGroup; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class FlowLayout extends ViewGroup { 13 public FlowLayout(Context context) { 14 this(context, null); 15 } 16 17 public FlowLayout(Context context, AttributeSet attrs) { 18 this(context, attrs, 0); 19 } 20 21 public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { 22 super(context, attrs, defStyleAttr); 23 24 } 25 26 //能够设置当前布局的宽度和高度 27 @Override 28 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 29 // super.onMeasure(widthMeasureSpec, heightMeasureSpec); 30 //获取设置的宽高的模式和具体的值 31 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 32 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 33 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 34 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 35 36 //如果用户使用的至多模式,那么使用如下两个变量计算真实的宽高值。 37 int width = 0; 38 int height = 0; 39 40 //每一行的宽度 41 int lineWidth = 0; 42 int lineHeight = 0; 43 44 //获取子视图 45 int childCount = getChildCount(); 46 for (int i = 0; i < childCount; i++) { 47 View childView = getChildAt(i); 48 49 //只有调用了如下的方法,方可计算子视图的测量的宽高 50 measureChild(childView, widthMeasureSpec, heightMeasureSpec); 51 52 //获取子视图的宽高 53 int childWidth = childView.getMeasuredWidth(); 54 int childHeight = childView.getMeasuredHeight(); 55 //要想保证可以获取子视图的边距参数对象,必须重写generateLayoutParams(). 56 MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams(); 57 58 if (lineWidth + childWidth + mp.leftMargin + mp.rightMargin <= widthSize){//不换行 59 60 lineWidth += childWidth + mp.leftMargin + mp.rightMargin; 61 lineHeight = Math.max(lineHeight,childHeight + mp.topMargin + mp.bottomMargin); 62 63 }else{//换行 64 width = Math.max(width,lineWidth); 65 height += lineHeight; 66 67 //重置 68 lineWidth = childWidth + mp.leftMargin + mp.rightMargin; 69 lineHeight = childHeight + mp.topMargin + mp.bottomMargin; 70 } 71 72 //最后一个元素 73 if(i == childCount - 1){ 74 width = Math.max(width,lineWidth); 75 height += lineHeight; 76 } 77 78 } 79 80 Log.e("TAG", "widthSize = " + widthSize + ",heightSize = " + heightSize); 81 Log.e("TAG", "width = " + width + ",height = " + height); 82 83 84 //设置当前流式布局的宽高 85 setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height); 86 } 87 88 //重写的目的:给每一个子视图指定显示的位置:childView.layout(l,t,r,b); 89 private List<List<View>> allViews = new ArrayList<>();//每一行的子视图的集合构成的集合。 90 private List<Integer> allHeights = new ArrayList<>();//每一行的高度构成的集合。 91 @Override 92 protected void onLayout(boolean changed, int l, int t, int r, int b) { 93 //一、给两个集合添加元素。 94 95 //每一行的宽高值 96 int lineWidth = 0; 97 int lineHeight = 0; 98 99 //提供一个集合,保存一行childView 100 List<View> lineList = new ArrayList<>(); 101 //获取布局的宽度 102 int width = this.getMeasuredWidth(); 103 104 int childCount = getChildCount(); 105 for(int i = 0; i < childCount; i++) { 106 View childView = getChildAt(i); 107 //获取视图的测量宽高、边距 108 int childWidth = childView.getMeasuredWidth(); 109 int childHeight = childView.getMeasuredHeight(); 110 111 MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams(); 112 113 if(lineWidth + childWidth + mp.leftMargin + mp.rightMargin <= width){//不换行 114 lineList.add(childView); 115 lineWidth += childWidth + mp.leftMargin + mp.rightMargin; 116 lineHeight = Math.max(lineHeight,childHeight + mp.topMargin + mp.bottomMargin); 117 118 }else{//换行 119 allViews.add(lineList); 120 allHeights.add(lineHeight); 121 122 lineWidth = childWidth + mp.leftMargin + mp.rightMargin; 123 lineHeight = childHeight + mp.topMargin + mp.bottomMargin; 124 lineList = new ArrayList<>(); 125 lineList.add(childView); 126 } 127 128 if(i == childCount - 1){//如果是最后一个元素 129 allViews.add(lineList); 130 allHeights.add(lineHeight); 131 } 132 133 } 134 135 Log.e("TAG", "allViews.size = " + allViews.size() + ",allHeights.size = " + allHeights.size()); 136 137 //二、给每一个子视图指定显示的位置 138 int x = 0; 139 int y = 0; 140 for(int i = 0; i < allViews.size(); i++) {//每遍历一次,对应一行元素 141 List<View> lineViews = allViews.get(i);//取出当前行构成的集合 142 for(int j = 0; j < lineViews.size(); j++) { 143 View childView = lineViews.get(j); 144 int childWidth = childView.getMeasuredWidth(); 145 int childHeight = childView.getMeasuredHeight(); 146 147 MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams(); 148 149 int left = x + mp.leftMargin; 150 int top = y + mp.topMargin; 151 int right = left + childWidth; 152 int bottom = top + childHeight; 153 154 childView.layout(left,top,right,bottom); 155 156 x += childWidth + mp.leftMargin + mp.rightMargin; 157 158 } 159 y += allHeights.get(i); 160 x = 0; 161 } 162 } 163 164 @Override 165 public LayoutParams generateLayoutParams(AttributeSet attrs) { 166 MarginLayoutParams mp = new MarginLayoutParams(getContext(), attrs); 167 return mp; 168 169 } 170 }
3、generateLayoutParams
因为我们只需要支持margin,所以直接使用系统的MarginLayoutParams
@Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); }
4、onMeasure
首先得到其父容器传入的测量模式和宽高的计算值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测量。然后根据所有childView的测量得出的宽和高得到该ViewGroup如果设置为wrap_content时的宽和高。最后根据模式,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高。
5、onLayout
onLayout中完成对所有childView的位置以及大小的指定
6、测试
我准备使用TextView作为我们的标签,所以为其简单写了一点样式:
res/values/styles.xml中:
<style name="text_flag_01"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_margin">4dp</item> <item name="android:background">@drawable/flag_01</item> <item name="android:textColor">#ffffff</item> </style>
flag_01.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" > <solid android:color="#7690A5" > </solid> <corners android:radius="5dp"/> <padding android:bottom="2dp" android:left="10dp" android:right="10dp" android:top="2dp" /> </shape>
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#E1E6F6" android:orientation="vertical" > <com.gcp.FlowLayout android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView style="@style/text_flag_01" android:text="Welcome" /> <TextView style="@style/text_flag_01" android:text="IT工程师" /> <TextView style="@style/text_flag_01" android:text="学习ing" /> <TextView style="@style/text_flag_01" android:text="恋爱ing" /> <TextView style="@style/text_flag_01" android:text="挣钱ing" /> <TextView style="@style/text_flag_01" android:text="努力ing" /> <TextView style="@style/text_flag_01" android:text="I thick i can" /> </com.gcp.FlowLayout> </LinearLayout>
效果图:
参考:
http://blog.csdn.net/jdsjlzx/article/details/45042081?ref=myread
http://blog.csdn.net/lmj623565791/article/details/38352503/
自定义流式布局