转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38352503 ,本文出自【张鸿洋的博客】

1、概述

上一篇已经基本给大家介绍了如何自定义ViewGroup,如果你还不了解,请查看:Android 手把手教您自定ViewGroup ,本篇将使用上篇介绍的方法,给大家带来一个实例:实现FlowLayout,何为FlowLayout,如果对Java的Swing比较熟悉的话一定不会陌生,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行。有点所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局。Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图:

Android 自定义ViewGroup 实战篇 -> 实现FlowLayout-LMLPHPAndroid 自定义ViewGroup 实战篇 -> 实现FlowLayout-LMLPHP

这些都特别适合使用FlowLayout,本篇博客会带领大家自己实现FlowLayout,然后使用我们自己定义的FlowLayout实现上面的标签效果。对了,github已经有了这样FlowLayout,但是我觉得丝毫不影响我们对其的学习,学会使用一个控件和学会写一个控件,我相信大家都明白,授人以鱼不如授人以渔。

2、简单的分析

1、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams.

2、onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可)

3、onLayout中对所有的childView进行布局。

3、generateLayoutParams

因为我们只需要支持margin,所以直接使用系统的MarginLayoutParams

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

4、onMeasure

  1. /**
  2. * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高
  3. */
  4. @Override
  5. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
  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. Log.e(TAG, sizeWidth + "," + sizeHeight);
  14. // 如果是warp_content情况下,记录宽和高
  15. int width = 0;
  16. int height = 0;
  17. /**
  18. * 记录每一行的宽度,width不断取最大宽度
  19. */
  20. int lineWidth = 0;
  21. /**
  22. * 每一行的高度,累加至height
  23. */
  24. int lineHeight = 0;
  25. int cCount = getChildCount();
  26. // 遍历每个子元素
  27. for (int i = 0; i < cCount; i++)
  28. {
  29. View child = getChildAt(i);
  30. // 测量每一个child的宽和高
  31. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  32. // 得到child的lp
  33. MarginLayoutParams lp = (MarginLayoutParams) child
  34. .getLayoutParams();
  35. // 当前子空间实际占据的宽度
  36. int childWidth = child.getMeasuredWidth() + lp.leftMargin
  37. + lp.rightMargin;
  38. // 当前子空间实际占据的高度
  39. int childHeight = child.getMeasuredHeight() + lp.topMargin
  40. + lp.bottomMargin;
  41. /**
  42. * 如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
  43. */
  44. if (lineWidth + childWidth > sizeWidth)
  45. {
  46. width = Math.max(lineWidth, childWidth);// 取最大的
  47. lineWidth = childWidth; // 重新开启新行,开始记录
  48. // 叠加当前高度,
  49. height += lineHeight;
  50. // 开启记录下一行的高度
  51. lineHeight = childHeight;
  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传入的宽和高,否则设置为自己计算的宽和高。

5、onLayout

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

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

allViews的每个Item为每行所有View的List集合。

mLineHeight记录的为每行的最大高度。

23-48行,遍历所有的childView,用于设置allViews的值,以及mLineHeight的值。

57行,根据allViews的长度,遍历所有的行数

67-91行,遍历每一行的中所有的childView,对childView的left , top , right , bottom 进行计算,和定位。

92-93行,重置left和top,准备计算下一行的childView的位置。

好了,到此完成了所有的childView的绘制区域的确定,到此,我们的FlowLayout的代码也结束了~~静下心来看一看是不是也不难~

6、测试

我准备使用TextView作为我们的标签,所以为其简单写了一点样式:

res/values/styles.xml中:

  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>

布局文件:

  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.zhy.zhy_flowlayout02.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.zhy.zhy_flowlayout02.FlowLayout>
  32. </LinearLayout>

效果图:

Android 自定义ViewGroup 实战篇 -&gt; 实现FlowLayout-LMLPHP

是不是还不错,下面继续简单自定义几个背景:

res/drawble/flog_02.xml

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

flag_03.xml

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

布局文件:

  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.zhy.zhy_flowlayout02.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.zhy.zhy_flowlayout02.FlowLayout>
  32. <com.zhy.zhy_flowlayout02.FlowLayout
  33. android:layout_width="fill_parent"
  34. android:layout_height="wrap_content"
  35. android:layout_marginTop="20dp" >
  36. <TextView
  37. style="@style/text_flag_01"
  38. android:background="@drawable/flag_02"
  39. android:text="Welcome"
  40. android:textColor="#888888" />
  41. <TextView
  42. style="@style/text_flag_01"
  43. android:background="@drawable/flag_02"
  44. android:text="IT工程师"
  45. android:textColor="#888888" />
  46. <TextView
  47. style="@style/text_flag_01"
  48. android:background="@drawable/flag_02"
  49. android:text="学习ing"
  50. android:textColor="#888888" />
  51. <TextView
  52. style="@style/text_flag_01"
  53. android:background="@drawable/flag_02"
  54. android:text="恋爱ing"
  55. android:textColor="#888888" />
  56. <TextView
  57. style="@style/text_flag_01"
  58. android:background="@drawable/flag_02"
  59. android:text="挣钱ing"
  60. android:textColor="#888888" />
  61. <TextView
  62. style="@style/text_flag_01"
  63. android:background="@drawable/flag_02"
  64. android:text="努力ing"
  65. android:textColor="#888888" />
  66. <TextView
  67. style="@style/text_flag_01"
  68. android:background="@drawable/flag_02"
  69. android:text="I thick i can"
  70. android:textColor="#888888" />
  71. </com.zhy.zhy_flowlayout02.FlowLayout>
  72. <com.zhy.zhy_flowlayout02.FlowLayout
  73. android:layout_width="fill_parent"
  74. android:layout_height="wrap_content"
  75. android:layout_marginTop="20dp" >
  76. <TextView
  77. style="@style/text_flag_01"
  78. android:background="@drawable/flag_03"
  79. android:text="Welcome"
  80. android:textColor="#43BBE7" />
  81. <TextView
  82. style="@style/text_flag_01"
  83. android:background="@drawable/flag_03"
  84. android:text="IT工程师"
  85. android:textColor="#43BBE7" />
  86. <TextView
  87. style="@style/text_flag_01"
  88. android:background="@drawable/flag_03"
  89. android:text="学习ing"
  90. android:textColor="#43BBE7" />
  91. <TextView
  92. style="@style/text_flag_01"
  93. android:background="@drawable/flag_03"
  94. android:text="恋爱ing"
  95. android:textColor="#43BBE7" />
  96. <TextView
  97. style="@style/text_flag_01"
  98. android:background="@drawable/flag_03"
  99. android:text="挣钱ing"
  100. android:textColor="#43BBE7" />
  101. <TextView
  102. style="@style/text_flag_01"
  103. android:background="@drawable/flag_03"
  104. android:text="努力ing"
  105. android:textColor="#43BBE7" />
  106. <TextView
  107. style="@style/text_flag_01"
  108. android:background="@drawable/flag_03"
  109. android:text="I thick i can"
  110. android:textColor="#43BBE7" />
  111. </com.zhy.zhy_flowlayout02.FlowLayout>
  112. </LinearLayout>

效果图:

Android 自定义ViewGroup 实战篇 -&gt; 实现FlowLayout-LMLPHP

暂不赞~~上面都是match_parent~~下面固定下宽度,实现文章最开始的移动开发热门标签:

flag_04.xml

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

布局文件:

  1. <com.zhy.zhy_flowlayout02.FlowLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="200dp"
  4. android:layout_height="wrap_content"
  5. android:background="#FFFFFF" >
  6. <TextView
  7. style="@style/text_flag_01"
  8. android:background="@drawable/flag_04"
  9. android:text="Welcome"
  10. android:textColor="#323232" />
  11. <TextView
  12. style="@style/text_flag_01"
  13. android:background="@drawable/flag_04"
  14. android:text="IT工程师"
  15. android:textColor="#323232" />
  16. <TextView
  17. style="@style/text_flag_01"
  18. android:background="@drawable/flag_04"
  19. android:text="学习ing"
  20. android:textColor="#323232" />
  21. <TextView
  22. style="@style/text_flag_01"
  23. android:background="@drawable/flag_04"
  24. android:text="恋爱ing"
  25. android:textColor="#323232" />
  26. <TextView
  27. style="@style/text_flag_01"
  28. android:background="@drawable/flag_04"
  29. android:text="挣钱ing"
  30. android:textColor="#323232" />
  31. <TextView
  32. style="@style/text_flag_01"
  33. android:background="@drawable/flag_04"
  34. android:text="努力ing"
  35. android:textColor="#323232" />
  36. <TextView
  37. style="@style/text_flag_01"
  38. android:background="@drawable/flag_04"
  39. android:text="I thick i can"
  40. android:textColor="#323232" />
  41. </com.zhy.zhy_flowlayout02.FlowLayout>

效果图:

Android 自定义ViewGroup 实战篇 -&gt; 实现FlowLayout-LMLPHP

是不是完全相同~~o了~

如果你觉得本篇博客对你有用,那么就留个言或者顶一个~~

05-11 20:30