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

摘要

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

正文

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

这个控件并不是我实现的,代码是从网上搜流式布局找到的。我只是为大家讲解一下实现过程以及原理。

先看代码

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,就放到当前行,如果放不下就放到下一行的最左边。
最终,遍历完成,也就相当于把自己的位置显示完成了。

效果截图

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

更多相关文章

  1. Android(安卓)开发中使用 SQLite 数据库
  2. 布局中文件中【控件间距参数详解以及单位选择】
  3. Android—TextView的XML属性和方法
  4. Android(安卓)开发中使用 SQLite 数据库
  5. android中的content provider的使用
  6. Android与H5混合开发「kotlin,WebView」
  7. Android之——自定义TextView
  8. Android(安卓)Launcher 分析
  9. Android—TextView的XML属性和方法

随机推荐

  1. Android Launcher3去除应用列表,二级菜单,
  2. ViewPagerIndicator使用
  3. Android Studio 调试工具常见问题
  4. React Native與Android交互
  5. android 获取以太网的连接状态
  6. Android中如何获得本机号码信息
  7. 整理一下Android中的ListView
  8. android protobuf错误
  9. 使用Android Studio解决Android 65k问题
  10. Android Adapter