[关闭]
@linux1s1s 2015-05-20T09:49:03.000000Z 字数 5494 阅读 2096

Android 自定义FlowLayout

AndroidView


在展开这个话题前说一下啥是FlowLayout,如果对JavaSwing比较熟悉的话一定不会陌生,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行。有点所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局。Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等。

关于这个FlowLayout控件GitHub上有比较不错的源码可以使用,但是会用和会开发这类控件完全是两个层次上的东西,所以有必要看看如何实现FlowLayout

Case

新建类FlowLayout继承自ViewGroup

重写generateLayoutParams方法

重写父类的该方法,返回MarginLayoutParams的实例,这样就为我们的ViewGroup指定了其LayoutParams为MarginLayoutParams。

  1. @Override
  2. public LayoutParams generateLayoutParams(AttributeSet attrs)
  3. {
  4. return new MarginLayoutParams(getContext(), attrs);
  5. }

重写onMeasure方法(protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec))

  1. @Override
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  3. {
  4. /**
  5. * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高
  6. */
  7. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  8. // 获得它的父容器为它设置的测量模式和大小
  9. int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
  10. int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
  11. int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
  12. int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
  13. // 如果是warp_content情况下,记录宽和高
  14. int width = 0;
  15. int height = 0;
  16. /**
  17. * 记录每一行的宽度,width不断取最大宽度
  18. */
  19. int lineWidth = 0;
  20. /**
  21. * 每一行的高度,累加至height
  22. */
  23. int lineHeight = 0;
  24. int cCount = getChildCount();
  25. // 遍历每个子元素
  26. for (int i = 0; i < cCount; i++)
  27. {
  28. View child = getChildAt(i);
  29. // 测量每一个child的宽和高
  30. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  31. // 得到child的lp
  32. MarginLayoutParams lp = (MarginLayoutParams) child
  33. .getLayoutParams();
  34. // 当前子空间实际占据的宽度
  35. int childWidth = child.getMeasuredWidth() + lp.leftMargin
  36. + lp.rightMargin;
  37. // 当前子空间实际占据的高度
  38. int childHeight = child.getMeasuredHeight() + lp.topMargin
  39. + lp.bottomMargin;
  40. /**
  41. * 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
  42. */
  43. if (lineWidth + childWidth > sizeWidth)
  44. {
  45. width = Math.max(lineWidth, childWidth);// 取最大的
  46. lineWidth = childWidth; // 重新开启新行,开始记录
  47. // 叠加当前高度,
  48. height += lineHeight;
  49. // 开启记录下一行的高度
  50. lineHeight = childHeight;
  51. }
  52. else
  53. // 否则累加值lineWidth,lineHeight取最大高度
  54. {
  55. lineWidth += childWidth;
  56. lineHeight = Math.max(lineHeight, childHeight);
  57. }
  58. // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
  59. if (i == cCount - 1)
  60. {
  61. width = Math.max(width, lineWidth);
  62. height += lineHeight;
  63. }
  64. }
  65. setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth
  66. : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
  67. : height);
  68. }

首先得到其父容器传入的测量模式和宽高的计算值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测量。然后根据所有childView的测量得出的宽和高得到该ViewGroup如果设置为wrap_content时的宽和高。最后根据模式,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高。

重写onLayout方法(protected void onLayout(boolean changed, int l, int t, int r, int b))

onLayout中完成对所有childView的位置以及大小的指定

  1. @Override
  2. protected void onLayout(boolean changed, int l, int t, int r, int b)
  3. {
  4. mAllViews.clear();
  5. mLineHeight.clear();
  6. int width = getWidth();
  7. int lineWidth = 0;
  8. int lineHeight = 0;
  9. // 存储每一行所有的childView
  10. List<View> lineViews = new ArrayList<>();
  11. int cCount = getChildCount();
  12. // 遍历所有的孩子
  13. for (int i = 0; i < cCount; i++)
  14. {
  15. View child = getChildAt(i);
  16. MarginLayoutParams lp = (MarginLayoutParams) child
  17. .getLayoutParams();
  18. int childWidth = child.getMeasuredWidth();
  19. int childHeight = child.getMeasuredHeight();
  20. // 如果已经需要换行
  21. if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width)
  22. {
  23. // 记录这一行所有的View以及最大高度
  24. mLineHeight.add(lineHeight);
  25. // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
  26. mAllViews.add(lineViews);
  27. lineWidth = 0;// 重置行宽
  28. lineViews = new ArrayList<>();
  29. }
  30. /**
  31. * 如果不需要换行,则累加
  32. */
  33. lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
  34. lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
  35. + lp.bottomMargin);
  36. lineViews.add(child);
  37. }
  38. // 记录最后一行
  39. mLineHeight.add(lineHeight);
  40. mAllViews.add(lineViews);
  41. int left = 0;
  42. int top = 0;
  43. // 得到总行数
  44. int lineNums = mAllViews.size();
  45. for (int i = 0; i < lineNums; i++)
  46. {
  47. // 每一行的所有的views
  48. lineViews = mAllViews.get(i);
  49. // 当前行的最大高度
  50. lineHeight = mLineHeight.get(i);
  51. // 遍历当前行所有的View
  52. for (int j = 0; j < lineViews.size(); j++)
  53. {
  54. View child = lineViews.get(j);
  55. if (child.getVisibility() == View.GONE)
  56. {
  57. continue;
  58. }
  59. MarginLayoutParams lp = (MarginLayoutParams) child
  60. .getLayoutParams();
  61. //计算childView的left,top,right,bottom
  62. int lc = left + lp.leftMargin;
  63. int tc = top + lp.topMargin;
  64. int rc = lc + child.getMeasuredWidth();
  65. int bc = tc + child.getMeasuredHeight();
  66. child.layout(lc, tc, rc, bc);
  67. left += child.getMeasuredWidth() + lp.rightMargin
  68. + lp.leftMargin;
  69. }
  70. left = 0;
  71. top += lineHeight;
  72. }
  73. }

注意上面代码性能上有问题,在L12 会抛出一个警告:Avoid object allocations during draw/layout operations (preallocate and reuse instead) 关于这个问题读者可以自行解决,这里不再展开来说。

CaseTest

先写个TextView的简单样式

  1. <style name="text_flag_01">
  2. <item name="android:layout_width">wrap_content</item>
  3. <item name="android:layout_height">wrap_content</item>
  4. <item name="android:layout_margin">4dp</item>
  5. <item name="android:background">@drawable/flag_01</item>
  6. <item name="android:textColor">#ffffff</item>
  7. </style>

flag_01.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <shape xmlns:android="http://schemas.android.com/apk/res/android" >
  3. <solid android:color="#7690A5" >
  4. </solid>
  5. <corners android:radius="5dp"/>
  6. <padding
  7. android:bottom="2dp"
  8. android:left="10dp"
  9. android:right="10dp"
  10. android:top="2dp" />
  11. </shape>

UI布局文件

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:background="#E1E6F6"
  6. android:orientation="vertical" >
  7. <com.test.FlowLayout
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content" >
  10. <TextView
  11. style="@style/text_flag_01"
  12. android:text="Welcome" />
  13. <TextView
  14. style="@style/text_flag_01"
  15. android:text="IT工程师" />
  16. <TextView
  17. style="@style/text_flag_01"
  18. android:text="学习ing" />
  19. <TextView
  20. style="@style/text_flag_01"
  21. android:text="恋爱ing" />
  22. <TextView
  23. style="@style/text_flag_01"
  24. android:text="挣钱ing" />
  25. <TextView
  26. style="@style/text_flag_01"
  27. android:text="努力ing" />
  28. <TextView
  29. style="@style/text_flag_01"
  30. android:text="I thick i can" />
  31. </com.test.FlowLayout>
  32. </LinearLayout>

此处输入图片的描述

另外对于文字比较枯燥的话可以看该大牛博主的视频教程

此篇博客转载自CSDN-HongYang的博客,在此向该大牛致敬

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注