一、Canvas概念
畫布,應用程序繪製圖形以及控制圖形的動畫都是在其上面實現的,它提供了圖形繪製的真實表面和繪製圖形相關的接口,你的繪畫操作真正通過它被渲染在窗口的Bitmap上,使得用戶可見。
在View的OnDraw回調事件裏,可以獲得當前View的Canvas;對於SurfaceView對象你也可以通過 SurfaceHolder.lockCanvas()來獲得Canvas;假如你需要創建的一個新的Canvas,你首先需要創建的一個用於真正繪製的Bitmap,通過Bitmap來創建Canvas,代碼如下:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
二、Canvas與View之間的關係
在應用程序窗口的繪製過程中,可以逐步理解到Canvas和View之間的關係。
首先由該窗口的ViewRootImpl通過SurfaceHolder.lockCanvas()來獲得創建一個Canvas,並把該Canvas傳給窗口的DecorView的draw方法,源代碼如下:
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
.......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
.......
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
接下來通過dispatchDraw(canvas)分發給窗口的子View,源代碼如下:
@Override
protected void dispatchDraw(Canvas canvas) {
.........
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// 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);
}
}
........
}
通過ViewGroup.drawChild(Canvas canvas, View child, long drawingTime)繪製子View,源代碼如下:
/**
* Draw one child of this View Group. This method is responsible for getting
* the canvas in the right state. This includes clipping, translating so
* that the child's scrolled origin is at 0, 0, and applying any animation
* transformations.
*
* @param canvas The canvas on which to draw the child
* @param child Who to draw
* @param drawingTime The time at which draw is occurring
* @return True if an invalidate() was issued
*/
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
然後又通過View.draw(Canvas canvas, ViewGroup parent, long drawingTime),來循環各個繪製自己的view,源代碼代碼如下:
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
.........
if (!drawingWithRenderNode) {
// apply clips directly, since RenderNode won't do it for this draw
if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
if (offsetForScroll) {
canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
} else {
if (!scalingRequired || cache == null) {
canvas.clipRect(0, 0, getWidth(), getHeight());
} else {
canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
}
}
}
if (mClipBounds != null) {
// clip bounds ignore scroll
canvas.clipRect(mClipBounds);
}
}
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
}
} else if (cache != null) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if (layerType == LAYER_TYPE_NONE) {
// no layer paint, use temporary paint to draw bitmap
Paint cachePaint = parent.mCachePaint;
if (cachePaint == null) {
cachePaint = new Paint();
cachePaint.setDither(false);
parent.mCachePaint = cachePaint;
}
cachePaint.setAlpha((int) (alpha * 255));
canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
} else {
// use layer paint to draw the bitmap, merging the two alphas, but also restore
int layerPaintAlpha = mLayerPaint.getAlpha();
mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
mLayerPaint.setAlpha(layerPaintAlpha);
}
}
.......
}
最終又回到真正繪製View的Draw(Canvas canvas)方法,從而從最外層DecorView一層層繪製,最終完成整個窗口的繪製。
分析以上過程可知,一個窗口都只有Canvas對象,通過方法draw(Canvas canvas, ViewGroup parent, long drawingTime)我們可以看到,每個子View藉助對canvas的裁剪來控制View的顯示區域,某View顯示區域對應着畫布大小,該View的座標原點對應着Canvas的座標原點。
三、Canvas操作
先了解一下Canvas.save()和Canvas.restore()這兩個方法。
Canvas.save()方法:保存畫布,將當前畫布的設置保存到一個私有堆棧,用以對畫布進行某些操作後也能夠恢復到保存之前的畫布狀態。
Canvas.restore()方法:恢復畫布,彈出當前私有堆棧中棧頂的畫布狀態,來設置當前的畫布。
對Canvas進行translate,scale,rotate,skew,concat操作,都只是對其座標系的操作, 對Canvas進行clipRect, clipPath操作,只會裁剪顯示的區域大小,不會影響到Canvas的大小。
四、Canvas層
(1)當前Screen的圖層
我們每次調用Canvas的draw…方法都會產生一個透明的層,相鄰的兩個圖層的之間效果可由Paint的Xfermode設置,Paint.setXfermode(new PorterDuffXfermode(int mode)),mode類型主要分爲以下16種:
1.PorterDuff.Mode.CLEAR 所繪製不會提交到畫布上。
2.PorterDuff.Mode.SRC 顯示上層繪製圖片
3.PorterDuff.Mode.DST 顯示下層繪製圖片
4.PorterDuff.Mode.SRC_OVER 正常繪製顯示,上下層繪製疊蓋。
5.PorterDuff.Mode.DST_OVER 上下層都顯示。下層居上顯示。
6.PorterDuff.Mode.SRC_IN 取兩層繪製交集。顯示上層。
7.PorterDuff.Mode.DST_IN 取兩層繪製交集。顯示下層。
8.PorterDuff.Mode.SRC_OUT 取上層繪製非交集部分。
9.PorterDuff.Mode.DST_OUT 取下層繪製非交集部分。
10.PorterDuff.Mode.SRC_ATOP 取下層非交集部分與上層交集部分
11.PorterDuff.Mode.DST_ATOP 取上層非交集部分與下層交集部分
12.PorterDuff.Mode.XOR 異或:去除兩圖層交集部分
13.PorterDuff.Mode.DARKEN 取兩圖層全部區域,交集部分顏色加深
14.PorterDuff.Mode.LIGHTEN 取兩圖層全部,點亮交集部分顏色
15.PorterDuff.Mode.MULTIPLY 取兩圖層交集部分疊加後顏色
16.PorterDuff.Mode.SCREEN 取兩圖層全部區域,交集部分變爲透明色
通過以上不同PorterDuff.Mode的Paint,來實現多個圖層之間繪畫的重疊效果,可以理解爲相鄰兩個圖層合併爲一個新的圖層,然後又與其上的相鄰圖層合併,進而在畫布上層層合併,呈現出多次繪畫的效果。
(2)幕後Screen的圖層
saveLayer (RectF bounds, Paint paint, int saveFlags):可以理解爲在後臺創建了一個臨時圖層,相比於當前Screen的圖層而言,該圖層不會直接渲染在屏幕上,只有當restore()被調用時,纔會合併到當前Screen的圖層上,否則不會合並,看不到任何繪製效果。創建這種圖層的成本比較高,尤其是bounds區域比較大時,應儘量避免採用這種方式創建圖層。