前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android自定义流式布局/自动换行布局实例

Android自定义流式布局/自动换行布局实例

作者头像
砸漏
修改2020-10-16 15:21:41
3.4K0
修改2020-10-16 15:21:41
举报
文章被收录于专栏:恩蓝脚本恩蓝脚本

最近,Google开源了一个流式排版库“FlexboxLayout”,功能强大,支持多种排版方式,如各种方向的自动换行等,具体资料各位可搜索学习^_^。

由于我的项目中,只需要从左到右S型的自动换行,需求效果图如下:

使用FlexboxLayout这个框架未免显得有些臃肿,所以自己动手写了一个流式ViewGroup。

安卓中自定义ViewGroup的步骤是:

1. 新建一个类,继承ViewGroup

2. 重写构造方法

3. 重写onMeasure、onLayout方法

onMeasuer方法里一般写测量子View宽高、确定此控件宽高的代码;onLayout方法则是确定子View如何摆放(排版)。

代码如下:

代码语言:javascript
复制
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class FlexBoxLayout extends ViewGroup {
private int mScreenWidth;
private int horizontalSpace, verticalSpace;
private float mDensity;//设备密度,用于将dp转为px
public FlexBoxLayout(Context context) {
this(context, null);
}
public FlexBoxLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//获取屏幕宽高、设备密度
mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;
mDensity = context.getResources().getDisplayMetrics().density;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//确定此容器的宽高
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//测量子View的宽高
int childCount = getChildCount();
View child = null;
//子view摆放的起始位置
int left = getPaddingLeft();
//一行view中将最大的高度存于此变量,用于子view进行换行时高度的计算
int maxHeightInLine = 0;
//存储所有行的高度相加,用于确定此容器的高度
int allHeight = 0;
for (int i = 0; i < childCount; i++) {
child = getChildAt(i);
//测量子View宽高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
//两两对比,取得一行中最大的高度
if (child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom()   maxHeightInLine) {
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
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (changed) {
//摆放子view
View child = null;
//初始子view摆放的左上位置
int left = getPaddingLeft();
int top = getPaddingTop();
//一行view中将最大的高度存于此变量,用于子view进行换行时高度的计算
int maxHeightInLine = 0;
for (int i = 0, len = getChildCount(); i < len; i++) {
child = getChildAt(i);
//从第二个子view开始算起
//因为第一个子view默认从头开始摆放
if (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
*
* @param dpValue
* @return
*/
private int dip2px(float dpValue) {
return (int) (dpValue * mDensity + 0.5f);
}
/**
* 设置子view间的水平间距 单位dp
*
* @param horizontalSpace
*/
public void setHorizontalSpace(int horizontalSpace) {
this.horizontalSpace = horizontalSpace;
}
/**
* 设置子view间的垂直间距 单位dp
*
* @param verticalSpace
*/
public void setVerticalSpace(int verticalSpace) {
this.verticalSpace = verticalSpace;
}
}  

使用如下:

xml文件:

代码语言:javascript
复制
<com.zengd.FlexBoxLayout
android:id="@+id/flexBoxLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" 
<!--这里写子View,也可代码动态添加-- 
……
</com.zengd.FlexBoxLayout 

Activity里的代码:

代码语言:javascript
复制
FlexBoxLayout flexBoxLayout = (FlexBoxLayout) findViewById(R.id.flex_box_layout);
flexBoxLayout.setHorizontalSpace(10);//不设置默认为0
flexBoxLayout.setVerticalSpace(10);//不设置默认为0

运行效果如图:

本项目Demo地址:

https://github.com/zengd0/FlexBoxLayout

补充知识:Android 流式布局(修改版) 当达到两行,隐藏多余的

我就废话不多说了,还是直接看代码吧!

代码语言:javascript
复制
public class SearchLayout extends LinearLayout {
private final int mParentWidth;
private float textSize;
private boolean textColor;
private boolean background;
private boolean isHide = true;
public void setHide(boolean hide) {
isHide = hide;
}
public SearchLayout(Context context, AttributeSet attrs) {
super(context, attrs);
//获取屏幕的宽度
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mParentWidth = metrics.widthPixels - dip2px(16f);
//自定义属性
TypedArray array = 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);
}
//移除子控件
public void removeView() {
removeAllViews();
}
//流式布局
public void setData(List<String  data) {
if (data.isEmpty()){
return;
}
//获取一个子布局
LinearLayout linearLayout = getLinearLayout();
for (int i = 0; i < data.size(); i++) {
//标题
final String name = data.get(i);
//已存在的宽度
int numBar = 0;
//子控件的个数
int count = linearLayout.getChildCount();
for (int j = 0; j < count; j++) {
//一个一个获取
ThemeTextView textView = (ThemeTextView) linearLayout.getChildAt(j);
//获取左外边距
LayoutParams params = (LayoutParams) textView.getLayoutParams();
int leftWidth = params.leftMargin;
int rightWidth = params.rightMargin;
//获取宽高
textView.measure(getMeasuredWidth(), getMeasuredHeight());
//计算已存在的宽度
numBar += textView.getMeasuredWidth()+leftWidth+rightWidth;
}
//获取一个子控件
ThemeTextView text = getText();
//给每一个控件设置点击事件
text.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (onItemTitleClickListener != null){
onItemTitleClickListener.onItemTitle(name);
}
}
});
//赋值
text.setText(name);
//获取宽高
text.measure(getMeasuredWidth(), getMeasuredHeight());
//当前控件的宽度
int textWidth = text.getMeasuredWidth() + text.getPaddingLeft() + text.getPaddingRight();
//判断是否超过屏幕
if (isHide && getChildCount() == 2){
ImageView imageView = getMore(false);
LayoutParams layoutParams = (LayoutParams) imageView.getLayoutParams();
int leftM = layoutParams.leftMargin;
int rightM = layoutParams.rightMargin;
imageView.measure(getMeasuredWidth(), getMeasuredHeight());
int width = imageView.getMeasuredWidth() + imageView.getPaddingLeft() + imageView.getPaddingRight();
int imageWidth = leftM + rightM + width;
if (numBar + textWidth + imageWidth  = mParentWidth){
if (numBar + textWidth + imageWidth   mParentWidth){
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onMoreClickListener != null){
onMoreClickListener.onShowMore(isHide);
}
}
});
linearLayout.addView(imageView);
return;
} else {
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onMoreClickListener != null){
onMoreClickListener.onShowMore(isHide);
}
}
});
linearLayout.addView(text);
linearLayout.addView(imageView);
return;
}
}else {
if (i + 1 <= data.size()-1) {
String title = data.get(i + 1);
ThemeTextView themeTextView = getText();
themeTextView.setText(title);
themeTextView.measure(getMeasuredWidth(),getMeasuredHeight());
int themeTextViewWidth = themeTextView.getMeasuredWidth() + themeTextView.getPaddingLeft() + themeTextView.getPaddingRight();
if (mParentWidth  = numBar + textWidth + imageWidth + themeTextViewWidth ){
linearLayout.addView(text);
continue;
}else {
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onMoreClickListener != null){
onMoreClickListener.onShowMore(isHide);
}
}
});
linearLayout.addView(text);
linearLayout.addView(imageView);
return;
}
}
}
}
if (i == data.size() - 1 && (getChildCount()  = 3 || (mParentWidth < numBar + textWidth) && getChildCount() == 2)){
ImageView imageView = getMore(true);
LayoutParams layoutParams = (LayoutParams) imageView.getLayoutParams();
int leftM = layoutParams.leftMargin;
int rightM = layoutParams.rightMargin;
imageView.measure(getMeasuredWidth(), getMeasuredHeight());
int width = imageView.getMeasuredWidth() + imageView.getPaddingLeft() + imageView.getPaddingRight();
int imageWidth = leftM + rightM + width;
imageView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (onMoreClickListener != null){
onMoreClickListener.onShowMore(isHide);
}
}
});
if (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);
}
}
}
public LinearLayout getLinearLayout() {
//创建LinearLayout布局
LinearLayout linearLayout = new LinearLayout(getContext());
//设置宽高
LayoutParams params = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
linearLayout.setLayoutParams(params);
//添加到主布局中
this.addView(linearLayout);
return linearLayout;
}
public ThemeTextView getText() {
//创建TextView控件
//设置字体大小,颜色,内边距
ThemeTextView themeTextView = new ThemeTextView(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);
}
//设置宽高
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
//外边距
params.setMargins(dip2px(8),dip2px(8),dip2px(8),dip2px(8));
themeTextView.setLayoutParams(params);
return themeTextView;
}
public ImageView getMore(boolean isHide){
ImageView imageView = new ImageView(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));
//设置宽高
LayoutParams params = new LayoutParams(ConfigSingleton.dip2px(27), ConfigSingleton.dip2px(27));
//外边距
params.setMargins(dip2px(8),dip2px(8),dip2px(8),dip2px(8));
imageView.setLayoutParams(params);
return imageView;
}
public interface OnItemTitleClickListener{
void onItemTitle(String title);
}
public interface OnMoreClickListener{
void onShowMore(boolean ishide);
}
private OnItemTitleClickListener onItemTitleClickListener;
private OnMoreClickListener onMoreClickListener;
public void setOnItemTitleClickListener(OnItemTitleClickListener onItemTitleClickListener) {
this.onItemTitleClickListener = onItemTitleClickListener;
}
public void setOnMoreClickListener(OnMoreClickListener onMoreClickListener) {
this.onMoreClickListener = onMoreClickListener;
}
public int dip2px(float dipValue) {
float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
}

attrs文件:

代码语言:javascript
复制
<declare-styleable name="SearchLayout" 
<attr name="Sear_textSize" format="dimension"/ 
<attr name="Sear_textColor" format="boolean"/ 
<attr name="Sear_background" format="boolean"/ 
</declare-styleable 

以上这篇Android自定义流式布局/自动换行布局实例就是小编分享给大家的全部内容了,希望能给大家一个参考。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档