上面两篇文章已经分析了Android view的测量和布局且详细介绍了linearLayout的measure机制。另外特别感谢博乐对我前一篇文章的推荐以,我也会一如既往的写好每一篇文章。
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_SAVE_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来设置渐变框的长度。大家可以参照下面的图理解一下:
无论有没有渐变效果,首先第一步就是绘制背景
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);
}
调用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,自己本身的视图就不会绘制。
开始调用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);
}
调用onDrawScrollBars绘制滚动条