Android绘制阶段

对上图中部分纠错:dispatchDraw()中的裁剪,只是对padding的裁剪。

从ViewRootImpl启动控件树绘制

调用performDraw(),从Surface中获取Canvas,在有其他窗口遮时(例如输入法窗口)根据visableInset保证关键控件可见,则需要滚动mView中的内容,所以需要对Canvas做滚动,即是操作mScrollX,mScrollY,canvas.translate(-mScrollX,-mScrollY)做平移变换后,把Canvas传给mView.draw(canvas),draw(Canvas)中调用onDraw()及dispatchDraw(Canvas),dispatchDraw(Canvas)则遍历子控件,调用子控件的draw(Canvas,ViewGroup,long)方法。draw(Canvas)的重载方法,draw(ViewGroup,Canvas,long),该方法被父控件调用,draw(ViewGroup,Canvas,long)中调用draw(Canvas)。

draw(ViewGroup,Canvas,long)的主要内容:

1.变换到自己的坐标,并处理mScrollX/Y。canvas.translate(mLeft-mScrollX, mTop-mScorllY),在draw(Canvas)中裁剪控件自身区域的时候,需要回滚对mScrollX/Y的变换,因为mScrollX/Y只滚动内容,不滚动控件。

2.动画计算。

动画产生的矩阵及mScrollX/Y对控件裁剪Surface的影响将跟坐标系变换相关的动画放到矩阵中,然后把矩阵应用合并到canvas中,而如alpha相关动画跟坐标系变换无关的,则放到RenderNode中。除了mScrollX/Y,其他动画属性及动画矩阵都会影响到对控件区域的裁剪,例如做平移动画,clipRect时就同时被平移变换影响了,裁剪到的区域就是平移后的区域,所以虽然绘制的地方随着平移变换变了,但是裁剪后控件能绘制的那块Surface区域也跟着变了,所以就算内容移动了,看内容的窗口也动了,所以内容就能被看到了。而为什么裁剪区域不受mScrollX/Y影响呢,因为通过canvas对Surface做裁剪前,先对mScrollX/Y的变换回滚了,在裁剪后,再重新对mScrollX/Y做平移变换。

动画矩阵是怎么产生的而动画是以矩阵的形式合并到canvas的,有两个产生动画矩阵的地方。第一个是View.startAnimation(Animation),在绘制时能通过Animation.getTransformation().getMatrix获得特定时刻的变换矩阵,这个属于控件动画;第二个就是通过View.setScale(), View.setTranslateX/Y(), View.setRoate(), View.setAlpha等方法,这种属于属性动画,每次绘制时,在draw(...)中通过View.getMatrix()获得这些动画属性的矩阵。

动画矩阵及mScrollX/Y对事件派发的影响而控件动画的动画矩阵是不被分派触摸事件时考虑进去的,所以控件动画时,只有原来的地方能正确接收事件,控件动画所在的区域是无法正确得到该控件的触摸事件的。而mScrollX/Y,及通过View.setScale(), View.setTranslateX/Y(), View.setRoate(), View.setAlpha等方法设置的属性,是被分派事件所考虑的,就是说父控件得到触摸事件后,派发给子控件时会考虑父控件的mScrollX/Y及子控件的动画属性对子控件的位置影响(注意mScrollX/Y是不会对本控件的位置有影响的,只会对子控件的位置有影响,因为mScrollX/Y只是移动本控件的内容,而子控件也属于内容),而通过View.startAnimation(Animation)或者View.setAnimation(Animation)设置的动画,其产生的矩阵也会对控件的显示位置产生影响,但是父控件派发触摸事件时,不会把这个矩阵考虑进去。

控件动画和属性动画是否会导致重新测量控件动画或者属性动画都不会调用requestLayout(),只会调用invalidate(),所以不会导致重新测量,窗口重新布局,控件树重新布局,只是对控件绘制的位置(即显示的位置)和获得触摸事件的区域有影响。例如一个在LinearLayout放置两个View,orientation为horizontal,让左边的view使用view.setTranslateX()或者view.setAnimation()向右平移,右边的view并没有动,最终向右移动的view被右边的view覆盖了,所以这证明了控件动画或者属性的确不会使控件树重新测量,如果重新测量的话,那么两个View不会重叠,而是随着左边的view向右移动,右边的view也向右移。有些动画,如通过改变LayoutParams.height/width对view做动画,那么则会导致requestLayout(),就需要重新测量了。

控件在动画时可见的范围子控件动画在父控件中是可见的,但是在父控件外是绝对不可见的,因为父控件在将Canvas交给子控件时,对Canvas做了裁剪,裁剪为父控件区域。子控件在绘制前也会对canvas做裁剪,但是是根据其动画矩阵做的裁剪,所以子控件内容移动后,子控件仍然可见。

3.通过Canvas裁剪Surface

父控件在其draw(Canvas)方法中调用draw(ViewGroup,Canvas,long)时,参数canvas已经裁剪过了,裁剪区域为父控件区域。而子控件本身也会再次对Canvas进行裁剪,裁剪区域为基于控件本身的mLeft,mRight,mTop,mBottom,及考虑属性动画,控件动画,使得属于控件的Surface区域和绘制的内容位置是一致,这样控件内容才可见。这个与父控件的mGroupFlag的FLAG_CLIP_CHILDREN有关,可通过setClipChildren()启用或禁用。

4.硬件加速

5.绘图缓存(在父控件addView()或者removeView()的时候,只要子控件其尺寸未变,缓存质量不变,重新测量布局控件树后,即使mLeft/mTop/mRight/mBottom变了,原来的缓存还是能重用的)

draw(Canvas)方法的主要内容:

1.通过Canvas裁剪Surface

父控件在其draw(Canvas)方法中调用draw(ViewGroup,Canvas,long)时,参数canvas已经裁剪过了,裁剪区域为父控件区域。而子控件本身也会再次对Canvas进行裁剪,裁剪区域为基于控件本身的mLeft,mRight,mTop,mBottom,及考虑属性动画,控件动画,使得属于控件的Surface区域和绘制的内容位置是一致,这样控件内容才可见。

2.绘制背景

背景虽然是控件内容的一部分,但是默认背景不会随着mScrollX/Y滚动,因为绘制前先对mScrollX/Y做了逆变换,在绘制完背景后再重新做对mScrollX/Y的变换。

3.调用onDraw(Canvas)绘制控件其他内容

4.调用dispatchDraw(Canvas),绘制子控件。如果不是ViewGroup及其子类,则这个方法什么也不做。

5.绘制scrollbar等控件装饰。

dispatchDraw(Canvas)方法的主要内容:

1.裁剪Canvas中的Surface

裁剪区域为该控件(即父控件)的区域,所以Canvas传到子控件的draw(Canvas,ViewGroup,long)方法中时,子控件可以使用Surface的父控件区域,但是在draw(Canvas)方法中,子控件本身又裁剪了一次,将Surface裁剪得只剩下子控件本身的区域。这个行为和本控件的mGroupFlag的CLIP_TO_PADDING_MASK有关,可通过setClipToPadding()启用或禁用。

2.遍历所有子控件

并调用每个子控件的draw(Canvas,ViewGroup,long)。但是遍历子控件的顺序,影响着子控件的Z序,越早遍历则越在底层,则越容易被遮挡着而不被看见。默认是按子控件在数组中的顺序,index小的先被访问。也可以通过ViewGroup.setChildDrawingOrderEnabled()方法将FLAG_USE_CHILD_DRAWING_ORDER,那么就可以重写getChildDrawingOrder(),自定义遍历顺序。

3.恢复变换,canvas.restoreToCount(saveCount)

动画矩阵对控件坐标getX/Y()的影响

既然控件动画矩阵和属性动画矩阵都影响裁剪,那么View.getX(),返回的是否是实际变换后的坐标。答案是不一定。

public float getX() {
        return mLeft + getTranslationX();
    }

以上代码显示,只有平移变换才会体现在getX()中,而没有体现scale和rotate变换。而且在代码运行实践中的结果也是跟代码所见一致。必须清楚的是,scale和rotate会影响触摸事件派发。

动画对measure,layout,draw各阶段的影响:

控件动画和属性动画过程都不会导致调用requestLayout(),所以不会去执行measure和layout阶段,只会执行draw阶段。所以onLayoutChangeListener不会被调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值