自定义流式布局

Posted 安卓笔记侠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义流式布局相关的知识,希望对你有一定的参考价值。

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/

以上是关于自定义流式布局的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义View之实现流式布局

Android之自定义流式布局FlowLayout

Android 中自定义ViewGroup实现流式布局的效果

自定义控件 - 流式布局:FlowLayout

Android自定义ViewGroup的布局,往往都是从流式布局开始

Android自定义ViewGroup,onMeasureonLayout,实现流式布局