一、概述:
在日常的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等方法。具体核心实现代码如下:packagecom.czm.flowlayout;
importjava.util.ArrayList;
importjava.util.List;
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.view.View;
importandroid.view.ViewGroup;
/**
*
*@authorcaizhiming
*
*/
publicclassXCFlowLayoutextendsViewGroup{
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
//TODOAuto-generatedmethodstub
//父控件传进来的宽度和高度以及对应的测量模式
intsizeWidth=MeasureSpec.getSize(widthMeasureSpec);
intmodeWidth=MeasureSpec.getMode(widthMeasureSpec);
intsizeHeight=MeasureSpec.getSize(heightMeasureSpec);
intmodeHeight=MeasureSpec.getMode(heightMeasureSpec);
//如果当前ViewGroup的宽高为wrap_content的情况
intwidth=0;//自己测量的宽度
intheight=0;//自己测量的高度
//记录每一行的宽度和高度
intlineWidth=0;
intlineHeight=0;
//获取子view的个数
intchildCount=getChildCount();
for(inti=0;i
Viewchild=getChildAt(i);
//测量子View的宽和高
measureChild(child,widthMeasureSpec,heightMeasureSpec);
//得到LayoutParams
MarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();
//子View占据的宽度
intchildWidth=child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
//子View占据的高度
intchildHeight=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);
}
@Override
protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
//TODOAuto-generatedmethodstub
mAllChildViews.clear();
mLineHeight.clear();
//获取当前ViewGroup的宽度
intwidth=getWidth();
intlineWidth=0;
intlineHeight=0;
//记录当前行的view
ListlineViews=newArrayList();
intchildCount=getChildCount();
for(inti=0;i
Viewchild=getChildAt(i);
MarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();
intchildWidth=child.getMeasuredWidth();
intchildHeight=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=newArrayList();
}
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的位置
intleft=0;
inttop=0;
//获取行数
intlineCount=mAllChildViews.size();
for(inti=0;i
//当前行的views和高度
lineViews=mAllChildViews.get(i);
lineHeight=mLineHeight.get(i);
for(intj=0;j
Viewchild=lineViews.get(j);
//判断是否显示
if(child.getVisibility()==View.GONE){
continue;
}
MarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams();
intcLeft=left+lp.leftMargin;
intcTop=top+lp.topMargin;
intcRight=cLeft+child.getMeasuredWidth();
intcBottom=cTop+child.getMeasuredHeight();
//进行子View进行布局
child.layout(cLeft,cTop,cRight,cBottom);
left+=child.getMeasuredWidth()+lp.leftMargin+lp.rightMargin;
}
left=0;
top+=lineHeight;
}
}
}
2.相关的布局文件:
引用自定义控件:
xmlns:tools="/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:id="@+id/flowlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
三、使用该自定义布局控件类
最后,如何使用该自定义的热门标签控件类呢?很简单,请看下面实例代码:packagecom.czm.flowlayout;
importandroid.app.Activity;
importandroid.graphics.Color;
importandroid.os.Bundle;
importandroid.view.ViewGroup.LayoutParams;
importandroid.view.ViewGroup.MarginLayoutParams;
importandroid.widget.TextView;
/**
*
*@authorcaizhiming
*
*/
publicclassMainActivityextendsActivity{
privateStringmNames[]={
"welcome","android","TextView",
"apple","jamy","kobebryant",
"jordan","layout","viewgroup",
"margin","padding","text",
"name","type","search","logcat"
};
privateXCFlowLayoutmFlowLayout;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initChildViews();
}
privatevoidinitChildViews(){
//TODOAuto-generatedmethodstub
mFlowLayout=(XCFlowLayout)findViewById(R.id.flowlayout);
MarginLayoutParamslp=newMarginLayoutParams(
LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
lp.leftMargin=5;
lp.rightMargin=5;
lp.topMargin=5;
lp.bottomMargin=5;
for(inti=0;i
TextViewview=newTextView(this);
view.setText(mNames[i]);
view.setTextColor(Color.WHITE);
view.setBackgroundDrawable(getResources().getDrawable(R.drawable.textview_bg));
mFlowLayout.addView(view,lp);
}
}
}
四、项目代码目录结构图