Android实现热门标签的流式布局
一、概述:
在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何
自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出)
类似的自定义布局。下面我们就来详细介绍流式布局的应用特点以及用的的技术点:
1.流式布局的特点以及应用场景
特点:当上面一行的空间不够容纳新的textview时候,
才开辟下一行的空间
原理图:
场景:主要用于关键词搜索或者热门标签等场景
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.最后来看看实现的最终效果图:
二、热门标签的流式布局的实现:
1. 自定义热门标签的viewgroup实现
根据上面的技术分析,自定义类继承于viewgroup,并重写 onmeasure和onlayout等方法。具体实现代码如下:
<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.相关的布局文件:
引用自定义控件:
<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的样式文件:
<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>
三、使用该自定义布局控件类
最后,如何使用该自定义的热门标签控件类呢?很简单,请看下面实例代码:
<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>
以上就是本文的全部内容,下面在给大家一个小福利:
// 流式布局 话不多说,比较简单,注释都写的很清楚 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实现热门标签的流式布局有所帮助。