每日一学——自定义View(原理+具体实现代码)(二)

常见的概念

  • xml:采用序列化,反射,通过 setContenView()方法绑定布局

  • LayoutPrames是什么?
    解析后的布局参数

  • MearSureScap是什么?
    是View中的内部类,基本都是二进制。由于int是32位的,用高两位表示mode
    低30位表示size,MODE_SHIFT=30的作用是移位

    3种情况:

MearSureScap 的 modesize含义
UNSPECIFIED不对View大小做限制,系统使用
EXACTLY切确的大小,如100dp
AT_MOST大小不超过某个值,如不超过 父容器

!](https://img-blog.csdnimg.cn/20200526210328536.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0xlbW9uX3d6cQ==,size_16,color_FFFFFF,t_70)
看上面的图不好理接,看下面的图你就懂这三种模式:
如图:

在这里插入图片描述
具体说明:
对于应用层 View ,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定 对于不同的父容器和view本身不同的LayoutParams,view就可以有多种MeasureSpec。

  • 当view采用固定宽 高的时候,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精确模式并且其大小遵循
    Layoutparams中的大小;

  • 当view的宽高是match_parent时,这个时候如果父容器的模式是精准模式,那么
    view也是精准模式并且其大小是父容器的剩余空间,如果父容器是最大模式,那么view也是最大模式并且其大 小不会超过父容器的剩余空间;

  • 当view的宽高是wrap_content时,不管父容器的模式是精准还是最大化,
    view的模式总是最大化并且大小不能超过父容器的剩余空间。

  • Unspecified模式,这个模式主要用于系统内
    部多次measure的情况下,一般来说,我们不需要关注此模式(这里注意自定义View放到ScrollView的情况 需要 处理)。

具体实现

在这里插入图片描述

  • onMeasure()测量
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        initMeasureParams();//初始化每一行(子View和行高)
         //度量子view的大小
        //度量所有的子View
        int childCount = getChildCount();
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();

        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);  //ViewGroup解析的宽度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的高度

        List<View> lineViews = new ArrayList<>(); //保存一行中的所有的view
        int lineWidthUsed = 0; //记录这行已经使用了多宽的size
        int lineHeight = 0; // 一行的行高

        int parentNeededWidth = 0;  // measure过程中,子View要求的父ViewGroup的宽
        int parentNeededHeight = 0; // measure过程中,子View要求的父ViewGroup的高

        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);

            LayoutParams childLP = childView.getLayoutParams();
            //将layoutParams转变为measureSpec
            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,
                    childLP.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,
                    childLP.height);

            childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            //获取子view的宽高
            int childMesauredWidth = childView.getMeasuredWidth();
            int childMeasuredHeight = childView.getMeasuredHeight();

            //通过宽度来判断是否需要换行,通过换行后的每行的行高来获取整个viewGroup的行高
            //如果需要换行
            if (childMesauredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
                allLines.add(lineViews);
                lineHeights.add(lineHeight);
                //注意重置不同生命周期的子view

                //一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
                parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing;
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing);

                lineViews = new ArrayList<>();
                lineWidthUsed = 0;
                lineHeight = 0;
            }
            // view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
            lineViews.add(childView);
            //每行都会有自己的宽和高
            lineWidthUsed = lineWidthUsed + childMesauredWidth + mHorizontalSpacing;
            lineHeight = Math.max(lineHeight, childMeasuredHeight);//以一行的最高为行高

            //如果当前childView是最后一行的最后一个
            if (i == childCount - 1) {//最后一行
                lineHeights.add(lineHeight);
                allLines.add(lineViews);
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed);
                parentNeededHeight += lineHeight;
            }

        }
        //根据子View的度量结果,来重新度量自己ViewGroup
        // 作为一个ViewGroup,它自己也是一个View,它的大小也需要根据它的父亲给它提供的宽高来度量
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth: parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ?selfHeight: parentNeededHeight;

        setMeasuredDimension(realWidth,realHeight);
    }
  • onLayout()测量
 /**
    将所有的子View布局到屏幕上,同时,由于FlowLayout自己没有特殊要求所以不需要对自己布局
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int lineCount = allLines.size();//有多少行
        /**
         * //左边界
         */
        int curL = getPaddingLeft();
        Log.d(TAG, "onLayout: getLeft(): " + getLeft());
        /**
         * 上边界
         */
        int curT = getPaddingTop();
        Log.d(TAG, "onLayout: getTop(): " + getTop());
        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = allLines.get(i);//一行有多少view
            int lineHeight = lineHeights.get(i);
            for (int j = 0; j < lineViews.size(); j++) {
                View view = lineViews.get(j);
                int left = curL;//左边界
                int top = curT;//右边界
                /**
                 * getHeight() 在layout()结束之后才能获取到—通过视图右边的坐标减去做百年的左边计算出来的
                 */
//                int bottom = top + view.getHeight();
//                int right = left + view.getWidth();
                /**
                 * getMeasureHeight()在onmesure()过程结束后就能获取到—通过setMeasureDimension()方法来进行设置的
                 */
                int bottom = top + view.getMeasuredHeight();
                int right = left + view.getMeasuredWidth();
                view.layout(left,top, right,bottom);//对应上面的childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                curL = right + mHorizontalSpacing;
            }
            curL = getPaddingLeft();
            curT = curT + lineHeight + mVerticalSpacing;
        }
    }

ViewPager的实现原理

  • 后续的自定义实战请看下一篇博客:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值