Android之自定义流式布局FlowLayout

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android之自定义流式布局FlowLayout相关的知识,希望对你有一定的参考价值。

流式布局常常用于“热门标签”中,大概功能就是将所有的子View一行一行的排列,如果一行中剩下的空间不足以盛放下一个子View,则换到另一行继续排列。这样做的好处是不需要在主线程中自己麻烦定义控件的位置,只需要把生成的控件放到容器中,容器自己会自动排列。首先来看一下运行结果:

技术分享

android中的自定义容器控件(继承自ViewGroup的控件)都有两个必须实现的方法:onMeasure()和onLayout()
(1)onMeasure:测量子View的宽和高,设置自己的宽和高,根据自View的布局文件,为子View设置测量模式和测量值 测量 = 测量模式 + 测量值
测量模式有三种:
-1:EXACTLY:100dip、match_parent等具体的情况
-2:AT_MOST:wrap_content  
-3:UNSPECIFIED:用于ScrollView等子控件想要多大就多大的情况,很少见

(2)onLayout:设置子View的位置

(3)控件的LayoutParams:View.getLayoutParams()返回的是控件所在父布局的LayoutParams
例如:一个控件在一个LinearLayout中,则该控件的getLayoutParams()方法返回的是LinearLayout.LayoutParams
本例中的FlowLayout使用的是Android系统内置的MarginLayoutParams

下面是自定义控件FlowLayout的代码,代码注释很详细,在这里就不唠叨了:

技术分享
  1 public class FlowLayout extends ViewGroup {
  2     /**
  3      * ViewGroup的绘制过程:onMeasure --> onLayout
  4      * onMeasure、onLayout和MeasureSpec解析参考:http://blog.csdn.net/yuliyige/article/details/12656751
  5      * onMeasure有两方面的额作用:①获得ViewGroup和子View的宽和高 ②设置子ViewGroup的宽和高(只是获得宽高并且存储在它各自的View中,这时ViewGroup根本就不知道子View的大小)
  6      * onLayout负责设置子View的大小和位置(告诉ViewGroup子View在它里面中的大小和应该放在哪里)
  7      */
  8     private List<List<View>> allViews = new ArrayList<List<View>>(); // 一行一行的存储所有的View
  9     private List<Integer> lineHeights = new ArrayList<Integer>(); // 存储每一行的高度
 10 
 11     // 我们让一个参数的构造方法调用两个参数的构造方法,两个参数的构造方法调用三个参数的构造方法,这样,不管我们用哪个构造方法产生实例,最终的调用代码都是一致的
 12     // 当我们在布局文件中书写了控件的属性,并且使用了自定义属性时,就可以使用这个构造方法
 13     public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
 14         super(context, attrs, defStyle);
 15     }
 16 
 17     // 当我们在布局文件中书写了控件的属性,但没有使用自定义属性时,就可以调用这个构造方法
 18     public FlowLayout(Context context, AttributeSet attrs) {
 19         this(context, attrs, 0);
 20     }
 21 
 22     // 当我们只需要通过一个上下文来NEW一个控件时,就可以创建这个控件
 23     public FlowLayout(Context context) {
 24         this(context, null);
 25     }
 26 
 27     @Override
 28     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 29         // widthMeasureSpec和heightMeasureSpec中包括了控件的宽和高的测量模式和测量值(测量=测量模式+测量值)
 30         // 获取容器的宽和高的测量模式和测量值(模式包括EXACTLY、AT_MOST、UNSPECIFIED)
 31         int width_size = MeasureSpec.getSize(widthMeasureSpec); // 一个MeasureSpec封装了父布局传递给子布局的布局要求,每个MeasureSpec代表了一组宽度和高度的要求
 32         int width_mode = MeasureSpec.getMode(widthMeasureSpec);
 33         int height_size = MeasureSpec.getSize(heightMeasureSpec);
 34         int height_mode = MeasureSpec.getMode(heightMeasureSpec);
 35         // 如果是wrap_content,那么我们想要得到的值是当前行宽度和历史最长宽度(不超出屏幕)中比较大的一个值
 36         // 分别用来存储如果FlowLayout的宽和高是wrap_content时容器的行宽和行高
 37         int width = 0; // 容器中当前最宽的宽度值
 38         int height = 0; // 容器中的控件所占的高度
 39         // 分别用来存储当前行所占的宽度和高度
 40         int lineWidth = 0;
 41         int lineHeight = 0;
 42         // 布局中元素的个数
 43         int itemCount = getChildCount();
 44         // 循环迭代布局中的每一个子View,为上面的几个变量赋值
 45         for (int i = 0; i < itemCount; i++) {
 46             View child = getChildAt(i);
 47             measureChild(child, widthMeasureSpec, heightMeasureSpec); // 测量子View的宽和高
 48             MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams(); // 得到LayoutParams
 49             // 获取子View占据的宽度和高度(子View本身的宽度/高度 加上子View与左右边/上下边之间的间距)
 50             int childWidth = child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
 51             int childHeight = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
 52             // 如果新加入的字View超出本行剩余的宽度,则换行,同时记录到现在为止容器中最宽的行的宽度
 53             if (childWidth + lineWidth > width_size - getPaddingLeft() - getPaddingRight()) {
 54                 width = Math.max(width, lineWidth); // 记录最宽的行的宽度
 55                 lineWidth = childWidth; // 换到下一行,当前行宽就是行中第一个子View的宽度
 56                 height += lineHeight;
 57                 lineHeight = childHeight; // 换到下一行,当前行高就是行中第一个子View的高度
 58             } else { // 未换行的情况
 59                 lineWidth += childWidth;
 60                 lineHeight = Math.max(lineHeight, childHeight);
 61             }
 62             // (当容器的高度是wrap_content时)到达最后一个控件
 63             // 如果没有下面这段代码,则在容器的高度是wrap_content时最后一个子View所在的一行都不能显示
 64             if (i == itemCount - 1) {
 65                 width = Math.max(width, lineWidth);
 66                 height += lineHeight;
 67             }
 68         }
 69         // 设置容器所占的宽和高(如果容器的宽和高是精确值,我们就使用父控件传入的宽度和高度,否则我们就使用我们自己测量的宽度和高度)
 70         setMeasuredDimension(width_mode == MeasureSpec.EXACTLY ? width_size : width + getPaddingLeft() + getPaddingRight(),
 71                 height_mode == MeasureSpec.EXACTLY ? height_size : height + getPaddingTop() + getPaddingBottom());
 72     }
 73 
 74     @Override
 75     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 76         allViews.clear();
 77         lineHeights.clear();
 78         int width = getWidth(); // 当前ViewGroup的宽度
 79         // 当前行的宽度和高度
 80         int lineWidth = 0;
 81         int lineHeight = 0;
 82         List<View> lineViews = new ArrayList<View>(); // 存储当前行中的子View
 83         int viewCount = getChildCount();
 84         for (int i = 0; i < viewCount; i++) {
 85             View child = getChildAt(i);
 86             MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
 87             // 当前子View的宽度和高度
 88             int childWidth = child.getMeasuredWidth();
 89             int childHeight = child.getMeasuredHeight();
 90             // 如果需要换行
 91             if (childWidth + lineWidth + layoutParams.leftMargin + layoutParams.rightMargin > width - getPaddingLeft()
 92                     - getPaddingRight()) {
 93                 lineHeights.add(lineHeight); // 记录LineHeight
 94                 allViews.add(lineViews); // 记录当前行所有的View
 95                 // 重置行宽和行高
 96                 lineWidth = 0;
 97                 lineHeight = childHeight + layoutParams.topMargin + layoutParams.bottomMargin;
 98                 lineViews = new ArrayList<View>(); // 重置一行中的View集合
 99             }
100             lineWidth += childWidth + layoutParams.leftMargin + layoutParams.rightMargin;
101             lineHeight = Math.max(lineHeight, childHeight + layoutParams.topMargin + layoutParams.bottomMargin);
102             lineViews.add(child);
103         }
104         // 处理最后一行
105         lineHeights.add(lineHeight);
106         allViews.add(lineViews);
107         // 设置子View的位置
108         int left = getPaddingLeft();
109         int top = getPaddingTop();
110         int lineCount = allViews.size();
111         for (int i = 0; i < lineCount; i++) {
112             lineViews = allViews.get(i); // 当前行中的子View
113             lineHeight = lineHeights.get(i);
114             for (int j = 0; j < lineViews.size(); j++) {
115                 View child = lineViews.get(j);
116                 // 不处理不可见的子View
117                 if (child.getVisibility() == View.GONE) {
118                     continue;
119                 }
120                 MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
121                 int childLeft = left + layoutParams.leftMargin;
122                 int childTop = top + layoutParams.topMargin;
123                 int childRight = childLeft + child.getMeasuredWidth();
124                 int childBottom = childTop + child.getMeasuredHeight();
125                 child.layout(childLeft, childTop, childRight, childBottom); // 为子View进行布局
126                 left += child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
127             }
128             left = getPaddingLeft();
129             top += lineHeight;
130         }
131     }
132 }
流式布局FlowLayout

下面是主界面布局代码:

技术分享
 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:background="#ffffffff" >
 6 
 7     <com.view.FlowLayout
 8         android:id="@+id/main_flowlayout"
 9         android:layout_width="fill_parent"
10         android:layout_height="fill_parent"
11         android:background="#DEDEDE"
12         android:padding="5.0dip" >
13     </com.view.FlowLayout>
14 
15 </RelativeLayout>
MainActivity布局代码

下面是主界面的JAVA代码:

技术分享
 1 public class MainActivity extends Activity {
 2     private FlowLayout flowLayout;
 3     private String[] texts = new String[] { "Hello World", "Welcome You Relative", "Android", "LinearLayout",
 4             "RelativeLayout", "Hello Sky", "Welcome", "Android", "LinearLayout", "RelativeLayout", "Hello My Name is XGZ",
 5             "Welcome", "Android Application", "LinearLayout is Very Good", "RelativeLayout", "MyNameIsXXX" };
 6 
 7     @Override
 8     protected void onCreate(Bundle savedInstanceState) {
 9         super.onCreate(savedInstanceState);
10         setContentView(R.layout.activity_main);
11         flowLayout = (FlowLayout) findViewById(R.id.main_flowlayout);
12         initData();
13     }
14 
15     private void initData() {
16         LayoutInflater inflater = LayoutInflater.from(this);
17         for (int i = 0; i < texts.length; i++) {
18             TextView subView = (TextView) inflater.inflate(R.layout.sideworks_textview, flowLayout, false);
19             MarginLayoutParams layoutParams = new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT,
20                     MarginLayoutParams.WRAP_CONTENT);
21             subView.setText(texts[i]);
22             flowLayout.addView(subView, layoutParams);
23         }
24     }
25 }
MainActivity代码

下面是两个辅助XML文件中的代码:

技术分享
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <TextView xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="wrap_content"
 4     android:layout_height="wrap_content"
 5     android:layout_margin="5.0dip"
 6     android:background="@drawable/main_text_shape"
 7     android:text="@string/app_name"
 8     android:textColor="#ff000000"
 9     android:textSize="15.0sp" >
10 
11 </TextView>
子控件布局代码
技术分享
 1 <?xml version="1.0" encoding="utf-8"?>
 2 <shape xmlns:android="http://schemas.android.com/apk/res/android" >
 3 
 4     <solid android:color="#ffffff" />
 5 
 6     <corners android:radius="30dp" />
 7 
 8     <padding
 9         android:bottom="2.0dip"
10         android:left="10.0dip"
11         android:right="10.0dip"
12         android:top="2.0dip" />
13 
14 </shape>
子控件样式代码

 

以上是关于Android之自定义流式布局FlowLayout的主要内容,如果未能解决你的问题,请参考以下文章

Android FlowLayout 流式布局

自定义流式布局

text Android的流式布局的FlowLayout,一款针对标签的布局

Android自定义FlowLayout,支持多种布局优化--android-flowlayout

Android自定义FlowLayout,支持多种布局优化--android-flowlayout

Android 自定义ViewGroup之实现FlowLayout-标签流容器