敢问Canvas来自何方

Canvas的故事

来自一个群友的问题:

使用Canvas绘制的时候坐标系是什么?是屏幕坐标系还是view坐标系?

Canvas是单例吗?

乐于助(shui)人(qun)的我说了一句… “翻看源码看看onDraw是怎么被调用的”,然后我也没有管住我这个手…

其实我们做的事情很简单 - 就是:分析onDraw的方法调用栈

代码环节

onDraw -> draw

然后我们看draw

AS代码分析

这几个方法我们挨个瞅瞅(这就是静态代码分析的蛋疼之处,一个函数被多处调用的时候,要挨个检查,一会我会说一个更方便的方法)

最后我们追查到ViewupdateDisplayListIfDirty方法

省略掉其他代码,我们可以看到Canvas被创建

/**
     * Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
     * @hide
     */
    @NonNull
    public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;
        // 省略大量代码
            int width = mRight - mLeft;
            int height = mBottom - mTop;
            int layerType = getLayerType();

            final DisplayListCanvas canvas = renderNode.start(width, height);
            canvas.setHighContrastText(mAttachInfo.mHighContrastText);

            try {
                // 省略大量代码
                        draw(canvas);
                    
                }
            } finally {
                renderNode.end(canvas);
                setDisplayListProperties(renderNode);
            }
        } else {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        return renderNode;
    }
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);

然后我们看renderNode.start这个方法

public DisplayListCanvas start(int width, int height) {
        return DisplayListCanvas.obtain(this, width, height);
    }

然后到这个obtain方法

//DisplayListCanvas类的方法
static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
        if (node == null) throw new IllegalArgumentException("node cannot be null");
        DisplayListCanvas canvas = sPool.acquire();
        if (canvas == null) {
            canvas = new DisplayListCanvas(node, width, height);
        } else {
            nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
                    width, height);
        }
        canvas.mNode = node;
        canvas.mWidth = width;
        canvas.mHeight = height;
        return canvas;
    }

在这里我们就可以看到,代码会先尝试从sPool取得一个Canvas,如果无法取得,那就自己new一个。如果成功取得,那就调用一个方法来把这个canvas给reset掉。

最后我们来看看这个sPool到底是啥东西

//DisplayListCanvas类的变量 
private static final int POOL_LIMIT = 25;
private static final SynchronizedPool sPool =
         new SynchronizedPool<>(POOL_LIMIT);

//DisplayListCanvas类的方法
void recycle() {
        mNode = null;
        sPool.release(this);
    }

这里就很清楚了,在需要Canvas的时候,从这个容量25的池子里面取一个来用,取不出就只好自己new一个,在canvas完成工作后,回收到这个池子里面。

所以Canvas不是全局单例,而是在一个池里缓存着。

碎碎念

另外一种分析代码的方法… 我叫做“动态分析法”,就是在代码运行的时候打断点,然后查看断点时的函数调用栈,通过Debug信息里面的调用栈来查看onDraw里面的Canvas是从哪里来的。

可以看另一篇博客,它就是动态分析来看这个Canvas,写的也很nice

Canvas坐标系

onDraw里面的坐标系是View坐标系而不是屏幕坐标系。

为什么?canvas在创建的时候并不知道view在哪里也不懂view坐标系,它只是一个画布。是Android系统帮你悄悄地做了Translate操作。

(这部分代码解析最近会更)

你可能感兴趣的:(敢问Canvas来自何方)