自定义ViewGroup 流式布局

使用



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="[email protected]" />         <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>

来自为知笔记(Wiz)

时间: 2024-10-23 22:04:36

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

Android自定义之流式布局

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

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

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

100行Android代码自定义一个流式布局-FlowLayout

首先来看一下 手淘HD - 商品详情 - 选择商品属性 页面的UI 商品有很多尺码,而且展现每个尺码所需要的View的大小也不同(主要是宽度),所以在从服务器端拉到数据之前,展现所有尺码所需要的行数和每一行的个数都无法确定,因此不能直接使用GridView或ListView. 如果使用LinearLayout呢? 一个LinearLayout只能显示一行,如果要展示多行,则每一行都要new一个LinearLayout出来,而且还必须要计算出每一个LinearLayout能容纳多少个尺码对应的Vi

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之自定义流式布局FlowLayout

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

自定义流式布局

1.概述 何为FlowLayout,就是控件根据ViewGroup的宽,自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行.有点所有的控件都往左飘的感觉,第一行满了,往第二行飘~所以也叫流式布局.Android并没有提供流式布局,但是某些场合中,流式布局还是非常适合使用的,比如关键字标签,搜索热词列表等,比如下图: 这些都特别适合使用FlowLayout 2.简单的分析 1.对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用Mar

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中常见的热门标签的流式布局的实现

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

流式布局的实现-1

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