使用


public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flowlayout);
        FlowLayout flow_layout = (FlowLayout) findViewById(R.id.flow_layout);
        //一定要注意,我们自定义的FlowLayout中使用的是MarginLayoutParams,所以这里也只能用MarginLayoutParams,不然报ClassCastException
        MarginLayoutParams marginLayoutParams = new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        int margins = (int) (2 * getResources().getDisplayMetrics().density + 0.5f);
        marginLayoutParams.setMargins(margins, margins, margins, margins);
        TextView tv1 = new TextView(new ContextThemeWrapper(this, R.style.text_style3), null, 0);//这是代码中设置style的方法!
        TextView tv2 = new TextView(new ContextThemeWrapper(this, R.style.text_style2), null, 0);
        TextView tv3 = new TextView(new ContextThemeWrapper(this, R.style.text_style1), null, 0);
        TextView tv4 = new TextView(new ContextThemeWrapper(this, R.style.text_style2), null, 0);
        tv1.setText("代码中添加View");
        tv2.setText("并设置style");
        tv3.setText("并设置margins");
        tv4.setText("博客:http://www.cnblogs.com/baiqiantao/,如果TextView内容特别长会是这种效果");
        tv1.setLayoutParams(marginLayoutParams);
        tv2.setLayoutParams(marginLayoutParams);
        tv3.setLayoutParams(marginLayoutParams);
        tv4.setLayoutParams(marginLayoutParams);
        flow_layout.addView(tv1);
        flow_layout.addView(tv2);
        flow_layout.addView(tv3);
        flow_layout.addView(tv4);
    }
}

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E1E6F6"
    android:orientation="vertical" >
    <com.bqt.myview.FlowLayout
        android:id="@+id/flow_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
        <TextView
            style="@style/text_style1"
            android:text="包青天" />
        <TextView
            style="@style/text_style1"
            android:text="流式布局" />
        <TextView
            style="@style/text_style1"
            android:text="自定义ViewGroup" />
        <TextView
            style="@style/text_style2"
            android:text="包青天" />
        <TextView
            style="@style/text_style2"
            android:text="奋发图强" />
        <TextView
            style="@style/text_style2"
            android:text="baiqiantao@sina.com" />
        <TextView
            style="@style/text_style3"
            android:text="努力工作" />
        <TextView
            style="@style/text_style3"
            android:text="月薪15K起" />
        <TextView
            style="@style/text_style3"
            android:text="移动开发" />
        <TextView
            style="@style/text_style3"
            android:text="android开发" />
    </com.bqt.myview.FlowLayout>
</LinearLayout>

分析

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

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

onLayout方法
  • onLayout中完成对所有childView的位置以及大小的指定
  • 遍历所有的childView,用于设置allViews的值,以及mLineHeight的值。
  • 根据allViews的长度,遍历所有的行
  • 遍历每一行的中所有的child,对child的left , top , right , bottom 进行计算和定位。
  • 重置left和top,准备计算下一行的childView的位置。

View

public class FlowLayout extends ViewGroup {
    /**存储所有的View,按行记录*/
    private List<List<View>> mAllViews = new ArrayList<List<View>>();
    /**记录每一行的最大高度*/
    private List<Integer> mLineHeight = new ArrayList<Integer>();
    /**布局的宽高*/
    private int width = 0, height = 0;
    /**每一行的宽度,width不断取其中最大的宽度*/
    private int lineWidth = 0;
    /**每一行的高度,累加至height*/
    private int lineHeight = 0;
    /**布局中的子控件*/
    private View child;
    /**布局中子控件设置的LayoutParam*/
    private MarginLayoutParams layoutParams;
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //根据所有子控件设置自己的宽和高
        for (int i = 0; i < getChildCount(); i++) {
            child = getChildAt(i);
            // 让系统去测量当前child的宽高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 获取当前child实际占据的宽高
            layoutParams = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
            int childHeight = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
            //如果加入当前child后超出最大允许宽度,则将目前最大宽度给width,累加height,然后开启新行
            if (lineWidth + childWidth > MeasureSpec.getSize(widthMeasureSpec)) {
                width = Math.max(lineWidth, childWidth);// 对比得到最大宽度
                // 开启新行,将当前行的宽高设为当前child的宽高
                lineWidth = childWidth;
                lineHeight = childHeight;
                // 累加行高
                height += lineHeight;
            } else {
                // 否则(不换行)累加行宽,lineHeight取最大高度
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较,并累加行高
            if (i == getChildCount() - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }
        }
        //如果是布局中设置的是wrap_content,设置为我们计算的值;否则,直接设置为父容器测量的值。
        width = (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) ? MeasureSpec.getSize(widthMeasureSpec) : width;
        height = (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) ? MeasureSpec.getSize(heightMeasureSpec) : height;
        setMeasuredDimension(width, height);
    }
    @SuppressLint("DrawAllocation")
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mAllViews.clear();
        mLineHeight.clear();
        lineWidth = 0;
        lineHeight = 0;
        // 存储每一行所有的childView
        List<View> lineViews = new ArrayList<View>();
        for (int i = 0; i < getChildCount(); i++) {
            child = getChildAt(i);
            layoutParams = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            // 如果需要换行
            if (childWidth + layoutParams.leftMargin + layoutParams.rightMargin + lineWidth > width) {
                // 记录这一行所有的View中的最大高度
                mLineHeight.add(lineHeight);
                // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
                mAllViews.add(lineViews);
                lineWidth = 0;// 重置行宽
                lineViews = new ArrayList<View>();
            }
            //如果不需要换行,则累加
            lineWidth += childWidth + layoutParams.leftMargin + layoutParams.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + layoutParams.topMargin + layoutParams.bottomMargin);
            lineViews.add(child);
        }
        // 记录最后一行
        mLineHeight.add(lineHeight);
        mAllViews.add(lineViews);
        //记录当前child相对前一个child的坐标位置
        int left = 0;
        int top = 0;
        // 一行一行的遍历
        for (int i = 0; i < mAllViews.size(); i++) {
            // 遍历每一行
            lineViews = mAllViews.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                child = lineViews.get(j);
                if (child.getVisibility() == View.GONE) continue;
                layoutParams = (MarginLayoutParams) child.getLayoutParams();
                //计算child的坐标
                int leftPosition = left + layoutParams.leftMargin;
                int topPosition = top + layoutParams.topMargin;
                int rightPosition = leftPosition + child.getMeasuredWidth();
                int bottomPosition = topPosition + child.getMeasuredHeight();
                //对child进行布局
                child.layout(leftPosition, topPosition, rightPosition, bottomPosition);
                //相对位置右移
                left = rightPosition + layoutParams.rightMargin;
            }
            //相对位置从左侧重头开始,并下移
            left = 0;
            top += mLineHeight.get(i);
        }
    }
}

样式、背景

样式
    <style name="text_style1">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">2dp</item>
        <item name="android:background">@drawable/flowlayout_shape1</item>
        <item name="android:textColor">#ffffff</item>
        <item name="android:paddingLeft">5dp</item>
        <item name="android:paddingRight">5dp</item>
        <item name="android:textSize">11sp</item>
    </style>
    <style name="text_style2">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">2dp</item>
        <item name="android:background">@drawable/flowlayout_shape2</item>
        <item name="android:textColor">#880</item>
        <item name="android:textSize">11sp</item>
    </style>
    <style name="text_style3">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">2dp</item>
        <item name="android:background">@drawable/flowlayout_shape3</item>
        <item name="android:textColor">#43BBE7</item>
        <item name="android:textSize">11sp</item>
    </style>

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

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

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="#C1FFC1" />
    <stroke
        android:width="1dp"
        android:color="#912CEE" />
    <corners android:radius="40dp" />
    <padding
        android:bottom="2dp"
        android:left="10dp"
        android:right="10dp"
        android:top="2dp" />
</shape>

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

  1. Android自定义之流式布局

    流式布局,好处就是父类布局可以自动的判断子孩子是不是需要换行,什么时候需要换行,可以做到网页版的标签的效果.今天就是简单的做了自定义的流式布局. 具体效果: 原理: 其实很简单,Measure  La ...

  2. 28 自定义View流式布局

    流式布局每行的行高以本行中最高的元素作为高,如果一个元素放不下到一行时直接到第二行 FlowLayoutView package com.qf.sxy.customview05.widget; imp ...

  3. Android控件进阶-自定义流式布局和热门标签控件

    技术:Android+java   概述 在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧,类 ...

  4. 自定义流式布局:ViewGroup的测量与布局

    目录 1.View生命周期以及View层级 1.1.View生命周期 1.2.View层级 2.View测量与MeasureSpec类 2.1.MeasureSpec类 2.2.父View的限制 :测 ...

  5. 自定义View(三)--实现一个简单地流式布局

    Android中的流式布局也就是常说的瀑布流很是常见,不仅在很多项目中都能见到,而且面试中也有很多面试官问道,那么什么是流式布局呢?简单来说就是如果当前行的剩余宽度不足以摆放下一个控件的时候,则自动将 ...

  6. Android 自定义View修炼-Android中常见的热门标签的流式布局的实现

    一.概述:在日常的app使用中,我们会在android 的app中看见 热门标签等自动换行的流式布局,今天,我们就来看看如何 自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出哈) 类似的 ...

  7. 【Android - 自定义View】之自定义可滚动的流式布局

    首先来介绍一下这个自定义View: (1)这个自定义View的名称叫做 FlowLayout ,继承自ViewGroup类: (2)在这个自定义View中,用户可以放入所有继承自View类的视图,这个 ...

  8. webapp,liveapp: 流式布局和rem布局

    liveapp场景应用,一般针对的是移动端,近来也是很火,颇有一些感受,拿来分享一下. 页面宽度范围: 一般移动端页面我们的像素范围是320px-640px,最大640px,最小320px,所以设计稿 ...

  9. Android流式布局实现

    查看我的所有开源项目[开源实验室] 欢迎增加我的QQ群:[201055521],本博客client下载[请点击] 摘要 新项目用到了一种全新布局----Android标签流式布局的功能,正好一直说给大 ...

随机推荐

  1. 使用WebFrom来模拟一些MVC的MODEL与View的数据交互功能

    MVC中有一点非常闪瞎人眼的功能就是,可以根据Model与View视图来直接将页面也数据模型进行绑定,那么我们在想客户端发送页面时不需要进行各种控件赋值,不需要操心怎么渲染,当客户提交表单给服务器时也 ...

  2. JDK1.5新特性随手记

    1.静态导入 import static 静态导入前写法: public class TestStatic { public static void main(String[] args) { Sys ...

  3. HDU 4605 Magic Ball Game (在线主席树|| 离线 线段树)

    转载请注明出处,谢谢http://blog.csdn.net/ACM_cxlove?viewmode=contents    by---cxlove 题意:给出一棵二叉树,每个结点孩子数目为0或者2. ...

  4. Mysql主从复制的配置(双机互为主从)

    目的: 让两台mysql服务器可以互为主从提供同步服务. 优点: 1. mysql的主从复制的主要优点是同步"备份", 在从机上的数据库就相当于一个(基本实时)备份库. 2. 在主 ...

  5. php异步调用方法实现示例

    php 异步调用方法   客户端与服务器端是通过HTTP协议进行连接通讯,客户端发起请求,服务器端接收到请求后执行处理,并返回处理结果.   有时服务器需要执行很耗时的操作,这个操作的结果并不需要返回 ...

  6. R for installing package 'omg'

    The time i have tried to install the package named 'PODBC'  and it worked. But now i meet a problem ...

  7. powerpoint无法输入中文怎么办|ppt文本框无法输入中文解决办法

    powerpoint文本框无法输入中文的情况不知大家是否遇到过呢?反正小编是遇到过这样的情况的,简直是急煞人也!那么powerpoint无法输入中文时应该怎么办呢?本节内容中小编就为大家带来ppt文本 ...

  8. C#7.0

    C#7.0中有哪些新特性? 以下将是 C# 7.0 中所有计划的语言特性的描述.随着 Visual Studio “15” Preview 4 版本的发布,这些特性中的大部分将活跃起来.现在是时候来展 ...

  9. strnclmp和strlen函数的用法

    一.strncmp 函数 函数原型: 1.函数原型:int strncmp (const char *s1, const char *s2, size_t  n) 2.头文件: <string. ...

  10. Linux系统编程(37)—— socket编程之原始套接字

    原始套接字的特点 原始套接字(SOCK_RAW)可以用来自行组装IP数据包,然后将数据包发送到其他终端.也就是说原始套接字是基于IP数据包的编程(SOCK_PACKET是基于数据链路层的编程).另外, ...