美文网首页
Android 流式布局FlowLayout

Android 流式布局FlowLayout

作者: 水言 | 来源:发表于2017-11-15 15:27 被阅读148次

流式布局一般会用在搜索页面展示热门搜索,看着错乱但是视觉效果还是不错的。Android 原生控件关于这个没有很好的实现,所以如果开发中有需求的话需要自己动手实现。

实现的效果如下:


效果图

自定义LayoutParams

ViewGroup的LayoutParams用于存储了子View在加入ViewGroup中时的一些参数信息。如果需要可以新建一个自定义的LayoutParams类,就像SDK中我们熟悉的LinearLayout.LayoutParams,RelativeLayout.LayoutParams类等一样。

为了方便记录子控件在父容器的位置,我们在这里定义了一个XYLayoutParams,其中的X和Y就是子控件相对父控件的位置信息。

   public static class XYLayoutParams extends ViewGroup.MarginLayoutParams {
        private int x;
        private int y;
        public XYLayoutParams(Context context, AttributeSet attributeSet) {
            super(context, attributeSet);
        }
        public XYLayoutParams(int width, int height) {
            super(width, height);
        }
        public XYLayoutParams(ViewGroup.LayoutParams layoutParams) {
            super(layoutParams);
        }
        public void setPosition(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }

向父容器中addView的时候会创建LayoutParams对象,想使用自定义的LayoutParams需要从写下面的四个方法:

   @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof XYLayoutParams;
    }
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new XYLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attributeSet) {
        return new XYLayoutParams(getContext(), attributeSet);
    }
    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new XYLayoutParams(p);
    }

原因是可以看ViewGroup中的addView源码:
generateDefaultLayoutParams(...)用于创建默认LayoutParams

 public void addView(View child, int index) {
        ...
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
        ...
        }
        addView(child, index, params);
    }

addView(...)会调用到addViewInner(...),其中会检测LayoutParams的类型,并调用generateLayoutParams(ViewGroup.LayoutParams p)从新创建
LayoutParams 对象

  private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        ...
        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
      ...

测量每个子View的大小

关于View换行
默认使用从左往右的横向排版,遍历测量子View的大小,连续记录子view的宽度,当新的View宽度加上之前记录的宽度大于父布局宽度的时候,那么换行,记录宽度值,行宽总和清零。

关于父View的宽高
1.当上述所需换行的时候记录换行前的宽度,对比每次换行的宽度,记录宽度最大值
2.换行后累加上上一行所需高度的最大值,记录为所需最小高度。
3.根据测绘模式决定FlowLayout最后的宽高

onMeasure()方法如下,主要流程是:首先获取父控件的大小和父控件的测绘模式,
遍历所有的View 根据上述所描述的换行规则依次排序,记录每个View的LayoutParams,记录所需最小的宽高。最后根据父控件的测绘模式决定父控件的最终大小。

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取控件的宽高和模式
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        //模式是wrap_content的时候需要最小宽高
        int width = 0;
        int height = 0;
        //当前行的宽高
        int lineWidth = 0;
        int lineHeight = 0;

        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }

            //测绘child
            XYLayoutParams lp = (XYLayoutParams) child.getLayoutParams();
            final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                    getPaddingLeft() + this.getPaddingRight()+horizontalSpace*2, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                    getPaddingTop() + getPaddingBottom()+verticalSpace*2, lp.height);
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            //相对父控件的位置
            int posX = 0;
            int posY = 0;
            //获取当前child所需的宽高
            int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + horizontalSpace;
            int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin + verticalSpace;
            //当前行的宽度加上child的宽度超过当前行允许的宽度,就换行
            if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()-horizontalSpace*2) {
                //需要换行
                width = Math.max(width, lineWidth);
                lineWidth = childWidth;
                height += lineHeight;
                lineHeight = childHeight;
                posX = getPaddingLeft() + horizontalSpace;
                posY = getPaddingTop() + height + verticalSpace;
            } else {
                //不需要换行
                posX = getPaddingLeft() + lineWidth + horizontalSpace;
                posY = getPaddingTop() + height + verticalSpace;
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            lp.setPosition(posX, posY);
        }
        //实际控件所需的宽高加上最后一行
        width = Math.max(lineWidth, width);
        height += lineHeight;

        setMeasuredDimension(
                //如果是精确测绘模式就用原来的数据,不然就用计算所需的大小。
                modeWidth == MeasureSpec.EXACTLY ? sizeWidth : width + getPaddingLeft() + getPaddingRight(),
                modeHeight == MeasureSpec.EXACTLY ? sizeHeight : height + getPaddingTop() + getPaddingBottom()//
        );
    }

确定每个子View相对父View的位置

由于在onMeasure的时候使用自定义的XYLayoutParams记录了每个子View相对父容器的位置,所以在OnLayout中只需要取出其对应位置,然后调用子View的layout方法。

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE)
            {
                continue;
            }

            XYLayoutParams lp = (XYLayoutParams) child.getLayoutParams();
            child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight());
        }
    }

项目地址:链接:http://pan.baidu.com/s/1sl1Dkpn 密码:no8x

相关文章

网友评论

      本文标题:Android 流式布局FlowLayout

      本文链接:https://www.haomeiwen.com/subject/xhhdextx.html