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 }
下面是主界面布局代码:
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>
下面是主界面的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 }
下面是两个辅助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的主要内容,如果未能解决你的问题,请参考以下文章
text Android的流式布局的FlowLayout,一款针对标签的布局
Android自定义FlowLayout,支持多种布局优化--android-flowlayout