Android自定义ViewGroup之实现FlowLayout流式布局

整理总结自鸿洋的博客:http://blog.csdn.net/lmj623565791/article/details/38352503/
 一、FlowLayout介绍
 所谓FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行。有点像所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局。Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图:

github上已有现成的FlowLayout,本文是从无到有去制作。

二、制作分析

1、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams.
2、onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可)
3、onLayout中对所有的childView进行布局。

三、代码 
1、MainActivity.java

 public class MainActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
//  setContentView(R.layout.activity_main2);
//  setContentView(R.layout.activity_main3);
 }
}

 2、CustomViewGroup.java

public class CustomViewGroup extends ViewGroup {

 private final String TAG = getClass().getSimpleName();

 public CustomViewGroup(Context context) {
  super(context);
 }

 public CustomViewGroup(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public CustomViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
 }

 /**
  * 一、重写generateLayoutParams,确定该ViewGroup的LayoutParams
  * 返回MarginLayoutParams的实例,这样就为我们的ViewGroup指定了其LayoutParams为MarginLayoutParams
  */
 @Override
 public LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new MarginLayoutParams(getContext(), attrs);
 }

 /**
  * 二、计算所有ChildView的宽度和高度 然后根据ChildView的计算结果,设置自己的宽和高
  */
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  //1、获得此ViewGroup上级容器为其推荐的宽和高,以及计算模式
  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  int heightMode = MeasureSpec.getMode(heightMeasureSpec);
  int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
  int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

  // 2、如果ViewGroup布局是wrap_content时,根据childView的尺寸,计算容器的宽和高
  int width = 0;//ViewGroup的宽度
  int height = 0;//ViewGroup的高度
  int lineWidth = 0;//childView所占据的当前行总宽度
  int lineHeight = 0;//childView所占据的各行总高度
  int cCount = getChildCount();////childView的数量
  for(int i=0; i<cCount; i++){//遍历每个childView
   View childView = getChildAt(i);
   measureChild(childView, widthMeasureSpec, heightMeasureSpec);// 测量当前child的宽和高
   MarginLayoutParams mlp = (MarginLayoutParams) childView.getLayoutParams();
   int cWidth = childView.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin;
   int cHeight = childView.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin;
   if(lineWidth + cWidth > sizeWidth){//如果加入当前childView后超出最大宽度,width取最大高度,累加lineHeight,然后开启新行
    width = Math.max(lineWidth, cWidth);
    height += lineHeight;
    lineWidth = cWidth;
   }else{//如果加入当前childView后小于最大宽度,则累加lineWidthheight lineHeight取最大高度
    lineWidth += cWidth;
    height = Math.max(lineHeight, cHeight);
   }
   if(i == cCount-1){// 如果是最后一个childView,则将当前记录的最大宽度和当前lineWidth做比较
    width = Math.max(lineWidth, cWidth);
    height += lineHeight;
   }
  }

  //3、如果是wrap_content设置为我们计算的值;否则直接设置为父容器计算的值
  setMeasuredDimension(
    (widthMode == MeasureSpec.EXACTLY) ? sizeWidth : width,
    (heightMode == MeasureSpec.EXACTLY) ? sizeHeight : height
  );
 }

 /**
  * 三、重写onLayout,对其所有childView进行定位(设置childView的绘制区域)
  */
 private List<List<View>> allChildViews = new ArrayList<List<View>>();//存储所有的childView,按行记录
 private List<Integer> maxLineHeight = new ArrayList<Integer>();//存储每行的最大高度值
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  allChildViews.clear();
  maxLineHeight.clear();

  int width = getWidth();//每行的最大宽度
  int lineWidth = 0;//每行的即时宽度
  int lineHeight = 0;//每行的即时高度

  List<View> lineChildViews = new ArrayList<View>();//存储每行所有的childView

  int cCount = getChildCount();
  for(int i=0; i<cCount; i++){//遍历所有childView
   View childView = getChildAt(i);
   MarginLayoutParams mlp = (MarginLayoutParams) childView.getLayoutParams();
   int cWidth = childView.getMeasuredWidth();
   int cHeight = childView.getMeasuredHeight();
   if(lineWidth + cWidth + mlp.leftMargin + mlp.rightMargin > width){//如果需要换行
    maxLineHeight.add(lineHeight);// 存储这一行最大高度
    allChildViews.add(lineChildViews);// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
    lineChildViews = new ArrayList<View>();
    lineWidth = 0;// 重置行宽
   }else{//如果不需要换行
    lineWidth += cWidth + mlp.leftMargin + mlp.rightMargin;//即时宽度累加
    lineHeight = Math.max(lineHeight,cHeight + mlp.topMargin + mlp.bottomMargin );//即时高度取最大值
    lineChildViews.add(childView);//把当前childView存入这一行的集合
   }
  }
  // 记录最后一行
  maxLineHeight.add(lineHeight);
  allChildViews.add(lineChildViews);

  int left = 0;//左坐标
  int top = 0;//上坐标
  int lineNums = allChildViews.size();// 得到总行数
  for (int i = 0; i < lineNums; i++) {
   lineChildViews = allChildViews.get(i);// 取得每一行的所有的views
   lineHeight = maxLineHeight.get(i);// 取得当前行的最大高度

   Log.e(TAG, "第" + i + "行 :" + lineChildViews.size() + " , " + lineChildViews);
   Log.e(TAG, "第" + i + "行, :" + lineHeight);

   // 遍历当前行所有的View
   for (int j = 0; j < lineChildViews.size(); j++) {
    View childView = lineChildViews.get(j);//取得childView
    if (childView.getVisibility() == View.GONE) {
     continue;
    }
    MarginLayoutParams mlp = (MarginLayoutParams) childView.getLayoutParams();
    //计算childView的left,top,right,bottom
    int lc = left + mlp.leftMargin;
    int tc = top + mlp.topMargin;
    int rc = lc + childView.getMeasuredWidth();
    int bc = tc + childView.getMeasuredHeight();

    Log.e(TAG, childView + " , l = " + lc + " , t = " + t + " , r =" + rc + " , b = " + bc);

    childView.layout(lc, tc, rc, bc);//设置这个childView的位置
    left += childView.getMeasuredWidth() + mlp.rightMargin + mlp.leftMargin;//左坐标累加
   }
   left = 0;//开始新的一行,左坐标重置
   top += lineHeight;//开始新的一行,上坐标累加
  }
 }
}

 3、activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:background="#E1E6F6"
 android:orientation="vertical">

 <com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">

  <TextView
   style="@style/text_flag_01"
   android:text="Welcome" />

  <TextView
   style="@style/text_flag_01"
   android:text="IT工程师" />

  <TextView
   style="@style/text_flag_01"
   android:text="学习ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="恋爱ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="挣钱ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="努力ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="I thick i can" />
 </com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup>

</LinearLayout>

4、activity_main2.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:background="#E1E6F6"
 android:orientation="vertical">

 <com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">

  <TextView
   style="@style/text_flag_01"
   android:text="Welcome" />

  <TextView
   style="@style/text_flag_01"
   android:text="IT工程师" />

  <TextView
   style="@style/text_flag_01"
   android:text="学习ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="恋爱ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="挣钱ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="努力ing" />

  <TextView
   style="@style/text_flag_01"
   android:text="I thick i can" />
 </com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup>

 <com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_marginTop="20dp">

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="Welcome"
   android:textColor="#888888" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="IT工程师"
   android:textColor="#888888" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="学习ing"
   android:textColor="#888888" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="恋爱ing"
   android:textColor="#888888" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="挣钱ing"
   android:textColor="#888888" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="努力ing"
   android:textColor="#888888" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_02"
   android:text="I thick i can"
   android:textColor="#888888" />
 </com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup>

 <com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_marginTop="20dp">

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="Welcome"
   android:textColor="#43BBE7" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="IT工程师"
   android:textColor="#43BBE7" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="学习ing"
   android:textColor="#43BBE7" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="恋爱ing"
   android:textColor="#43BBE7" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="挣钱ing"
   android:textColor="#43BBE7" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="努力ing"
   android:textColor="#43BBE7" />

  <TextView
   style="@style/text_flag_01"
   android:background="@drawable/flag_03"
   android:text="I thick i can"
   android:textColor="#43BBE7" />
 </com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup>

</LinearLayout>

 5、activity_main3.xml

<com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="200dp"
 android:layout_height="wrap_content"
 android:background="#FFFFFF">

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="Welcome"
  android:textColor="#323232" />

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="IT工程师"
  android:textColor="#323232" />

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="学习ing"
  android:textColor="#323232" />

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="恋爱ing"
  android:textColor="#323232" />

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="挣钱ing"
  android:textColor="#323232" />

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="努力ing"
  android:textColor="#323232" />

 <TextView
  style="@style/text_flag_01"
  android:background="@drawable/flag_04"
  android:text="I thick i can"
  android:textColor="#323232" />

</com.cctvjiatao.customviewgroupflowlayout.view.CustomViewGroup>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android中Toolbar随着ScrollView滑动透明度渐变效果实现

    Android中Toolbar随着ScrollView滑动透明度渐变效果实现 一.思路:监听ScrollView的滑动事件 不断的修改Toolbar的透明度 二.注意 1.ScrollView 6.0以前没有scrollView.setOnScrollChangeListener(l)方法  所以要自定义ScrollView 在onScrollChanged()中监听 2.ScrollView 6.0(23)以前没有scrollView.setOnScrollChangeListener()方法

  • Android自定义ViewGroup实现带箭头的圆角矩形菜单

    本文和大家一起做一个带箭头的圆角矩形菜单,大概长下面这个样子: 要求顶上的箭头要对准菜单锚点,菜单项按压反色,菜单背景色和按压色可配置. 最简单的做法就是让UX给个三角形的图片往上一贴,但是转念一想这样是不是太low了点,而且不同分辨率也不太好适配,干脆自定义一个ViewGroup吧! 自定义ViewGroup其实很简单,基本都是按一定的套路来的. 一.定义一个attrs.xml 就是声明一下你的这个自定义View有哪些可配置的属性,将来使用的时候可以自由配置.这里声明了7个属性,分别是:箭头宽

  • Android继承ViewGroup实现Scroll滑动效果的方法示例

    本文实例讲述了Android继承ViewGroup实现Scroll滑动效果的方法.分享给大家供大家参考,具体如下: extends ViewGroup需要重写onMeasure和onLayout方法 onMeasure方法是去测量ViewGroup需要的大小以及包含的子View需要的大小. 执行完上面的方法后,再执行onLayout方法去设置子View的摆放位置. 实现Scroll滑动效果需要去检测滑动速率,即要知道每个单位时间滑动了多少像素值,根据这个像素值去判断Scroll滑动到下一页还是上

  • Android自定义ViewGroup实现标签浮动效果

    前面在学习鸿洋大神的一些自定义的View文章,看到了自定义ViewGroup实现浮动标签,初步看了下他的思路以及结合自己的思路完成了自己的浮动标签的自定义ViewGroup.目前实现的可以动态添加标签.可点击.效果图如下: 1.思路  首先在onMeasure方法中测量ViewGroup的宽和高,重点是处理当我们自定义的ViewGroup设置为wrap_content的情况下,如何去测量其大小的问题.当我们自定义的ViewGroup设置为wrap_content时,我们需要让子View先去测量自

  • 详解Android应用开发中Scroller类的屏幕滑动功能运用

    今天给大家介绍下Android中滑屏功能的一个基本实现过程以及原理初探,最后给大家重点讲解View视图中scrollTo 与scrollBy这两个函数的区别 .   首先 ,我们必须明白在Android View视图是没有边界的,Canvas是没有边界的,只不过我们通过绘制特定的View时对Canvas对象进行了一定的操作,例如 : translate(平移).clipRect(剪切)等,以便达到我们的对该Canvas对象绘制的要求 ,我们可以将这种无边界的视图称为"视图坐标"----

  • Android中ScrollView实现滑动距离监听器的方法

    前言 众所周知ScrollView是我们经常使用的一个UI控件,也许你在使用ScrollView的过程中会发现,当你想监听ScrollView滑动的距离时却没有合适的监听器!当然在API 23中有setOnScrollChangeListener(View.OnScrollChangeListener l)可以使用,但是并不兼容低版本的API.那怎么办呢?只好重写ScrollView来实现对滑动距离的监听了. 话不多说,直接上代码: public class MyScrollView exten

  • Android编程重写ViewGroup实现卡片布局的方法

    本文实例讲述了Android编程重写ViewGroup实现卡片布局的方法.分享给大家供大家参考,具体如下: 实现效果如图: 实现思路 1. 重写onMeasure(int widthMeasureSpec, int heightMeasureSpec)设置每个子View的大小 2. 重写onLayout(boolean changed, int l, int t, int r, int b) 设置每个子View的位置 第一步:新建FlowLayout继承ViewGroup package com

  • Android HorizontalScrollView左右滑动效果

    本文实例为大家分享了Android HorizontalScrollView左右滑动的具体代码,供大家参考,具体内容如下 效果图 一.什么是HorizontalScrollView HorizontalScrollView实际上是一个FrameLayout ,这意味着你只能在它下面放置一个子控件 ,这个子控件可以包含很多数据内容.有可能这个子控件本身就是一个布局控件,可以包含非常多的其他用来展示数据的控件.这个布局控件一般使用的是一个水平布局的LinearLayout.TextView也是一个可

  • Android App开发中自定义View和ViewGroup的实例教程

    View Android所有的控件都是View或者View的子类,它其实表示的就是屏幕上的一块矩形区域,用一个Rect来表示,left,top表示View相对于它的parent View的起点,width,height表示View自己的宽高,通过这4个字段就能确定View在屏幕上的位置,确定位置后就可以开始绘制View的内容了. View绘制过程 View的绘制可以分为下面三个过程: Measure View会先做一次测量,算出自己需要占用多大的面积.View的Measure过程给我们暴露了一个

  • Android ScrollView滑动实现仿QQ空间标题栏渐变

    今天来研究的是ScrollView-滚动视图,滚动视图又分横向滚动视图(HorizontalScrollView)和纵向滚动视图(ScrollView),今天主要研究纵向的.相信大家在开发中经常用到,ScrollView的功能已经很强大了,但是仍然满足不了我们脑洞大开的UI设计师们,所以我们要自定义-本篇文章主要讲监听ScrollView的滑动实现仿QQ空间标题栏渐变,先看一下效果图: 好了我们切入主题. 有可能你不知道的那些ScrollView属性  •android:scrollbars 设

  • Android 触摸事件监听(Activity层,ViewGroup层,View层)详细介绍

    Android不同层次的触摸事件监听 APP开发中,经常会遇到有关手势处理的操作,比如向右滑动返回上一个页面.关于触摸事件的处理,我们可以大概处理在不同的层次上. Activity层:可以看做触摸事件获取的最顶层 ViewGroup层:ViewGroup层可以自主控制是否让子View获取触摸事件 View层:可以决定自己是否真正的消费触摸事件,如果不消费抛给上层ViewGroup Activity级别的手势监听:(右滑动返回上层界面) Activity层手势监听的使用场景:一般用于当前页面中没有

  • Android中实现监听ScrollView滑动事件

    时候我们需要监听ScroView的滑动情况,比如滑动了多少距离,是否滑到布局的顶部或者底部.可惜的是SDK并没有相应的方法,不过倒是提供了一个 复制代码 代码如下: protected void onScrollChanged(int x, int y, int oldx, int oldy) 方法,显然这个方法是不能被外界调用的,因此就需要把它暴露出去,方便使用.解决方式就是写一个接口, 复制代码 代码如下: package com.example.demo1;    public inter

随机推荐