Android高级进阶——自定义View实践篇(一)自定义标签流布局

本文介绍了Android自定义View的实践,特别是自定义了一个标签流容器FlowLayout。文章涵盖了自定义View的分类,注意事项,以及FlowLayout的onMeasure和onLayout方法的实现,同时讨论了如何处理子View的添加和padding、margin的支持。最后,文章给出了FlowLayout的完整代码,并预告了接下来可能实现的点击拖拽排序功能。
摘要由CSDN通过智能技术生成

开篇

前面已经介绍了一系列的 View 的自定义,后面的几篇会找几个实际的例子来动手练一下,今天就先瞅瞅 标签流容器

先给出效果图:

image.png

这个自定义 View 是非常简单的,只要你把前面的 view 的工作原理一、二、三 大致看一遍就可以很轻松的撸出来

自定义 View 的种类

自定义 View 的分类标准不唯一,大致可以分为 4 类

  • 1、继承 View 重写 onDraw 方法

这种方法主要用于实现一些不规则的效果,即这种效果不方便通过布局的组合方式来达到,往往需要静态或者动态地显示一些不规则的图形。很显然这需要通过绘制的方式来实现,即重写 onDraw 方法。采用这种方式需要自己支持 wrap_content,并且 padding 也需要自己处理。

  • 2、继承 ViewGroup 派生特殊的 Layout

这种方法主要用于实现自定义的布局,即除了 LinearLayout、RelativeLayout、FrameLayout 这几种系统的布局之外,我们重新定义一种新布局,当某种效果看起来很像几种 View 组合在一起的时候,可以采用这种方法来实现。采用这种方式稍微复杂一些,需要合适地处理 ViewGroup 的测量、布局这两个过程,并同时处理子元素的测量和布局过程。

  • 3、继承特定的 View (比如 TextView)

    这种方法比较常见,一般是用于扩展某种已有的 View 的功能,比如 TextView,这种方法比较容易实现。这种方法不需要自己支持 wrap_content 和 padding 等。

  • 4、继承特定的 ViewGroup (比如 LinearLayout)

这种方法也比较常见,当某种效果看起来很像几种 View 组合在一起的时候,可以采用这种方法来实现。采用这种方法不需要自己处理 ViewGroup 的测量和布局这两个过程。需要注意这种方法和方法 2 的区别,一般来说 方法 2 能实现的效果方法 4 也都能实现,两者的主要差别在于 方法 2 更接近 View 的底层。

自定义 View 常见注意事项

这里我们会列举一些自定义 View 过程中的一些注意事项,这些问题如果处理不好,有些会影响 View 的正常使用,而有些会导致内存泄漏等。

  • 1、让 View 支持 wrap_content

这是因为直接继承 View 或者 ViewGroup 的控件,如果不在 onMeasure 中对 wrap_content 做特殊处理,那么外界在布局中使用 wrap_content 时就无法达到预期的效果,这个就不在这里细说了,有兴趣的可以去看一下我 CSDN 上的简单介绍 Android——View的工作原理(一)

  • 2、如果有必要,让你的 View 支持 padding

这是因为直接继承 View 的控件,如果不在 draw 方法中处理 padding,那么 padding 属性是无法起作用的。另外,直接继承自 ViewGroup 的控件需要在 onMeasure 和 onLayout 中考虑 padding 和 子元素的 margin 对其造成的影响,不然将导致 padding 和 子元素的 margin 失效。

  • 3、尽量不要在 View 中使用 Handler,没必要

这是因为 View 内部本身就提供了 post 系列方法,完全可以替代 Handler 的作用,当然除非你很明确地要使用 Handler 来发送消息。

  • 4、View 中如果有线程或动画,需要及时停止,参考 View#onDetachedFromWindow

这一条也很好理解,如果有线程或者动画需要停止时,那么 onDetachedFromWindow 方法是一个很好的时机。当包含此 View 的 Activity 退出或者当前 View 被 remove 时,View 的 onDetachedFromWindow 方法会被调用,和此方法对应的是 onAttachedToWindow 方法,当包含此 View 的 Activity 启动时,View 的 onAttachedToWindow 方法会被调用。同时,当 View 变得不可见时我们也需要停止线程和动画,如果不及时处理这种问题,有可能会造成内存泄漏。

  • 5、View 带有滑动嵌套情形时,需要处理号滑动冲突

如果有滑动冲突的话,那么要合适地处理滑动冲突,否则将会严重影响 View 的效果

自定义 标签流容器

onMeasure 方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int usedWidth = 0;      //已使用的宽度
        int remaining = 0;      //剩余可用宽度
        int totalHeight = 0;    //总高度
        int lineHeight = 0;     //当前行高
        int maxLineHeight = 0;  //最大行高

        //for 循环遍历 子 view
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
            //获取 layoutParams
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            if (widthMode == MeasureSpec.AT_MOST) {
                throw new RuntimeException("FlowLayout 的 \"layout_width\" 必须为 \"match_parent\" 或者 精确数值");
            } else {
                //测量 子 view
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
                // 剩余可用 width
                remaining = widthSize - usedWidth - getPaddingLeft() - getPaddingRight();
                //当剩余空间不足以放下一个新 view 时,换行
                if (childView.getMeasuredWidth() > remaining) {
                    //累加高度,用于作为当前 FlowLayout 的最终高度
                    totalHeight += maxLineHeight;
                    //重置
                    maxLineHeight = 0;
                    usedWidth = 0;
                }
                //已使用 width 进行 累加
                usedWidth += lp.leftMargin + lp.rightMargin + childView.getMeasuredWidth();
                //当前 view 的高度
                lineHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
                //取出每行 view 的最大高度
                maxLineHeight = Math.max(lineHeight, maxLineHeight);
            }
        }

        //最终高度,记得加上最后一行的view 的高度
        totalHeight += maxLineHeight + getPaddingTop() + getPaddingBottom();
        if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = totalHeight;
        }
        //去较大的一个作为 FlowLayout 的最终高度
        heightSize = Math.max(totalHeight, heightSize);
        setMeasuredDimension(widthSize, heightSize);
    }

其实就是一个遍历的过程,通过遍历获取子 view 的 layoutParams,然后进行一个模拟排版过程,最终拿到 FlowLayout 的最终高度࿰

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值