从源码角度分析view的draw过程
2015-08-21 10:36
309 查看
上面两篇文章已经分析了Android view的测量和布局且详细介绍了linearLayout的measure机制。另外特别感谢博乐对我前一篇文章的推荐以,我也会一如既往的写好每一篇文章。
1、从源码角度分析Android View的绘制机制(一)
2、从源码角度分析linearLayout测量过程以及weight机制
3、从源码角度分析view的layout过程
和前面介绍的一样,view的绘制也是从ViewRoot的performTraversals方法中开启。
完成measure和layout过程后,会调用mTreeObserver.dispatchOnGlobalLayout()方法,这个也就可以解决我们平时的一个需求,比如说要在onCreate方法中获取某个控件的测量大小以及它所在屏幕的位置,那么我们就可以给mTreeObserver添加一个OnGlobalLayout监听器,当measure和layout全部完毕后,会自动回调。再比如说你要在系统执行绘制操作前要做些什么事情,同样也可以给mTreeObserver添加一个OnPreDraw监听器,具体使用方式如下:
紧接着就是调用draw方法进行绘制了。
最终调用的还是mView.draw(canvas);draw方法在view中是final的,所以在自定义viewGroup的时候,一般都会重写onDraw方法。view中的draw方法的处理逻辑总共可以分为5个步骤,如下所述:
我们来一步一步的对view中draw方法做如下分析:
我们发现上面的一段代码只是做了绘制步骤中的1、3、4、6,少了步骤2和5,原因是2和5主要是用来绘制一些渐变的,而在if语句中的条件是!verticalEdges && !horizontalEdges,也就是说如果view没有设置渐变框。渐变框可以通过android:fadingEdge设置渐变的方向,android:fadingEdgeLength来设置渐变框的长度。大家可以参照下面的图理解一下:
无论有没有渐变效果,首先第一步就是绘制背景
[b]Step 1:背景的绘制[/b]
background.draw(canvas);调用Drawable的draw方法进行绘制,这个方法其实是个抽象方法,如果说背景是个BitmapDrawable,那么真正的绘制还是调用了native_drawBitmap方法,也就是个native方法。
[b]Step 3:绘制视图本身[/b]
调用view的onDraw方法进行视图本身的绘制。onDraw在view中的方法体是个空的,所以自定义view的时候都会重写这个方法。如果本身是个ViewGroup的话,调用的则是viewGroup的onDraw方法,但是viewGroup也没有重写这个方法。以LinearLayout为例,在2.3.3的源码中,其实LinearLayout也没有复写这个方法,貌似到到了3.0之后LinearLayout重写了这个方法,因为LinearLayout中添加了一个新的特性,可以给内部元素添加分隔。
从源码中也看到,如果mDivider 为null,自己本身的视图就不会绘制。
[b]Step 4:绘制子视图[/b]
开始调用dispatchDraw(canvas)绘制子视图,view中的这个方法也是个空方法体,对于没有孩子的view而言,不需要绘制子视图,所以viewGroup是必须要重写这个方法。
绘制子视图的过程中,首先遍历子视图,如果child是visiable的就会给child设置布局动画,这个布局动画我们可以自己定义,如果没有定义的话就会有一个默认的动画。紧跟着就是判断我们是否需要自定义child的绘制顺序。默认是按照index的先后顺序。如果我们想要调整child的绘制顺序可以重写getChildDrawingOrder方法,这个方法默认返回当前childview的index值。最后调用drawChild(canvas, child, drawingTime)方法进行子view的绘制。
[b]Step 6:绘制装饰,比如说滚动条[/b]
调用onDrawScrollBars绘制滚动条
1、从源码角度分析Android View的绘制机制(一)
2、从源码角度分析linearLayout测量过程以及weight机制
3、从源码角度分析view的layout过程
和前面介绍的一样,view的绘制也是从ViewRoot的performTraversals方法中开启。
measure过程 layout过程 ....... if (triggerGlobalLayoutListener) { attachInfo.mRecomputeGlobalAttributes = false; // 全局布局完成后,进行分发 attachInfo.mTreeObserver.dispatchOnGlobalLayout(); } ........ // 在draw之前调用 boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw(); if (!cancelDraw && !newSurface) { mFullRedrawNeeded = false; // 开始 绘制 draw(fullRedrawNeeded); .......... try { // 告诉wms绘制完毕 sWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { } }
完成measure和layout过程后,会调用mTreeObserver.dispatchOnGlobalLayout()方法,这个也就可以解决我们平时的一个需求,比如说要在onCreate方法中获取某个控件的测量大小以及它所在屏幕的位置,那么我们就可以给mTreeObserver添加一个OnGlobalLayout监听器,当measure和layout全部完毕后,会自动回调。再比如说你要在系统执行绘制操作前要做些什么事情,同样也可以给mTreeObserver添加一个OnPreDraw监听器,具体使用方式如下:
View view = new View(context); ViewTreeObserver mViewTreeObserver = view.getViewTreeObserver(); mViewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { } }); mViewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() { @Override public boolean onPreDraw() { return false; } });
紧接着就是调用draw方法进行绘制了。
private void draw(boolean fullRedrawNeeded) { Surface surface = mSurface; if (mUseGL) { if (!dirty.isEmpty()) { Canvas canvas = mGlCanvas; if (mGL != null && canvas != null) { ........... mView.mPrivateFlags |= View.DRAWN; int saveCount = canvas.save(Canvas.MATRIX_S***E_FLAG); try { canvas.translate(0, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } // 设置屏幕密度 canvas.setScreenDensity(scalingRequired? DisplayMetrics.DENSITY_DEVICE : 0); // 开始绘制 mView.draw(canvas); ......... } finally { canvas.restoreToCount(saveCount); } .......... } } ............ return; } ................ }
最终调用的还是mView.draw(canvas);draw方法在view中是final的,所以在自定义viewGroup的时候,一般都会重写onDraw方法。view中的draw方法的处理逻辑总共可以分为5个步骤,如下所述:
public void draw(Canvas canvas) { final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); // Step 1, draw the background, if needed 绘制背景 // step 2, If necessary, save the canvas' layers to prepare for fading // Step 3, draw the content 绘制视图本身 // Step 4, draw the children 绘制子视图 // Step 5,If necessary, draw the fading edges and restore layers // Step 6, draw decorations (scrollbars) 绘制滚动条 }
我们来一步一步的对view中draw方法做如下分析:
// Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; } if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content 绘制视图本身 if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children 绘制子视图 dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) 绘制装饰,比如说滚动条 onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } return; } ...........
我们发现上面的一段代码只是做了绘制步骤中的1、3、4、6,少了步骤2和5,原因是2和5主要是用来绘制一些渐变的,而在if语句中的条件是!verticalEdges && !horizontalEdges,也就是说如果view没有设置渐变框。渐变框可以通过android:fadingEdge设置渐变的方向,android:fadingEdgeLength来设置渐变框的长度。大家可以参照下面的图理解一下:
无论有没有渐变效果,首先第一步就是绘制背景
[b]Step 1:背景的绘制[/b]
background.draw(canvas);调用Drawable的draw方法进行绘制,这个方法其实是个抽象方法,如果说背景是个BitmapDrawable,那么真正的绘制还是调用了native_drawBitmap方法,也就是个native方法。
public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { if (dst == null) { throw new NullPointerException(); } throwIfCannotDraw(bitmap); native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst, paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity); }
[b]Step 3:绘制视图本身[/b]
调用view的onDraw方法进行视图本身的绘制。onDraw在view中的方法体是个空的,所以自定义view的时候都会重写这个方法。如果本身是个ViewGroup的话,调用的则是viewGroup的onDraw方法,但是viewGroup也没有重写这个方法。以LinearLayout为例,在2.3.3的源码中,其实LinearLayout也没有复写这个方法,貌似到到了3.0之后LinearLayout重写了这个方法,因为LinearLayout中添加了一个新的特性,可以给内部元素添加分隔。
@Override protected void onDraw(Canvas canvas) { if (mDivider == null) { return; } if (mOrientation == VERTICAL) { drawDividersVertical(canvas); } else { drawDividersHorizontal(canvas); } }
从源码中也看到,如果mDivider 为null,自己本身的视图就不会绘制。
[b]Step 4:绘制子视图[/b]
开始调用dispatchDraw(canvas)绘制子视图,view中的这个方法也是个空方法体,对于没有孩子的view而言,不需要绘制子视图,所以viewGroup是必须要重写这个方法。
@Override protected void dispatchDraw(Canvas canvas) { final int count = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); // 给view设置布局动画 attachLayoutAnimationParameters(child, params, i, count); bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); child.buildDrawingCache(true); } } } final LayoutAnimationController controller = mLayoutAnimationController; if (controller.willOverlap()) { mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE; } controller.start(); mGroupFlags &= ~FLAG_RUN_ANIMATION; mGroupFlags &= ~FLAG_ANIMATION_DONE; if (cache) { mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE; } if (mAnimationListener != null) { // 动画开始 mAnimationListener.onAnimationStart(controller.getAnimation()); } } ......... if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } else { for (int i = 0; i < count; i++) { // Override this if you want to change the drawing order of children. final View child = children[getChildDrawingOrder(count, i)]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } // Draw any disappearing views that have animations if (mDisappearingChildren != null) { final ArrayList<View> disappearingChildren = mDisappearingChildren; final int disappearingCount = disappearingChildren.size() - 1; // Go backwards -- we may delete as animations finish for (int i = disappearingCount; i >= 0; i--) { final View child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); } } ........... }
绘制子视图的过程中,首先遍历子视图,如果child是visiable的就会给child设置布局动画,这个布局动画我们可以自己定义,如果没有定义的话就会有一个默认的动画。紧跟着就是判断我们是否需要自定义child的绘制顺序。默认是按照index的先后顺序。如果我们想要调整child的绘制顺序可以重写getChildDrawingOrder方法,这个方法默认返回当前childview的index值。最后调用drawChild(canvas, child, drawingTime)方法进行子view的绘制。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
[b]Step 6:绘制装饰,比如说滚动条[/b]
调用onDrawScrollBars绘制滚动条
相关文章推荐
- LeetCode题解:Valid Parentheses
- ecshop文件权限
- Eclipse安装FindBugs
- Android之ListView原理学习与优化总结
- [LeetCode] 根据中序和后序序列重建二叉树
- 递归与非递归的转换
- 高通平台AT命令扩展
- “.网络”域名总量TOP12:新网万网中资源位列三强
- hdfs常用文件命令
- 销售管理流程图
- 黑马程序员--String类和StringBuffer类
- SQLite数据库文件格式全面分析
- VMWare下的CentOS如何上网
- SQLAlchemy 学习(二)
- Android开源资料大集合_架构&UI
- Evolutility.js
- Linxu命令(3)
- 手机wap页面表单被键盘遮挡的一个解决方法
- Oracle中exp的使用2
- Undraw the Trees