星期一的早上,还没从假期缓过来的你,遇到产品给的新需求,要做一个你没看过的View,是不是有点崩溃。哎,抹干眼泪,拿起自定义View开始埋头苦干吧~
View的绘制流程是从ViewRoot
的performTraversals
开始的,它经过measure,layout,draw
三个过程最终将View绘制出来。performTraversals会依次调用performMeasure,performLayout,performDraw
三个方法,他们会依次调用measure,layout,draw
方法,然后又调用了onMeasure,onLayout,dispatchDraw
。
对于自定义的单一view的测量,只需要根据父 view 传递的MeasureSpec
进行计算大小。
对于ViewGroup的测量,一般要重写onMeasure
方法,在onMeasure方法中,父容器会对所有的子View进行Measure
,子元素又会作为父容器,重复对它自己的子元素进行Measure,这样Measure
过程就从DecorView一级一级传递下去了,也就是要遍历所有子View的的尺寸,最终得出出总的viewGroup的尺寸。Layout和Draw方法也是如此。
measure
子 View 所得到的布局大小和布局参数,将子View放在合适的位置上。对于自定义的单一view,计算本身的位置即可。
对于ViewGroup来说,需要重写onlayout
方法。除了计算自己View的位置,还需要确定每一个子View在父容器的位置以及子view的宽高(getMeasuredWidth和getMeasuredHeight),最后调用所有子view的layout
方法来设定子view的位置。
draw()会依次调用四个方法:
1)drawBackground()
,根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界。2)onDraw()
,绘制View本身的内容,一般自定义单一view会重写这个方法,实现一些绘制逻辑。3) dispatchDraw()
,绘制子View 4)onDrawScrollBars(canvas)
,绘制装饰,如 滚动指示器、滚动条、和前景
MeasureSpec
是由父View的MeasureSpec和子View的LayoutParams
通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。
MeasureSpec
是一个大小跟模式的组合值,MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,其中高两位是mode,后面30位存的是size // 获取测量模式
int specMode = MeasureSpec.getMode(measureSpec)
// 获取测量大小
int specSize = MeasureSpec.getSize(measureSpec)
// 通过Mode 和 Size 生成新的SpecMode
int measureSpec=MeasureSpec.makeMeasureSpec(size, mode);
MeasureSpec
值根据子View的布局参数和父容器的MeasureSpec值计算得来的,所以就有一个父布局测量模式,子视图布局参数,以及子view本身的MeasureSpec
关系图:其实也就是getChildMeasureSpec
方法的源码逻辑,会根据子View的布局参数和父容器的MeasureSpec计算出来单个子view的MeasureSpec。
对于自定义的单一view,一般可以不处理onMeasure
方法,如果要对宽高进行自定义,就重写onMeasure方法,并将算好的宽高通过setMeasuredDimension
方法传进去。对于自定义的ViewGroup,一般需要重写onMeasure
方法,并且调用measureChildren
方法遍历所有子View并进行测量(measureChild方法是测量具体某一个view的宽高),然后可以通过getMeasuredWidth/getMeasuredHeight
获取宽高,最后通过setMeasuredDimension方法存储本身的总宽高。
MotionEvent.ACTION_UP
事件触发时调用startScroll()方法,该方法并没有进行实际的滑动操作,而是记录滑动相关量(滑动距离、滑动时间)invalidate/postInvalidate()
方法,请求View重绘,导致View.draw方法被执行computeScroll
方法,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate
方法来进行第二次重绘,和之前流程一样,如此反复导致View不断进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动,直到整个滑动过成结束。
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