流式布局

自定义ViewGroup
需要重写两个方法:
(1)onMeasure:测量View的款和高,设置自己的宽和高
(2)onLayout:设置子View 的位置

onMeasure:根据子View的布局文件,为子View设置测量模式和测量值
测量包括:测量模式和测量值;
测量模式,有三种情况:
1、当设置的容器大小为精确值或者match_parent时,为EXACTLY;
2、当设置的容器大小为wrap_content时,为AT_MOST;
3、当自定义容器的大小时,为UNSPCIFIED;

ViewGroup的LayoutParams为MarginLayoutParams
(关于LayoutParams,在设置的时候的规则是容器是哪种Layout,设置的Layoutparams就是哪种params)
效果图如下:



首先创建新项目Mooc,自定义一个GroupView--FlowLayout.java:
import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

public class FlowLayout extends ViewGroup {

 // 有自定义的属性
 public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
 }

 public FlowLayout(Context context, AttributeSet attrs) {
  this(context, attrs, 0);
 }

 public FlowLayout(Context context) {
  this(context, null);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

  // 测量子控件的宽和高
  // 容器是fill或者是精确的值
  int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
  int modeWidth = MeasureSpec.getMode(widthMeasureSpec);// 测量模式
                // 容器是fill或者是精确的值这里会是EXACTLY
  int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
  int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

  // wrap_content属性的情况,控件的宽度和高度
  int width = 0;
  int height = 0;

  // 记录每一行的宽和高
  int lineWidth = 0;
  int lineHeight = 0;
  // 得到内部元素的个数
  int cCount = getChildCount();
  for (int i = 0; i < cCount; i++) {
   View child = getChildAt(i);
   // 测量子View的宽和高
   measureChild(child, widthMeasureSpec, heightMeasureSpec);
   // 得到LayoutParams
   MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
   // 子view占据的宽度
   int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
   // 高度
   int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

   // 检测是否换行
   if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
    // 得到最大的宽度
    width = Math.max(lineWidth, width);// 这里决定了整个容器的高度和宽度
    // 重置lineWidth
    lineWidth = childWidth;

    height += lineHeight;
    lineHeight = childHeight;
   } else {
    // 叠加行的宽度
    lineWidth += childWidth;
    // 得到当前行最大的高度
    lineHeight = Math.max(lineHeight, childHeight);
   }

   if (i == cCount - 1) {
    width = Math.max(lineWidth, width);
    height += lineHeight;

   }

  }

  Log.e("TAG", "sizeWidth" + sizeWidth);
  Log.e("TAG", "sizeHeight" + sizeHeight);

  setMeasuredDimension(modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width
    + getPaddingLeft() + getPaddingRight(),
    modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop()
      + getPaddingBottom());

 }

 private List<List<View>> mAllViews = new ArrayList<List<View>>();
 private List<Integer> mLineHeight = new ArrayList<Integer>();

 @SuppressLint("DrawAllocation")
 @Override
 protected void onLayout(boolean changed, int l, int r, int t, int b) {

  // 这里的原因是这个执行过程可能会执行很多次
  mAllViews.clear();
  mLineHeight.clear();

  int width = getWidth();
  int lineWidth = 0;
  int lineHeight = 0;

  List<View> lineViews = new ArrayList<View>();
  int cCount = getChildCount();
  for (int i = 0; i < cCount; i++) {

   View child = getChildAt(i);
   MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

   int childWidht = child.getMeasuredWidth();
   int childHeight = child.getMeasuredHeight();

   // 换行
   if (childWidht + lineWidth + lp.leftMargin + lp.rightMargin > width - getPaddingLeft() - getPaddingRight()) {

    mLineHeight.add(lineHeight);
    mAllViews.add(lineViews);

    lineWidth = 0;
    lineHeight = childHeight + lp.topMargin + lp.bottomMargin;

    lineViews = new ArrayList<View>();
   }

   lineWidth += childWidht + lp.leftMargin + lp.rightMargin;
   lineHeight = Math.max(lineHeight, childHeight + lp.topMargin + lp.bottomMargin);
   lineViews.add(child);
  }// for end

  // the last line
  mLineHeight.add(lineHeight);
  mAllViews.add(lineViews);

  // set the position of view;
  int left = getPaddingLeft();
  int top = getPaddingTop();

  int lineNum = mAllViews.size();
  for (int i = 0; i < lineNum; i++) {

   lineViews = mAllViews.get(i);
   lineHeight = mLineHeight.get(i);

   for (int j = 0; j < lineViews.size(); j++) {

    View child = lineViews.get(j);

    if (child.getVisibility() == View.GONE) {
     continue;
    }

    MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    int lc = left + lp.leftMargin;
    int tc = top + lp.topMargin;
    int rc = lc + child.getMeasuredWidth();
    int bc = tc + child.getMeasuredHeight();

    // layout child views;
    child.layout(lc, tc, rc, bc);

    left += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
   }

   left = getPaddingLeft();
   top += lineHeight;
  }

 }

 // 对应的MarginLayoutParams
 @Override
 public LayoutParams generateLayoutParams(AttributeSet attrs) {
  // 定义params
  return new MarginLayoutParams(getContext(), attrs);
 }

}
这里考虑到了多种情况,比如你设置了容器的padding或者无论你设置是match_parent还是wrap_content,但是有一个问题自己有所疑问,就是打印这里面的log时,当布局为match_parent时为屏幕的宽和高,但是当设置为精确值时比如200dp,打印的理所当然应该是200dp,但是却打印的是400dp,为真实值的2倍。这里需要注意一下当你设置为精确值的情况,可能不会得到你预期的效果。

一下为了效果的美观进行了一些自定义控件的外观:
一个背景效果:/drawable/text_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android=" http://schemas.android.com/apk/res/android" >

    <solid android:color="#e7e7e7" >
    </solid>

    <corners android:radius="30dp" />

    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />

</shape>

一个自定义的TextView:/res/layout/text_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android=" http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:background="@drawable/text_bg" >

</TextView>

然后就可以直接在MainActivity.java中使用这个容器FlowLayout,把他直接添加到MainActivity的layout文件中去。
MainActivity.java代码:

import android.os.Bundle;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.ViewGroup.MarginLayoutParams;
import android.widget.TextView;

public class MainActivity extends Activity {

 private String[] mVals = new String[] { "优酷", "PPTV聚力", "暴风影音", "QQ控件", "微博", "Adobe", "凤凰新闻",
   "淘宝", "京东", "一号店", "百度云盘", "酷我音乐", "网易新闻" };
 
 private FlowLayout mFlowLayout;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
 
  mFlowLayout = (FlowLayout) findViewById(R.id.flow_layout);
  initData();
 }
 
 private void initData(){
 
  LayoutInflater mInflater = LayoutInflater.from(this);
  for(int i = 0; i < mVals.length; i++){

   TextView myText = (TextView) mInflater.inflate(R.layout.text_view, mFlowLayout, false);
   myText.setText(mVals[i]);
// MarginLayoutParams lp = new MarginLayoutParams(MarginLayoutParams.WRAP_CONTENT, MarginLayoutParams.WRAP_CONTENT);
// mFlowLayout.addView(myText,lp);//这里注意,如果设置的自定义控件的margin没有效果的时候,请尝试使用这段代码,否则使用下一行
   mFlowLayout.addView(myText);
  }
 }
}

至此,上述效果图就可以实现了

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值