Android实现热门标签的流式布局

时间:2022-11-19 15:25:53

一、概述:
在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何
自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出)
类似的自定义布局。下面我们就来详细介绍流式布局的应用特点以及用的的技术点:
1.流式布局的特点以及应用场景
    特点:当上面一行的空间不够容纳新的textview时候,
    才开辟下一行的空间
 原理图:

    Android实现热门标签的流式布局

    场景:主要用于关键词搜索或者热门标签等场景
2.自定义viewgroup,重点重写下面两个方法
    1)、onmeasure:测量子view的宽高,设置自己的宽和高
    2)、onlayout:设置子view的位置
    onmeasure:根据子view的布局文件中属性,来为子view设置测量模式和测量值
    测量=测量模式+测量值;
    测量模式有3种:
    exactly:表示设置了精确的值,一般当childview设置其宽、高为精确值、match_parent时,viewgroup会将其设置为exactly;
    at_most:表示子布局被限制在一个最大值内,一般当childview设置其宽、高为wrap_content时,viewgroup会将其设置为at_most;
    unspecified:表示子布局想要多大就多大,一般出现在aadapterview的item的heightmode中、scrollview的childview的heightmode中;此种模式比较少见。
3.layoutparams
    viewgroup layoutparams :每个 viewgroup 对应一个 layoutparams; 即 viewgroup -> layoutparams
    getlayoutparams 不知道转为哪个对应的layoutparams ,其实很简单,就是如下:
    子view.getlayoutparams 得到的layoutparams对应的就是 子view所在的父控件的layoutparams;
    例如,linearlayout 里面的子view.getlayoutparams ->linearlayout.layoutparams
    所以 咱们的flowlayout 也需要一个layoutparams,由于上面的效果图是子view的 margin,
    所以应该使用marginlayoutparams。即flowlayout->marginlayoutparams
4.最后来看看实现的最终效果图:

 Android实现热门标签的流式布局

二、热门标签的流式布局的实现:
1. 自定义热门标签的viewgroup实现
  根据上面的技术分析,自定义类继承于viewgroup,并重写 onmeasure和onlayout等方法。具体实现代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
<font color="#362e2b"><font style="background-color:rgb(255, 255, 255)"><font face="arial"><font style="font-size:14px">package com.czm.flowlayout;
 
import java.util.arraylist;
import java.util.list;
 
import android.content.context;
import android.util.attributeset;
import android.view.view;
import android.view.viewgroup;
/**
 *
 * @author caizhiming
 * @created on 2015-4-13
 */
public class xcflowlayout extends viewgroup{
 
  //存储所有子view
  private list<list<view>> mallchildviews = new arraylist<>();
  //每一行的高度
  private list<integer> mlineheight = new arraylist<>();
   
  public xcflowlayout(context context) {
    this(context, null);
    // todo auto-generated constructor stub
  }
  public xcflowlayout(context context, attributeset attrs) {
    this(context, attrs, 0);
    // todo auto-generated constructor stub
  }
  public xcflowlayout(context context, attributeset attrs, int defstyle) {
    super(context, attrs, defstyle);
    // todo auto-generated constructor stub
  }
  @override
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
    // todo auto-generated method stub
     
    //父控件传进来的宽度和高度以及对应的测量模式
    int sizewidth = measurespec.getsize(widthmeasurespec);
    int modewidth = measurespec.getmode(widthmeasurespec);
    int sizeheight = measurespec.getsize(heightmeasurespec);
    int modeheight = measurespec.getmode(heightmeasurespec);
     
    //如果当前viewgroup的宽高为wrap_content的情况
    int width = 0;//自己测量的 宽度
    int height = 0;//自己测量的高度
    //记录每一行的宽度和高度
    int linewidth = 0;
    int lineheight = 0;
     
    //获取子view的个数
    int childcount = getchildcount();
    for(int i = 0;i < childcount; i ++){
      view child = getchildat(i);
      //测量子view的宽和高
      measurechild(child, widthmeasurespec, heightmeasurespec);
      //得到layoutparams
      marginlayoutparams lp = (marginlayoutparams) getlayoutparams();
      //子view占据的宽度
      int childwidth = child.getmeasuredwidth() + lp.leftmargin + lp.rightmargin;
      //子view占据的高度
      int childheight = child.getmeasuredheight() + lp.topmargin + lp.bottommargin;
      //换行时候
      if(linewidth + childwidth > sizewidth){
        //对比得到最大的宽度
        width = math.max(width, linewidth);
        //重置linewidth
        linewidth = childwidth;
        //记录行高
        height += lineheight;
        lineheight = childheight;
      }else{//不换行情况
        //叠加行宽
        linewidth += childwidth;
        //得到最大行高
        lineheight = math.max(lineheight, childheight);
      }
      //处理最后一个子view的情况
      if(i == childcount -1){
        width = math.max(width, linewidth);
        height += lineheight;
      }
    }
    //wrap_content
    setmeasureddimension(modewidth == measurespec.exactly ? sizewidth : width,
        modeheight == measurespec.exactly ? sizeheight : height);
    super.onmeasure(widthmeasurespec, heightmeasurespec);
  }
   
  @override
  protected void onlayout(boolean changed, int l, int t, int r, int b) {
    // todo auto-generated method stub
    mallchildviews.clear();
    mlineheight.clear();
    //获取当前viewgroup的宽度
    int width = getwidth();
     
    int linewidth = 0;
    int lineheight = 0;
    //记录当前行的view
    list<view> lineviews = new arraylist<view>();
    int childcount = getchildcount();
    for(int i = 0;i < childcount; i ++){
      view child = getchildat(i);
      marginlayoutparams lp = (marginlayoutparams) child.getlayoutparams();
      int childwidth = child.getmeasuredwidth();
      int childheight = child.getmeasuredheight();
       
      //如果需要换行
      if(childwidth + linewidth + lp.leftmargin + lp.rightmargin > width){
        //记录lineheight
        mlineheight.add(lineheight);
        //记录当前行的views
        mallchildviews.add(lineviews);
        //重置行的宽高
        linewidth = 0;
        lineheight = childheight + lp.topmargin + lp.bottommargin;
        //重置view的集合
        lineviews = new arraylist();
      }
      linewidth += childwidth + lp.leftmargin + lp.rightmargin;
      lineheight = math.max(lineheight, childheight + lp.topmargin + lp.bottommargin);
      lineviews.add(child);
    }
    //处理最后一行
    mlineheight.add(lineheight);
    mallchildviews.add(lineviews);
     
    //设置子view的位置
    int left = 0;
    int top = 0;
    //获取行数
    int linecount = mallchildviews.size();
    for(int i = 0; i < linecount; i ++){
      //当前行的views和高度
      lineviews = mallchildviews.get(i);
      lineheight = mlineheight.get(i);
      for(int j = 0; j < lineviews.size(); j ++){
        view child = lineviews.get(j);
        //判断是否显示
        if(child.getvisibility() == view.gone){
          continue;
        }
        marginlayoutparams lp = (marginlayoutparams) child.getlayoutparams();
        int cleft = left + lp.leftmargin;
        int ctop = top + lp.topmargin;
        int cright = cleft + child.getmeasuredwidth();
        int cbottom = ctop + child.getmeasuredheight();
        //进行子view进行布局
        child.layout(cleft, ctop, cright, cbottom);
        left += child.getmeasuredwidth() + lp.leftmargin + lp.rightmargin;
      }
      left = 0;
      top += lineheight;
    }
     
  }
  /**
   * 与当前viewgroup对应的layoutparams
   */
  @override
  public layoutparams generatelayoutparams(attributeset attrs) {
    // todo auto-generated method stub
     
    return new marginlayoutparams(getcontext(), attrs);
  }
}</font></font></font></font>

2.相关的布局文件:
引用自定义控件:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<font color="#362e2b"><font style="background-color:rgb(255, 255, 255)"><font face="arial"><font style="font-size:14px"><relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/container"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >
 
  <com.czm.flowlayout.xcflowlayout
    android:id="@+id/flowlayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
  </com.czm.flowlayout.xcflowlayout>
 
</relativelayout></font></font></font></font>

textview的样式文件:

?
1
2
3
4
5
6
7
8
9
10
11
12
<font color="#362e2b"><font style="background-color:rgb(255, 255, 255)"><font face="arial"><font style="font-size:14px"><?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
  <solid android:color="#666666" />
  <corners android:radius="10dp" />
  <padding
    android:left="5dp"
    android:right="5dp"
    android:top="5dp"
    android:bottom="5dp"
    />
 
</shape></font></font></font></font>

三、使用该自定义布局控件类
最后,如何使用该自定义的热门标签控件类呢?很简单,请看下面实例代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<font color="#362e2b"><font style="background-color:rgb(255, 255, 255)"><font face="arial"><font style="font-size:14px">package com.czm.flowlayout;
 
import android.app.activity;
import android.graphics.color;
import android.os.bundle;
import android.view.viewgroup.layoutparams;
import android.view.viewgroup.marginlayoutparams;
import android.widget.textview;
/**
 *
 * @author caizhiming
 * @created on 2015-4-13
 */
public class mainactivity extends activity {
 
  private string mnames[] = {
      "welcome","android","textview",
      "apple","jamy","kobe bryant",
      "jordan","layout","viewgroup",
      "margin","padding","text",
      "name","type","search","logcat"
  };
  private xcflowlayout mflowlayout;
  @override
  protected void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.activity_main);
     
    initchildviews();
     
  }
  private void initchildviews() {
    // todo auto-generated method stub
    mflowlayout = (xcflowlayout) findviewbyid(r.id.flowlayout);
    marginlayoutparams lp = new marginlayoutparams(
        layoutparams.wrap_content,layoutparams.wrap_content);
    lp.leftmargin = 5;
    lp.rightmargin = 5;
    lp.topmargin = 5;
    lp.bottommargin = 5;
    for(int i = 0; i < mnames.length; i ++){
      textview view = new textview(this);
      view.settext(mnames[i]);
      view.settextcolor(color.white);
      view.setbackgrounddrawable(getresources().getdrawable(r.drawable.textview_bg));
      mflowlayout.addview(view,lp);
    }
  }
 
}</font></font></font></font>

以上就是本文的全部内容,下面在给大家一个小福利:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// 流式布局 话不多说,比较简单,注释都写的很清楚
 
 
import java.util.arraylist;
import java.util.list;
 
import android.content.context;
import android.util.attributeset;
import android.view.view;
import android.view.viewgroup;
 
/**
 *
 * @author mr.himan
 * @version 1.0<br>
 *     2015年11月4日 11:12:06 <br>
 *     流式布局 设置margintop 和marginleft有效 marginright 暂未实现
 */
public class flowlayout extends viewgroup {
 
 /**
 * 存储所有的子view
 */
 private list<list<view>> mallchildviews = new arraylist<list<view>>();
 
 /**
 * 存储每一行的高度
 */
 private list<integer> mlineheight = new arraylist<integer>();
 
 public flowlayout(context context) {
 this(context, null);
 }
 
 public flowlayout(context context, attributeset attrs) {
 this(context, attrs, 0);
 }
 
 public flowlayout(context context, attributeset attrs, int defstyle) {
 super(context, attrs, defstyle);
 }
 
 @override
 protected void onlayout(boolean changed, int l, int t, int r, int b) {
 mallchildviews.clear();
 mlineheight.clear();
 // 获取当前viewgroup的宽度
 int width = getwidth();
 
 int linewidth = 0;
 int lineheight = 0;
 // 记录当前行的view
 list<view> lineviews = new arraylist<view>();
 int childcount = getchildcount();
 for (int i = 0; i < childcount; i++) {
  view child = getchildat(i);
  marginlayoutparams lp = (marginlayoutparams) child
   .getlayoutparams();
  int childwidth = child.getmeasuredwidth();
  int childheight = child.getmeasuredheight();
 
  // 如果需要换行
  if (childwidth + linewidth + lp.leftmargin + lp.rightmargin > width) {
  // 记录lineheight
  mlineheight.add(lineheight);
  // 记录当前行的views
  mallchildviews.add(lineviews);
  // 重置行的宽高
  linewidth = 0;
  lineheight = childheight + lp.topmargin + lp.bottommargin;
  // 重置view的集合
  lineviews = new arraylist();
  }
  linewidth += childwidth + lp.leftmargin + lp.rightmargin;
  lineheight = math.max(lineheight, childheight + lp.topmargin
   + lp.bottommargin);
  lineviews.add(child);
 }
 // 处理最后一行
 mlineheight.add(lineheight);
 mallchildviews.add(lineviews);
 
 marginlayoutparams params = (marginlayoutparams) this.getlayoutparams();
 
 // 设置子view的位置
 int left = 0;
 // 添加margintop
 int top = 0 + params.topmargin;
 // 获取行数
 int linecount = mallchildviews.size();
 for (int i = 0; i < linecount; i++) {
  // 当前行的views和高度
  lineviews = mallchildviews.get(i);
  lineheight = mlineheight.get(i);
  for (int j = 0; j < lineviews.size(); j++) {
  // 为每一列设置marginleft
  if (j == 0) {
   left = 0 + params.leftmargin;
  }
  view child = lineviews.get(j);
  // 判断是否显示
  if (child.getvisibility() == view.gone) {
   continue;
  }
  marginlayoutparams lp = (marginlayoutparams) child
   .getlayoutparams();
  int cleft = left + lp.leftmargin;
  int ctop = top + lp.topmargin;
  int cright = cleft + child.getmeasuredwidth();
  int cbottom = ctop + child.getmeasuredheight();
  // 进行子view进行布局
  child.layout(cleft, ctop, cright, cbottom);
  left += child.getmeasuredwidth() + lp.leftmargin
   + lp.rightmargin;
  }
  left = 0;
  top += lineheight;
 }
 }
 
 @override
 protected void onmeasure(int widthmeasurespec, int heightmeasurespec) {
 
 // 父控件传进来的宽度和高度以及对应的测量模式
 int sizewidth = measurespec.getsize(widthmeasurespec);
 int modewidth = measurespec.getmode(widthmeasurespec);
 int sizeheight = measurespec.getsize(heightmeasurespec);
 int modeheight = measurespec.getmode(heightmeasurespec);
 
 // 如果当前viewgroup的宽高为wrap_content的情况
 int width = 0;// 自己测量的 宽度
 int height = 0;// 自己测量的高度
 // 记录每一行的宽度和高度
 int linewidth = 0;
 int lineheight = 0;
 
 // 获取子view的个数
 int childcount = getchildcount();
  
 for (int i = 0; i < childcount; i++) {
  view child = getchildat(i);
  // 测量子view的宽和高
  measurechild(child, widthmeasurespec, heightmeasurespec);
  // 得到layoutparams
 
  marginlayoutparams params = (marginlayoutparams) child
   .getlayoutparams();
  // 子view占据的宽度
  int childwidth = child.getmeasuredwidth() + params.leftmargin
   + params.rightmargin;
  // 子view占据的高度
  int childheight = child.getmeasuredheight() + params.bottommargin
   + params.topmargin;
  // 换行时候
  if (linewidth + childwidth > sizewidth) {
  // 对比得到最大的宽度
  width = math.max(width, linewidth);
  // 重置linewidth
  linewidth = childwidth;
  // 记录行高
  height += lineheight;
  lineheight = childheight;
  } else {
  // 不换行情况
  // 叠加行宽
  linewidth += childwidth;
  // 得到最大行高
  lineheight = math.max(lineheight, childheight);
  }
  // 处理最后一个子view的情况
  if (i == childcount - 1) {
  width = math.max(width, linewidth);
  height += lineheight;
  }
 }
 setmeasureddimension(modewidth == measurespec.exactly ? sizewidth
  : width, modeheight == measurespec.exactly ? sizeheight
  : height);
 
 }
 
 /**
 * 与当前viewgroup对应的layoutparams
 */
 @override
 public layoutparams generatelayoutparams(attributeset attrs) {
 return new marginlayoutparams(getcontext(), attrs);
 }
}

希望本文所述对大家学习android实现热门标签的流式布局有所帮助。