android中的canvas绘制

510 阅读4分钟

前几天做了一个气泡形状的组件,类似微信的聊天对话框的形状,所以需要把图片裁剪出一个不规则的形状,虽然最终完美解决,但是中间遇到了比较多的问题,暴露了自己在这方面的不足,所以开始系统的学习这方面的知识,并写下这篇文章。

用canvas画点,画线,画面,类似的常规操作我们就不涉及了,在这里我们主要研究Canvas类中的save和restore两类方法。

/**
 * Saves the current matrix and clip onto a private stack.
 *
 * 将当前的矩阵存入一个私有的栈中
 *
 * <p>
 * Subsequent calls to translate,scale,rotate,skew,concat or clipRect,
 * clipPath will all operate as usual, but when the balancing call to
 * restore() is made, those calls will be forgotten, and the settings that
 * existed before the save() will be reinstated.
 *
 * 随后canvas调用的平移,缩放,旋转,倾斜,合并,裁剪矩形,裁剪轨迹等等都可以正常使用,
 * 但是当调用了restore()之后,之前提到的那些操作都将无效,save()方法之前的设置将会恢复
 *
 * @return The value to pass to restoreToCount() to balance this save()
 *
 * 返回值是用来传递给restoreToCount()方法来抵消这次save的
 */
public int save() {
    return nSave(mNativeCanvasWrapper, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
}

/**
 * This behaves the same as save(), but in addition it allocates and
 * redirects drawing to an offscreen rendering target.
 * <p class="note"><strong>Note:</strong> this method is very expensive,
 * incurring more than double rendering cost for contained content. Avoid
 * using this method when possible and instead use a
 * {@link android.view.View#LAYER_TYPE_HARDWARE hardware layer} on a View
 * to apply an xfermode, color filter, or alpha, as it will perform much
 * better than this method.
 * <p>
 * All drawing calls are directed to a newly allocated offscreen rendering target.
 * Only when the balancing call to restore() is made, is that offscreen
 * buffer drawn back to the current target of the Canvas (which can potentially be a previous
 * layer if these calls are nested).
 * <p>
 * Attributes of the Paint - {@link Paint#getAlpha() alpha},
 * {@link Paint#getXfermode() Xfermode}, and
 * {@link Paint#getColorFilter() ColorFilter} are applied when the
 * offscreen rendering target is drawn back when restore() is called.
 *
 * @param bounds May be null. The maximum size the offscreen render target
 *               needs to be (in local coordinates)
 * @param paint  This is copied, and is applied to the offscreen when
 *               restore() is called.
 * @return       value to pass to restoreToCount() to balance this save()
 */
public int saveLayer(@Nullable RectF bounds, @Nullable Paint paint) {
    return saveLayer(bounds, paint, ALL_SAVE_FLAG);
}

这个方法的注释有点乱,我把翻译放在下面:

这个方法与save()的效果一样,但是它会额外分配和重定向绘制到一个离屏渲染的目标上。这个方法非常消耗性能, 会对包含的内容造成超过两倍的渲染开销。

所有的绘制都会被导向一个新分配的离屏渲染目标上。只有当用来平衡这次save的restore()方法被调用之后, 离屏缓冲区中的内容才会被绘制回canvas的当前目标上(如果saveLayer有嵌套调用的话也可能是上一个layer)。

/**
 * This call balances a previous call to save(), and is used to remove all
 * modifications to the matrix/clip state since the last save call. It is
 * an error to call restore() more times than save() was called.
 *
 * 这个方法调用可以抵消(平衡)一次之前的save方法调用,并且可以用来移除所有上一个save方法调用
 * 之后的对矩阵或者裁剪状态的修改。restore方法的调用次数超过save将会报错
 */
public void restore() {
    if (!nRestore(mNativeCanvasWrapper)
            && (!sCompatibilityRestore || !isHardwareAccelerated())) {
        throw new IllegalStateException("Underflow in restore - more restores than saves");
    }
}

/**
 * Efficient way to pop any calls to save() that happened after the save
 * count reached saveCount. It is an error for saveCount to be less than 1.
 *
 * 将当前栈中layer的数量减少到saveCount,也就是说超过的layer全部出栈
 *
 * Example:
 *    int count = canvas.save();
 *    ... // more calls potentially to save()
 *    canvas.restoreToCount(count);
 *    // now the canvas is back in the same state it was before the initial
 *    // call to save().
 *
 * @param saveCount The save level to restore to.
 */
public void restoreToCount(int saveCount) {
    if (saveCount < 1) {
        if (!sCompatibilityRestore || !isHardwareAccelerated()) {
            // do nothing and throw without restoring
            throw new IllegalArgumentException(
                    "Underflow in restoreToCount - more restores than saves");
        }
        // compat behavior - restore as far as possible
        saveCount = 1;
    }
    nRestoreToCount(mNativeCanvasWrapper, saveCount);
}

最后我根据自己的理解说明一下Canvas和Bitmap的关系。

Bitmap是一个类,它存贮了一个矩形区域内所有像素点的信息。我们可以读取矩形区域内的每一个像素点的颜色值, 在android屏幕上的给定区域显示一张图片。 而Canvas类,是一张盖在Bitmap上的透明的坐标纸,我们可以根据这张坐标纸在bitmap上进行绘制,所有的这些绘制, 其实最后都存贮在了Canvas持有的bitmap对象中。