自定义流式布局

1、概述

何为FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行。有点所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局。Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图:

这些都特别适合使用FlowLayout

2、简单的分析

1、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams.

2、onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。(当然,如果不是wrap_content,直接使用父ViewGroup传入的计算值即可)

3、onLayout中对所有的childView进行布局。

  1 package com.gcp;
  2
  3 import android.content.Context;
  4 import android.util.AttributeSet;
  5 import android.util.Log;
  6 import android.view.View;
  7 import android.view.ViewGroup;
  8
  9 import java.util.ArrayList;
 10 import java.util.List;
 11
 12 public class FlowLayout extends ViewGroup {
 13     public FlowLayout(Context context) {
 14         this(context, null);
 15     }
 16
 17     public FlowLayout(Context context, AttributeSet attrs) {
 18         this(context, attrs, 0);
 19     }
 20
 21     public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
 22         super(context, attrs, defStyleAttr);
 23
 24     }
 25
 26     //能够设置当前布局的宽度和高度
 27     @Override
 28     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 29 //        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 30         //获取设置的宽高的模式和具体的值
 31         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
 32         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 33         int widthSize = MeasureSpec.getSize(widthMeasureSpec);
 34         int heightSize = MeasureSpec.getSize(heightMeasureSpec);
 35
 36         //如果用户使用的至多模式,那么使用如下两个变量计算真实的宽高值。
 37         int width = 0;
 38         int height = 0;
 39
 40         //每一行的宽度
 41         int lineWidth = 0;
 42         int lineHeight = 0;
 43
 44         //获取子视图
 45         int childCount = getChildCount();
 46         for (int i = 0; i < childCount; i++) {
 47             View childView = getChildAt(i);
 48
 49             //只有调用了如下的方法,方可计算子视图的测量的宽高
 50             measureChild(childView, widthMeasureSpec, heightMeasureSpec);
 51
 52             //获取子视图的宽高
 53             int childWidth = childView.getMeasuredWidth();
 54             int childHeight = childView.getMeasuredHeight();
 55             //要想保证可以获取子视图的边距参数对象,必须重写generateLayoutParams().
 56             MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
 57
 58             if (lineWidth + childWidth + mp.leftMargin + mp.rightMargin <= widthSize){//不换行
 59
 60                 lineWidth += childWidth + mp.leftMargin + mp.rightMargin;
 61                 lineHeight = Math.max(lineHeight,childHeight + mp.topMargin + mp.bottomMargin);
 62
 63             }else{//换行
 64                 width = Math.max(width,lineWidth);
 65                 height += lineHeight;
 66
 67                 //重置
 68                 lineWidth = childWidth + mp.leftMargin + mp.rightMargin;
 69                 lineHeight = childHeight + mp.topMargin + mp.bottomMargin;
 70             }
 71
 72             //最后一个元素
 73             if(i == childCount - 1){
 74                 width = Math.max(width,lineWidth);
 75                 height += lineHeight;
 76             }
 77
 78         }
 79
 80         Log.e("TAG", "widthSize = " + widthSize + ",heightSize = " + heightSize);
 81         Log.e("TAG", "width = " + width + ",height = " + height);
 82
 83
 84         //设置当前流式布局的宽高
 85         setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height);
 86     }
 87
 88     //重写的目的:给每一个子视图指定显示的位置:childView.layout(l,t,r,b);
 89     private List<List<View>> allViews = new ArrayList<>();//每一行的子视图的集合构成的集合。
 90     private List<Integer> allHeights = new ArrayList<>();//每一行的高度构成的集合。
 91     @Override
 92     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 93         //一、给两个集合添加元素。
 94
 95         //每一行的宽高值
 96         int lineWidth = 0;
 97         int lineHeight = 0;
 98
 99         //提供一个集合,保存一行childView
100         List<View> lineList = new ArrayList<>();
101         //获取布局的宽度
102         int width = this.getMeasuredWidth();
103
104         int childCount = getChildCount();
105         for(int i = 0; i < childCount; i++) {
106             View childView = getChildAt(i);
107             //获取视图的测量宽高、边距
108             int childWidth = childView.getMeasuredWidth();
109             int childHeight = childView.getMeasuredHeight();
110
111             MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
112
113             if(lineWidth + childWidth + mp.leftMargin + mp.rightMargin <= width){//不换行
114                 lineList.add(childView);
115                 lineWidth += childWidth + mp.leftMargin + mp.rightMargin;
116                 lineHeight = Math.max(lineHeight,childHeight + mp.topMargin + mp.bottomMargin);
117
118             }else{//换行
119                 allViews.add(lineList);
120                 allHeights.add(lineHeight);
121
122                 lineWidth = childWidth + mp.leftMargin + mp.rightMargin;
123                 lineHeight = childHeight + mp.topMargin + mp.bottomMargin;
124                 lineList = new ArrayList<>();
125                 lineList.add(childView);
126             }
127
128             if(i == childCount - 1){//如果是最后一个元素
129                 allViews.add(lineList);
130                 allHeights.add(lineHeight);
131             }
132
133         }
134
135         Log.e("TAG", "allViews.size = " + allViews.size() + ",allHeights.size = " + allHeights.size());
136
137         //二、给每一个子视图指定显示的位置
138         int x = 0;
139         int y = 0;
140         for(int i = 0; i < allViews.size(); i++) {//每遍历一次,对应一行元素
141             List<View> lineViews = allViews.get(i);//取出当前行构成的集合
142             for(int j = 0; j < lineViews.size(); j++) {
143                 View childView = lineViews.get(j);
144                 int childWidth = childView.getMeasuredWidth();
145                 int childHeight = childView.getMeasuredHeight();
146
147                 MarginLayoutParams mp = (MarginLayoutParams) childView.getLayoutParams();
148
149                 int left = x + mp.leftMargin;
150                 int top = y + mp.topMargin;
151                 int right = left + childWidth;
152                 int bottom = top + childHeight;
153
154                 childView.layout(left,top,right,bottom);
155
156                 x +=  childWidth + mp.leftMargin + mp.rightMargin;
157
158             }
159             y += allHeights.get(i);
160             x = 0;
161         }
162     }
163
164     @Override
165     public LayoutParams generateLayoutParams(AttributeSet attrs) {
166         MarginLayoutParams mp = new MarginLayoutParams(getContext(), attrs);
167         return mp;
168
169     }
170 }

FlowLayout

3、generateLayoutParams

因为我们只需要支持margin,所以直接使用系统的MarginLayoutParams

@Override  
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)  
{  
    return new MarginLayoutParams(getContext(), attrs);  
}  

4、onMeasure

  首先得到其父容器传入的测量模式和宽高的计算值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测量。然后根据所有childView的测量得出的宽和高得到该ViewGroup如果设置为wrap_content时的宽和高。最后根据模式,如果是MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高。

5、onLayout

onLayout中完成对所有childView的位置以及大小的指定

6、测试

我准备使用TextView作为我们的标签,所以为其简单写了一点样式:

res/values/styles.xml中:

<style name="text_flag_01">  
       <item name="android:layout_width">wrap_content</item>  
       <item name="android:layout_height">wrap_content</item>  
       <item name="android:layout_margin">4dp</item>  
       <item name="android:background">@drawable/flag_01</item>  
       <item name="android:textColor">#ffffff</item>  
 </style>  

flag_01.xml

<?xml version="1.0" encoding="utf-8"?>  
<shape xmlns:android="http://schemas.android.com/apk/res/android" >  
  
    <solid android:color="#7690A5" >  
    </solid>  
  
    <corners android:radius="5dp"/>  
    <padding  
        android:bottom="2dp"  
        android:left="10dp"  
        android:right="10dp"  
        android:top="2dp" />  
  
</shape>  

布局文件:

<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.gcp.FlowLayout  
        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.gcp.FlowLayout>  
      
    </LinearLayout>  

效果图:

  

参考:

http://blog.csdn.net/jdsjlzx/article/details/45042081?ref=myread

http://blog.csdn.net/lmj623565791/article/details/38352503/

时间: 2024-10-23 08:27:36

自定义流式布局的相关文章

Android之自定义流式布局FlowLayout

流式布局常常用于“热门标签”中,大概功能就是将所有的子View一行一行的排列,如果一行中剩下的空间不足以盛放下一个子View,则换到另一行继续排列.这样做的好处是不需要在主线程中自己麻烦定义控件的位置,只需要把生成的控件放到容器中,容器自己会自动排列.首先来看一下运行结果: Android中的自定义容器控件(继承自ViewGroup的控件)都有两个必须实现的方法:onMeasure()和onLayout() (1)onMeasure:测量子View的宽和高,设置自己的宽和高,根据自View的布局

android流式布局热门标签的实现

在日常的app使用中,我们会在android 的app中看见热门标签等自动换行的流式布局,今天就为大家分享一种android流式布局的实现. 先看最终效果 自定义流式布局的实现 package com.sunny.flowlayout.view; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.util.AttributeSet; import an

Android自定义之流式布局

流式布局,好处就是父类布局可以自动的判断子孩子是不是需要换行,什么时候需要换行,可以做到网页版的标签的效果.今天就是简单的做了自定义的流式布局. 具体效果: 原理: 其实很简单,Measure  Layout.只需要这两个步骤就可以搞定了.完全的手动去Measure  Layout. 我们看一下代码. 解释就在代码里面做注释了,因为使用为知笔记写的博客,格式不符合代码格式.大家可以看具体的源码.最后又源码下载地址. 1.Measure  测量 @Override protected void o

自定义ViewGroup 流式布局

使用 public class MainActivity extends Activity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_flowlayout);         FlowLayout flow_layout = 

ViewGroup2——自定义实现流式布局

Android中的线性布局LinearLayout,只能横向或纵向排列子控件,而且横向排列时不能自动换行.实际上,通过扩展ViewGroup就能够实现控件自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行,也就是所谓的流式布局. 自定义CustomViewGroup.java如下 public class CustomViewGroup extends ViewGroup { int mCellWidth; int mCellHeight; public CustomViewGroup(

Android自定义ViewGroup实现流式布局

实现宽度不足自动换行的流式布局: FlowLayout.java package com.jackie.flowlayout; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * Created by Jackie on 8/28/15. */ public class FlowLayout

Android中常见的热门标签的流式布局的实现

一.概述:在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出) 类似的自定义布局.下面我们就来详细介绍流式布局的应用特点以及用的的技术点: 1.流式布局的特点以及应用场景    特点:当上面一行的空间不够容纳新的TextView时候,    才开辟下一行的空间 原理图: 场景:主要用于关键词搜索或者热门标签等场景2.自定义ViewGroup,重点重写下面两个方法 1.o

流式布局的实现-1

流式布局可以实现逐行填满的布局效果:适用于关键词搜索和热门展示,可以动态的添加标签,用起来十分方便与快捷 源码下载(由慕课网的老师提供,谢谢) 之后说说主要的安排: 第一篇:创建类,确定继承关系,实现构造函数,确定成员函数: 第二篇:实现FlowLayout(流式布局)主要函数的方法: 第一篇:创建类,确定继承关系,实现构造函数,确定成员函数: 第二篇与之后几篇:实现各函数,并说明成员变量的作用; 和用listView实现下拉刷新一样,还是先分析文件结构: 包括了两个类: public clas

安卓流式布局

一.流式布局效果 二.工程结构 三.新建工程,自定义GroupView(流式布局) package com.yuanlei.flowlayoutdemo; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.view.ViewGroup; import java.util.ArrayLi