栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 移动开发 > Android

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

Android 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

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

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

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

1. 新建一个类,继承ViewGroup

2. 重写构造方法

3. 重写onMeasure、onLayout方法

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

代码如下:

  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());
}
     }
   }

    
    private int dip2px(float dpValue) {
      return (int) (dpValue * mDensity + 0.5f);
    }

    
    public void setHorizontalSpace(int horizontalSpace) {
      this.horizontalSpace = horizontalSpace;
    }

    
    public void setVerticalSpace(int verticalSpace) {
      this.verticalSpace = verticalSpace;
    }
  }  

使用如下:

xml文件:

    
      
      ……

    

Activity里的代码:

  FlexBoxLayout flexBoxLayout = (FlexBoxLayout) findViewById(R.id.flex_box_layout);
  flexBoxLayout.setHorizontalSpace(10);//不设置默认为0
  flexBoxLayout.setVerticalSpace(10);//不设置默认为0

运行效果如图:

本项目Demo地址:

https://github.com/zengd0/FlexBoxLayout

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

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

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 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文件:

 
    
    
    
  

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/154426.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号