查看我的全部开源项目【开源实验室】
欢迎加入我的QQ群:【201055521】,本博客客户端下载【请点击】

摘要

新项目用到了一种全新布局————Android标签流式布局的功能,正好一直说给大家讲自定义控件的实现,今天就为大家讲一种android流式布局的实现。
本文原创,转载请注明地址:http://blog.kymjs.com/

正文

在日常的app使用中,我们会在android 的app中看见热门标签等自动换行的流式布局,今天,我们就来看看如何自定义一个类似热门标签那样的流式布局吧(源码下载在下面最后给出)
OpenSourceLaboratory
这个控件并不是我实现的,代码是从网上搜流式布局找到的。我只是为大家讲解一下实现过程以及原理。

先看代码

public class FlowLayout extends ViewGroup {    private float mVerticalSpacing; //每个item纵向间距    private float mHorizontalSpacing; //每个item横向间距    public FlowLayout(Context context) {        super(context);    }    public FlowLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public void setHorizontalSpacing(float pixelSize) {        mHorizontalSpacing = pixelSize;    }    public void setVerticalSpacing(float pixelSize) {        mVerticalSpacing = pixelSize;    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int selfWidth = resolveSize(0, widthMeasureSpec);        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int paddingBottom = getPaddingBottom();        int childLeft = paddingLeft;        int childTop = paddingTop;        int lineHeight = 0;        //通过计算每一个子控件的高度,得到自己的高度        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {            View childView = getChildAt(i);            LayoutParams childLayoutParams = childView.getLayoutParams();            childView.measure(                    getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,                            childLayoutParams.width),                    getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,                            childLayoutParams.height));            int childWidth = childView.getMeasuredWidth();            int childHeight = childView.getMeasuredHeight();            lineHeight = Math.max(childHeight, lineHeight);            if (childLeft + childWidth + paddingRight > selfWidth) {                childLeft = paddingLeft;                childTop += mVerticalSpacing + lineHeight;                lineHeight = childHeight;            } else {                childLeft += childWidth + mHorizontalSpacing;            }        }        int wantedHeight = childTop + lineHeight + paddingBottom;        setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec));    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int myWidth = r - l;        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int childLeft = paddingLeft;        int childTop = paddingTop;        int lineHeight = 0;        //根据子控件的宽高,计算子控件应该出现的位置。        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {            View childView = getChildAt(i);            if (childView.getVisibility() == View.GONE) {                continue;            }            int childWidth = childView.getMeasuredWidth();            int childHeight = childView.getMeasuredHeight();            lineHeight = Math.max(childHeight, lineHeight);            if (childLeft + childWidth + paddingRight > myWidth) {                childLeft = paddingLeft;                childTop += mVerticalSpacing + lineHeight;                lineHeight = childHeight;            }            childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);            childLeft += childWidth + mHorizontalSpacing;        }    }}

从控件创建过程说起

  1. 当这个流式布局在被加载如内存并显示在屏幕上这一过程中,首先会调用view.measure(w,h)这个方法,表示测量view的宽度与高度,其中参数w与h分别表示这个控件的父控件的宽高。
  2. 在view.measure()方法的调用过程中又会调用view本身的一个回调方法,onMeasure(),这个是view自身的一个回调方法,用于让开发者在自定义View的时候重新计算自身的大小。一般会在这个方法中循环遍历,计算出这个控件的全部子孙控件的宽高。
  3. 在View的宽高计算完成以后,考虑将这个控件显示到屏幕的指定位置上,此时view的onLayout()方法会被调用。 一般同时会在这个方法中计算出全部子孙控件在这个控件中的位置。
    可能基本流程有些枯燥,接下来结合代码看看。

流布局的实现

看到onMeasure()方法中的这段:

//通过计算每一个子控件的高度,得到自己的高度for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {        View childView = getChildAt(i);        LayoutParams childLayoutParams = childView.getLayoutParams();        childView.measure(                getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,                        childLayoutParams.width),                getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,                        childLayoutParams.height));        int childWidth = childView.getMeasuredWidth();        int childHeight = childView.getMeasuredHeight();        lineHeight = Math.max(childHeight, lineHeight);        if (childLeft + childWidth + paddingRight > selfWidth) {            childLeft = paddingLeft;            childTop += mVerticalSpacing + lineHeight;            lineHeight = childHeight;        } else {            childLeft += childWidth + mHorizontalSpacing;        }    }

首先通过循环,遍历这个控件的所有子控件,同时调用子控件的measure()方法,这时measure方法的两个参数是控件能给这个子控件的最大宽高(我们都知道的,子控件再大,显示的大小也不能比父控件还大)。这里getChildMeasureSpec()方法的作用是用来计算一个合适子视图的尺寸大小(宽度或者高度),结合我们从子视图的LayoutParams所给出的MeasureSpec信息来获取最合适的结果。比如,如果这个View知道自己的大小尺寸(因为它本身的MeasureSpec的model为Exactly,)并且子视图的大小恰好跟父窗口一样大,父窗口必须用给定的大小去layout子视图
参数含义:spec 父窗口传递给子视图的大小和模式
padding 父窗口的边距,也就是xml中的android:padding
childDimension 子视图想要绘制的准确大小,但最终不一定绘制此值

当得到了每一个子控件的大小以后,再要计算自己的宽高就简单了。
int wantedHeight = childTop + lineHeight + paddingBottom;

同理,在onLayout中的这一句

for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {        View childView = getChildAt(i);        if (childView.getVisibility() == View.GONE) {            continue;        }        int childWidth = childView.getMeasuredWidth();        int childHeight = childView.getMeasuredHeight();        lineHeight = Math.max(childHeight, lineHeight);        if (childLeft + childWidth + paddingRight > myWidth) {            childLeft = paddingLeft;            childTop += mVerticalSpacing + lineHeight;            lineHeight = childHeight;        }        childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);        childLeft += childWidth + mHorizontalSpacing;    } 

首先通过循环遍历,控制每个item子控件的显示位置,如果当前行还能放得下一个item,就放到当前行,如果放不下就放到下一行的最左边。
最终,遍历完成,也就相当于把自己的位置显示完成了。

效果截图

OpenSourceLaboratory

版权声明:本文为博主原创文章,未经博主允许不得转载。

更多相关文章

  1. 布局中文件中【控件间距参数详解以及单位选择】
  2. Android—TextView的XML属性和方法
  3. android 各种控件颜色值的设置(使用Drawable,Color)
  4. Android 基本控件及表单三大控件,事件处理
  5. Android来电铃声默认设置的实现方法与怎么设置语音来电的默认铃
  6. Android L新控件RecyclerView简介
  7. NDK开发历程(一):android native code的调试方法
  8. Android ListView拖动时背景变黑的解决方法

随机推荐

  1. 简单播放系统提示音 android
  2. Android Binder设计与实现 – 设计篇
  3. android - Shader 着色器 翻译
  4. Android 技巧 - 网络可用性侦测
  5. Android全屏设置
  6. ReactNative js判断android还是ios
  7. Android实时抓取日志,生成文件
  8. Android UI控件的初识
  9. 2012/3/1 Android导入包错误
  10. Android中坐标系相关的问题