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();i 0){ //两两对比,取得一行中最大的高度 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(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。