前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >自定义View三问—字节真题

自定义View三问—字节真题

作者头像
码上积木
发布2020-10-29 16:43:52
发布2020-10-29 16:43:52
52300
代码可运行
举报
文章被收录于专栏:码上积木码上积木
运行总次数:0
代码可运行

星期一的早上,还没从假期缓过来的你,遇到产品给的新需求,要做一个你没看过的View,是不是有点崩溃。哎,抹干眼泪,拿起自定义View开始埋头苦干吧~

  • 说说View的绘制流程
  • 说说你理解的MeasureSpec
  • Scroller是怎么实现View的弹性滑动?

说说View/ViewGroup的绘制流程

View的绘制流程是从ViewRootperformTraversals开始的,它经过measure,layout,draw三个过程最终将View绘制出来。performTraversals会依次调用performMeasure,performLayout,performDraw三个方法,他们会依次调用measure,layout,draw方法,然后又调用了onMeasure,onLayout,dispatchDraw

  • measure :

对于自定义的单一view的测量,只需要根据父 view 传递的MeasureSpec进行计算大小。

对于ViewGroup的测量,一般要重写onMeasure方法,在onMeasure方法中,父容器会对所有的子View进行Measure,子元素又会作为父容器,重复对它自己的子元素进行Measure,这样Measure过程就从DecorView一级一级传递下去了,也就是要遍历所有子View的的尺寸,最终得出出总的viewGroup的尺寸。Layout和Draw方法也是如此。

  • layout :根据 measure 子 View 所得到的布局大小和布局参数,将子View放在合适的位置上。

对于自定义的单一view,计算本身的位置即可。

对于ViewGroup来说,需要重写onlayout方法。除了计算自己View的位置,还需要确定每一个子View在父容器的位置以及子view的宽高(getMeasuredWidth和getMeasuredHeight),最后调用所有子view的layout方法来设定子view的位置。

  • draw :把 View 对象绘制到屏幕上。

draw()会依次调用四个方法:

1)drawBackground(),根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界。2)onDraw(),绘制View本身的内容,一般自定义单一view会重写这个方法,实现一些绘制逻辑。3) dispatchDraw(),绘制子View 4)onDrawScrollBars(canvas),绘制装饰,如 滚动指示器、滚动条、和前景

说说你理解的MeasureSpec

MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。

  • 首先,MeasureSpec是一个大小跟模式的组合值,MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,其中高两位是mode,后面30位存的是size
代码语言:javascript
代码运行次数:0
复制
    // 获取测量模式
    int specMode = MeasureSpec.getMode(measureSpec)

    // 获取测量大小
    int specSize = MeasureSpec.getSize(measureSpec)

    // 通过Mode 和 Size 生成新的SpecMode
    int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
  • 其次,每个子View的MeasureSpec值根据子View的布局参数和父容器的MeasureSpec值计算得来的,所以就有一个父布局测量模式,子视图布局参数,以及子view本身的MeasureSpec关系图:

其实也就是getChildMeasureSpec方法的源码逻辑,会根据子View的布局参数和父容器的MeasureSpec计算出来单个子view的MeasureSpec。

  • 最后是实际应用时:

对于自定义的单一view,一般可以不处理onMeasure方法,如果要对宽高进行自定义,就重写onMeasure方法,并将算好的宽高通过setMeasuredDimension方法传进去。对于自定义的ViewGroup,一般需要重写onMeasure方法,并且调用measureChildren方法遍历所有子View并进行测量(measureChild方法是测量具体某一个view的宽高),然后可以通过getMeasuredWidth/getMeasuredHeight获取宽高,最后通过setMeasuredDimension方法存储本身的总宽高。

Scroller是怎么实现View的弹性滑动?

  • MotionEvent.ACTION_UP事件触发时调用startScroll()方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量(滑动距离、滑动时间)
  • 接着调用invalidate/postInvalidate()方法,请求View重绘,导致View.draw方法被执行
  • 当View重绘后会在draw方法中调用computeScroll方法,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法来进行第二次重绘,和之前流程一样,如此反复导致View不断进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,直到整个滑动过成结束。
代码语言:javascript
代码运行次数:0
复制

mScroller = new Scroller(context);


@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                // 滚动开始时X的坐标,滚动开始时Y的坐标,横向滚动的距离,纵向滚动的距离
                mScroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

@Override
    public void computeScroll() {
        // 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

参考

https://www.jianshu.com/p/1dab927b2f36

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码上积木 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说说View/ViewGroup的绘制流程
  • 说说你理解的MeasureSpec
  • Scroller是怎么实现View的弹性滑动?
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档