Android高级UI之自定义FlowLayout

一、自定义View之前要了解的知识点

1、自定义 View 和自定义 ViewGroup 之间的区别

自定义父类控件 需重写的方法
自定义View onMeasure() + onDraw()
自定义ViewGroup onMeasure() + onLayout()

View 在Activity 中显示出来要经历测量、布局和绘制三个步骤,分别对应measure、layout、draw,其中

  • 测量:onMeasure() 决定 View 的大小
  • 布局:onLayout()决定 View 在 ViewGroup 中的位置
  • 绘制: onDraw()决定绘制这个 View

各个方法的功能都知道了,为啥自定义 View 和自定义 ViewGroup重写的方法不一样呢?
这是个人理解,因为View是布局的最小单位,是各种组件的基类,而ViewGroup继承自View,是一个布局容器,负责将控制容器中的View的位置就行(对应onLayout());

2、自定义View的绘制流程
在这里插入图片描述
3、View的构造方法
view的构造方法一共有四个(第四个很少用到,具体什么时候用不太清楚)

 // 如果View是在Java代码里面new的,则调用第一个构造函数
 public XFlowLayout(Context context) {
        super(context);
}

// 如果View是在.xml里声明的,则调用第二个构造函数 , 自定义属性是从AttributeSet传进来的
 public XFlowLayout(Context context, AttributeSet attrs) {
     super(context, attrs);
 }

 // 不会自动调用 , 一般是在第二个构造函数里主动调用
 public XFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
     super(context, attrs, defStyleAttr);
 }

4、Android坐标系的认识
Android坐标系:屏幕左上角为坐标原点,向右为 x 增大方向,向下为 y 增大方向,其 View 的位置是相对父控件而言的:

  • top : 子View上边界到父View上边界的距离(View 的位置通过view.getTop()获取)
  • bottom: 子View下边界到父View上边界的距离(View 的位置通过view.getBottom()获取)
  • left : 子View左边界到父View左边界的距离(View 的位置通过view.getLeft()获取)
  • right : 子View右边界到父View左边界的距离(View 的位置通过view.getRight()获取)

另外,还有一个概念要分清,MotionEvent中 get()和getRaw()的区别

  • get 是触摸点相对于其所在组件坐标系的坐标—— event.getX(); event.getY();
  • getRow 是触摸点相对于屏幕默认坐标系的坐标—— event.getRawX(); event.getRawY();

5、LayoutParams
这是一个布局参数,子View通过LayoutParams告诉父容器(ViewGroup)应该如何放置自己,将对应宽高的值传给下面方法的第三个参数(其中MATCH_PARENT的值是-1 ,WRAP_CONTENT的值是-2,若设置了具体的宽高对应的值则是大于0),重点就是将LayoutParams转换成MeasureSpec,代码逻辑如下(ViewGroup源码):

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);//父控件的测量模式
        int specSize = MeasureSpec.getSize(spec);//父控件的测量大小

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

以上逻辑可以通过生活中的一个现象来帮助理解:

在这里插入图片描述
在这里插入图片描述
6、MeasureSpec
MeasureSpec 是 View下的内部类,封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode 有如下三种:

  • UNSPECIFIED:父控件没有任何限制----相当于生活事例中的第三种情况
  • EXACTLY:父控件已经知道你所需的精确大小,你的最终大小应该就是这么大----相当于生活事例中的第一种情况
  • AT_MOST:你的大小不能大于父控件给你指定的size,但具体是多少,得看你自己的实现----相当于生活事例中的第一种情况

二、自定义 FlowLayout 的实现

第一步,创建好新的类继承ViewPager,写好构造函数,重写 onMeasure() 和 onLayout()方法;
第二步:准备处理 onMeasure()的度量逻辑,在此之前要确定是先度量子View的宽高来确定父View的宽高,还是先度量父View来确定子View的宽高(目前只有ViewPager是这样),选择大势所趋的先子后父。先搭出框架:

public class XFlowLayout extends ViewGroup {
    public XFlowLayout(Context context) {
        super(context);
    }
    
    public XFlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public XFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值