Android自定义流式布局/自动换行布局实例
最近,Google开源了一个流式排版库“FlexboxLayout”,功能强大,支持多种排版方式,如各种方向的自动换行等,具体资料各位可搜索学习^_^。
由于我的项目中,只需要从左到右S型的自动换行,需求效果图如下:
使用FlexboxLayout这个框架未免显得有些臃肿,所以自己动手写了一个流式ViewGroup。
安卓中自定义ViewGroup的步骤是:
1.新建一个类,继承ViewGroup
2.重写构造方法
3.重写onMeasure、onLayout方法
onMeasuer方法里一般写测量子View宽高、确定此控件宽高的代码;onLayout方法则是确定子View如何摆放(排版)。
代码如下:
importandroid.content.Context;
importandroid.util.AttributeSet;
importandroid.view.View;
importandroid.view.ViewGroup;
publicclassFlexBoxLayoutextendsViewGroup{
privateintmScreenWidth;
privateinthorizontalSpace,verticalSpace;
privatefloatmDensity;//设备密度,用于将dp转为px
publicFlexBoxLayout(Contextcontext){
this(context,null);
}
publicFlexBoxLayout(Contextcontext,AttributeSetattrs){
super(context,attrs);
//获取屏幕宽高、设备密度
mScreenWidth=context.getResources().getDisplayMetrics().widthPixels;
mDensity=context.getResources().getDisplayMetrics().density;
}
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
//确定此容器的宽高
intwidthMode=MeasureSpec.getMode(widthMeasureSpec);
intwidthSize=MeasureSpec.getSize(widthMeasureSpec);
intheightMode=MeasureSpec.getMode(heightMeasureSpec);
intheightSize=MeasureSpec.getSize(heightMeasureSpec);
//测量子View的宽高
intchildCount=getChildCount();
Viewchild=null;
//子view摆放的起始位置
intleft=getPaddingLeft();
//一行view中将最大的高度存于此变量,用于子view进行换行时高度的计算
intmaxHeightInLine=0;
//存储所有行的高度相加,用于确定此容器的高度
intallHeight=0;
for(inti=0;imaxHeightInLine){
maxHeightInLine=child.getMeasuredHeight()+child.getPaddingTop()+child.getPaddingBottom();
}
left+=child.getMeasuredWidth()+dip2px(horizontalSpace)+child.getPaddingLeft()+child.getPaddingRight();
if(left>=widthSize-getPaddingRight()-getPaddingLeft()){//换行
left=getPaddingLeft();
//累积行的总高度
allHeight+=maxHeightInLine+dip2px(verticalSpace);
//因为换行了,所以每行的最大高度置0
maxHeightInLine=0;
}
}
//再加上最后一行的高度,因为之前的高度累积条件是换行
//最后一行没有换行操作,所以高度应该再加上
allHeight+=maxHeightInLine;
if(widthMode!=MeasureSpec.EXACTLY){
widthSize=mScreenWidth;//如果没有指定宽,则默认为屏幕宽
}
if(heightMode!=MeasureSpec.EXACTLY){//如果没有指定高度
heightSize=allHeight+getPaddingBottom()+getPaddingTop();
}
setMeasuredDimension(widthSize,heightSize);
}
@Override
protectedvoidonLayout(booleanchanged,intl,intt,intr,intb){
if(changed){
//摆放子view
Viewchild=null;
//初始子view摆放的左上位置
intleft=getPaddingLeft();
inttop=getPaddingTop();
//一行view中将最大的高度存于此变量,用于子view进行换行时高度的计算
intmaxHeightInLine=0;
for(inti=0,len=getChildCount();i0){
//两两对比,取得一行中最大的高度
if(getChildAt(i-1).getMeasuredHeight()>maxHeightInLine){
maxHeightInLine=getChildAt(i-1).getMeasuredHeight();
}
//当前子view的起始left为上一个子view的宽度+水平间距
left+=getChildAt(i-1).getMeasuredWidth()+dip2px(horizontalSpace);
if(left+child.getMeasuredWidth()>=getWidth()-getPaddingRight()-getPaddingLeft()){//这一行所有子view相加的宽度大于容器的宽度,需要换行
//换行的首个子view,起始left应该为0+容器的paddingLeft
left=getPaddingLeft();
//top的位置为上一行中拥有最大高度的某个View的高度+垂直间距
top+=maxHeightInLine+dip2px(verticalSpace);
//将上一行View的最大高度置0
maxHeightInLine=0;
}
}
//摆放子view
child.layout(left,top,left+child.getMeasuredWidth(),top+child.getMeasuredHeight());
}
}
}
/**
*dp转为px
*
*@paramdpValue
*@return
*/
privateintdip2px(floatdpValue){
return(int)(dpValue*mDensity+0.5f);
}
/**
*设置子view间的水平间距单位dp
*
*@paramhorizontalSpace
*/
publicvoidsetHorizontalSpace(inthorizontalSpace){
this.horizontalSpace=horizontalSpace;
}
/**
*设置子view间的垂直间距单位dp
*
*@paramverticalSpace
*/
publicvoidsetVerticalSpace(intverticalSpace){
this.verticalSpace=verticalSpace;
}
}
使用如下:
xml文件:
……
Activity里的代码:
FlexBoxLayoutflexBoxLayout=(FlexBoxLayout)findViewById(R.id.flex_box_layout); flexBoxLayout.setHorizontalSpace(10);//不设置默认为0 flexBoxLayout.setVerticalSpace(10);//不设置默认为0
运行效果如图:
本项目Demo地址:
https://github.com/zengd0/FlexBoxLayout
补充知识:Android流式布局(修改版)当达到两行,隐藏多余的
我就废话不多说了,还是直接看代码吧!
publicclassSearchLayoutextendsLinearLayout{
privatefinalintmParentWidth;
privatefloattextSize;
privatebooleantextColor;
privatebooleanbackground;
privatebooleanisHide=true;
publicvoidsetHide(booleanhide){
isHide=hide;
}
publicSearchLayout(Contextcontext,AttributeSetattrs){
super(context,attrs);
//获取屏幕的宽度
DisplayMetricsmetrics=context.getResources().getDisplayMetrics();
mParentWidth=metrics.widthPixels-dip2px(16f);
//自定义属性
TypedArrayarray=context.obtainStyledAttributes(attrs,R.styleable.SearchLayout);
background=array.getBoolean(R.styleable.SearchLayout_Sear_background,false);
textColor=array.getBoolean(R.styleable.SearchLayout_Sear_textColor,false);
textSize=array.getDimension(R.styleable.SearchLayout_Sear_textSize,0);
//方向为纵向
setOrientation(VERTICAL);
}
//移除子控件
publicvoidremoveView(){
removeAllViews();
}
//流式布局
publicvoidsetData(Listdata){
if(data.isEmpty()){
return;
}
//获取一个子布局
LinearLayoutlinearLayout=getLinearLayout();
for(inti=0;i=mParentWidth){
if(numBar+textWidth+imageWidth>mParentWidth){
imageView.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
if(onMoreClickListener!=null){
onMoreClickListener.onShowMore(isHide);
}
}
});
linearLayout.addView(imageView);
return;
}else{
imageView.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
if(onMoreClickListener!=null){
onMoreClickListener.onShowMore(isHide);
}
}
});
linearLayout.addView(text);
linearLayout.addView(imageView);
return;
}
}else{
if(i+1<=data.size()-1){
Stringtitle=data.get(i+1);
ThemeTextViewthemeTextView=getText();
themeTextView.setText(title);
themeTextView.measure(getMeasuredWidth(),getMeasuredHeight());
intthemeTextViewWidth=themeTextView.getMeasuredWidth()+themeTextView.getPaddingLeft()+themeTextView.getPaddingRight();
if(mParentWidth>=numBar+textWidth+imageWidth+themeTextViewWidth){
linearLayout.addView(text);
continue;
}else{
imageView.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
if(onMoreClickListener!=null){
onMoreClickListener.onShowMore(isHide);
}
}
});
linearLayout.addView(text);
linearLayout.addView(imageView);
return;
}
}
}
}
if(i==data.size()-1&&(getChildCount()>=3||(mParentWidth=numBar+textWidth+imageWidth){
linearLayout.addView(text);
linearLayout.addView(imageView);
}else{
if(mParentWidth>=numBar+textWidth){
linearLayout.addView(text);
linearLayout=getLinearLayout();
linearLayout.addView(imageView);
}else{
linearLayout=getLinearLayout();
linearLayout.addView(text);
linearLayout.addView(imageView);
}
}
return;
}
if(mParentWidth>=numBar+textWidth){
//没有,继续添加
linearLayout.addView(text);
}else{
//否者,重新获取一个子布局,再添加
linearLayout=getLinearLayout();
linearLayout.addView(text);
}
}
}
publicLinearLayoutgetLinearLayout(){
//创建LinearLayout布局
LinearLayoutlinearLayout=newLinearLayout(getContext());
//设置宽高
LayoutParamsparams=newLayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
linearLayout.setLayoutParams(params);
//添加到主布局中
this.addView(linearLayout);
returnlinearLayout;
}
publicThemeTextViewgetText(){
//创建TextView控件
//设置字体大小,颜色,内边距
ThemeTextViewthemeTextView=newThemeTextView(getContext());
themeTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,textSize);
themeTextView.setMaxEms(7);
themeTextView.setLines(1);
themeTextView.setEllipsize(TextUtils.TruncateAt.END);
themeTextView.setPadding(dip2px(8),dip2px(4),dip2px(8),dip2px(4));
if(textColor){//可以根据自己的需求修改判断
themeTextView.setTextColor(ContextCompat.getColor(getContext(),R.color.day_text_color_thirdly));
}else{
themeTextView.setTextColor(ContextCompat.getColor(getContext(),R.color.day_text_color_thirdly));
}
if(background){
themeTextView.setBackgroundResource(R.drawable.border_search_background_day);
}
//设置宽高
LayoutParamsparams=newLayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
//外边距
params.setMargins(dip2px(8),dip2px(8),dip2px(8),dip2px(8));
themeTextView.setLayoutParams(params);
returnthemeTextView;
}
publicImageViewgetMore(booleanisHide){
ImageViewimageView=newImageView(getContext());
if(background){
imageView.setBackgroundResource(R.drawable.border_search_background_day);
}
imageView.setImageResource(R.drawable.icon_more);
if(isHide){
imageView.setRotation(180f);
}
imageView.setColorFilter(ContextCompat.getColor(getContext(),R.color.day_text_color_primary));
imageView.setPadding(dip2px(6),dip2px(6),dip2px(7),dip2px(7));
//设置宽高
LayoutParamsparams=newLayoutParams(ConfigSingleton.dip2px(27),ConfigSingleton.dip2px(27));
//外边距
params.setMargins(dip2px(8),dip2px(8),dip2px(8),dip2px(8));
imageView.setLayoutParams(params);
returnimageView;
}
publicinterfaceOnItemTitleClickListener{
voidonItemTitle(Stringtitle);
}
publicinterfaceOnMoreClickListener{
voidonShowMore(booleanishide);
}
privateOnItemTitleClickListeneronItemTitleClickListener;
privateOnMoreClickListeneronMoreClickListener;
publicvoidsetOnItemTitleClickListener(OnItemTitleClickListeneronItemTitleClickListener){
this.onItemTitleClickListener=onItemTitleClickListener;
}
publicvoidsetOnMoreClickListener(OnMoreClickListeneronMoreClickListener){
this.onMoreClickListener=onMoreClickListener;
}
publicintdip2px(floatdipValue){
floatscale=getContext().getResources().getDisplayMetrics().density;
return(int)(dipValue*scale+0.5f);
}
}
attrs文件:
以上这篇Android自定义流式布局/自动换行布局实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。