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

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

自定义CustomViewGroup.java如下

public class CustomViewGroup extends ViewGroup {

int mCellWidth;

int mCellHeight;

public CustomViewGroup(Context context, AttributeSet attrs) {

super(context, attrs);

mCellWidth = 120;

mCellHeight = 120;

}

public CustomViewGroup(Context context) {

super(context);

}

public void setCellWidth(int width) {

mCellWidth = width;

requestLayout();

}

public void setCellHeight(int height) {

mCellHeight = height;

requestLayout();

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int cellWidthSpec = MeasureSpec.makeMeasureSpec(mCellWidth,

MeasureSpec.AT_MOST);

int cellHeightSpec = MeasureSpec.makeMeasureSpec(mCellHeight,

MeasureSpec.AT_MOST);

int count = getChildCount();

for (int i = 0; i < count; i++) {

View childView = getChildAt(i);

measureChild(childView, cellWidthSpec, cellHeightSpec);

// childView.measure(cellWidthSpec, cellHeightSpec);

}

// 使用父容器给我们的尺寸和计算出的尺寸进行比较,选择正确的尺寸设置容器控件所占区域大小

setMeasuredDimension(resolveSize(mCellWidth * count, widthMeasureSpec),

resolveSize(mCellHeight * count, heightMeasureSpec));

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

int cellWidth = mCellWidth;

int cellHeight = mCellHeight;

int columns = (r - l) / cellWidth;

if (columns < 0) {

columns = 1;

}

int x = 0;// 横坐标

int y = 0;// 纵坐标

int i = 0;

int count = getChildCount();

for (int index = 0; index < count; index++) {

final View childView = getChildAt(index);

int cWidth = childView.getMeasuredWidth(); // childView的宽度

int cHeight = childView.getMeasuredHeight();

int left = x + ((cellWidth - cWidth) / 2);// 相对父容器,左边的位置

int top = y + ((cellHeight - cHeight) / 2);

childView.layout(left, top, left + cWidth, top + cHeight);

if (i >= (columns - 1)) {

// 转到下一行

i = 0;

x = 0;

y += cellHeight;

} else {

i++;

x += cellWidth;

}

}

}

}

在xml布局中使用CustomViewGroup

<?xml version="1.0" encoding="utf-8"?>

<com.example.view.CustomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res/com.example.activity"

android:id="@+id/viewgroup_main_flow"

android:layout_width="match_parent"

android:layout_height="match_parent" >

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_launcher" />

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_launcher" />

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_launcher" />

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_launcher" />

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_launcher" />

<ImageView

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/ic_launcher" />

</com.example.view.CustomViewGroup>

效果如下:

因为在创建CustomViewGroup时,系统会调用构造函数初始化

public CustomViewGroup(Context context, AttributeSet attrs) {

super(context, attrs);

mCellWidth = 120;

mCellHeight = 120;

}

因此所有子控件的单元格都设置为120,当然也可以通过代码改变控件大小,如下:

CustomViewGroup customViewGroup=(CustomViewGroup) findViewById(R.id.viewgroup_main_flow);

customViewGroup.setCellWidth(200);

customViewGroup.setCellHeight(200);

但是这个例子有很大的局限性——那就是每个控件的大小都是一样的,如果我们的子控件不相同怎么办?

直接盗用鸿洋大神的劳动成果了,看代码

CustomViewGroup.java如下

public class CustomViewGroup extends ViewGroup {

public CustomViewGroup(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected ViewGroup.LayoutParams generateLayoutParams(

ViewGroup.LayoutParams p) {

return new MarginLayoutParams(p);

}

@Override

public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {

return new MarginLayoutParams(getContext(), attrs);

}

@Override

protected ViewGroup.LayoutParams generateDefaultLayoutParams() {

return new MarginLayoutParams(LayoutParams.MATCH_PARENT,

LayoutParams.MATCH_PARENT);

}

/**

* 负责设置子控件的测量模式和大小,根据所有子控件设置自己的宽和高

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

// 获得ViewGroup的父容器为它设置的测量模式和大小

int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);

int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

int modeWidth = MeasureSpec.getMode(widthMeasureSpec);

int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

// 如果是warp_content,记录宽和高

int width = 0;

int height = 0;

/**

* 记录每一行的宽度,width取lineWidth最大宽度

*/

int lineWidth = 0;

/**

* 每一行的高度,累加至height

*/

int lineHeight = 0;

int count = getChildCount();

// 遍历每个子元素

for (int i = 0; i < count; i++) {

View childView = getChildAt(i);

// 测量每一个child的宽和高

measureChild(childView, widthMeasureSpec, heightMeasureSpec);

// 得到childView的lp

MarginLayoutParams lp = (MarginLayoutParams) childView

.getLayoutParams();

// 当前子空间实际占据的宽度

int childWidth = childView.getMeasuredWidth() + lp.leftMargin

+ lp.rightMargin;

// 当前子空间实际占据的高度

int childHeight = childView.getMeasuredHeight() + lp.topMargin

+ lp.bottomMargin;

/**

* 如果加入当前childView,则超出最大宽度,则得到目前最大宽度给width,类加height 然后开启新的一行

*/

if (lineWidth + childWidth > sizeWidth) {

width = Math.max(lineWidth, childWidth);// 取最大的

lineWidth = childWidth; // 重新开启新行,开始记录

// 叠加当前高度,

height += lineHeight;

// 开启记录下一行的高度

lineHeight = childHeight;

} else

// 否则累加值lineWidth,lineHeight取最大高度

{

lineWidth += childWidth;

lineHeight = Math.max(lineHeight, childHeight);

}

// 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较

if (i == count - 1) {

width = Math.max(width, lineWidth);

height += lineHeight;

}

}

setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth

: width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight

: height);

}

/**

* 存储所有的View,按行记录

*/

private List<List<View>> mAllViews = new ArrayList<List<View>>();

/**

* 记录每一行的最大高度

*/

private List<Integer> mLineHeight = new ArrayList<Integer>();

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

mAllViews.clear();

mLineHeight.clear();

int width = getWidth();

int lineWidth = 0;

int lineHeight = 0;

// 存储每一行所有的childView

List<View> lineViews = new ArrayList<View>();

int count = getChildCount();

// 遍历所有的孩子

for (int i = 0; i < count; i++) {

View childView = getChildAt(i);

MarginLayoutParams lp = (MarginLayoutParams) childView

.getLayoutParams();

int childWidth = childView.getMeasuredWidth();

int childHeight = childView.getMeasuredHeight();

// 如果已经需要换行

if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {

// 记录这一行所有的View以及最大高度

mLineHeight.add(lineHeight);

// 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView

mAllViews.add(lineViews);

lineWidth = 0;// 重置行宽

lineViews = new ArrayList<View>();

}

/**

* 如果不需要换行,则累加

*/

lineWidth += childWidth + lp.leftMargin + lp.rightMargin;

lineHeight = Math.max(lineHeight, childHeight + lp.topMargin

+ lp.bottomMargin);

lineViews.add(childView);

}

// 记录最后一行

mLineHeight.add(lineHeight);

mAllViews.add(lineViews);

int left = 0;

int top = 0;

// 得到总行数

int lineNums = mAllViews.size();

for (int i = 0; i < lineNums; i++) {

// 每一行的所有的views

lineViews = mAllViews.get(i);

// 当前行的最大高度

lineHeight = mLineHeight.get(i);

// 遍历当前行所有的View

for (int j = 0; j < lineViews.size(); j++) {

View child = lineViews.get(j);

if (child.getVisibility() == View.GONE) {

continue;

}

MarginLayoutParams lp = (MarginLayoutParams) child

.getLayoutParams();

// 计算childView的left,top,right,bottom

int lc = left + lp.leftMargin;

int tc = top + lp.topMargin;

int rc = lc + child.getMeasuredWidth();

int bc = tc + child.getMeasuredHeight();

child.layout(lc, tc, rc, bc);

left += child.getMeasuredWidth() + lp.rightMargin

+ lp.leftMargin;

}

left = 0;

top += lineHeight;

}

}

}

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

看下activity_main.xml

<com.example.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" >

<TextView

style="@style/text_style"

android:background="@drawable/flag_01"

android:text="Welcome" />

<TextView

style="@style/text_style"

android:background="@drawable/flag_01"

android:text="You" />

<TextView

style="@style/text_style"

android:background="@drawable/flag_02"

android:text="自定义" />

<TextView

style="@style/text_style"

android:background="@drawable/flag_02"

android:text="ViewGroup" />

<TextView

style="@style/text_style"

android:background="@drawable/flag_03"

android:text="努力" />

<TextView

style="@style/text_style"

android:background="@drawable/flag_03"

android:text="学习" />

<TextView

style="@style/text_style"

android:background="@drawable/flag_04"

android:text="Try you best" />

</com.example.view.CustomViewGroup>

实现效果如下:

篇幅有限,先说这么多吧

源代码

参考:

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

时间: 2024-08-04 23:22:51

ViewGroup2——自定义实现流式布局的相关文章

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 = 

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

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

Android之自定义流式布局FlowLayout

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

自定义流式布局

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

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中看见热门标签等自动换行的流式布局,今天就为大家分享一种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