【问题标题】:android : Xfermode masking disappearing on 180 degree rotation of Canvasandroid:Xfermode 遮罩在 Canvas 旋转 180 度时消失
【发布时间】:2014-10-26 14:01:47
【问题描述】:

我受到了新的 Material Design 动画的启发,并努力创建了一个类似的可绘制对象,用于新的支持 v7 Action Bar Drawer Toggle。

我创建了一个 CustomDrawable。我实际上所做的只是在画布上创建了一个播放三角形,并在可见画布的左边距左侧创建了暂停徽标。我根据进度旋转画布并恢复它。然后我使用 Xfermode 将旋转后的结果裁剪成圆形。

我找不到问题的解决方案。

问题是 xFermode 没有应用于 180 度旋转的结果(在调用 canvas.restore() 之后)。

这是Activity的代码。

public class MainActivity extends Activity{

ImageView iv;
CustomDrawable drawable = new CustomDrawable();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    iv = (ImageView) findViewById(R.id.button);
    iv.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    iv.setBackgroundDrawable(drawable);

    iv.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {
            float[] values = { 0, 1 };
            if (drawable.getProgress() != 0) {
                values[0] = drawable.getProgress();
                values[1] = 0;
            }
            ObjectAnimator animator = ObjectAnimator.ofFloat(drawable,
                    "progress", values);
            animator.setDuration(2000);
            animator.start();

        }
    });
 }
}

以及 CustomDrawable 的代码

public class CustomDrawable extends Drawable {
    private float mProgress = 0;
    private Paint mPaint = new Paint();
    private Path mPath = new Path();
    private final float rootTwo = (float) Math.sqrt(2);
    private final float rootThree = (float) Math.sqrt(3);
    private float radius = 0;
    private float side = 0;
    private Point[] triangle = new Point[3];
    Paint xferpaint = new Paint();
    Canvas cropper;
    Bitmap bitmap;
    Interpolator interpolator = new AnticipateOvershootInterpolator();
    private float width;
    Rect rec1, rec2;

public CustomDrawable() {
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Style.FILL);
    xferpaint.setColor(Color.RED);
    xferpaint.setStyle(Style.FILL);
    xferpaint.setAntiAlias(true);
}

@Override
public void draw(Canvas canvas) {
    canvas.getClipBounds(bound);
    boundsf.set(bound);
    if (radius == 0) {
        radius = Math.min(bound.centerX(), bound.centerY());
        radius -= 5;

        bitmap = Bitmap.createBitmap(bound.width(), bound.height(),
                Config.ARGB_8888);
        cropper = new Canvas(bitmap);
        cropper.drawCircle(bound.centerX(), bound.centerY(), radius,
                xferpaint);

        xferpaint.setXfermode(new PorterDuffXfermode(Mode.DST_IN));

        side = rootTwo * radius;

        triangle[0] = new Point(
                (int) (bound.centerX() + (side / rootThree)),
                bound.centerY());
        triangle[1] = new Point(bound.centerX()
                - (int) (side / (2 * rootThree)), bound.centerY()
                - (int) (side / 2));
        triangle[2] = new Point(bound.centerX()
                - (int) (side / (2 * rootThree)), bound.centerY()
                + (int) (side / 2));
        width = side / 4;
        rec1 = new Rect((int) (-bound.centerX() - (3 * width / 2)),
                (int) (bound.centerY() - (side / 2)),
                (int) (-bound.centerX() - (width / 2)),
                (int) (bound.centerY() + (side / 2)));
        rec2 = new Rect((int) (-bound.centerX() + (width / 2)),
                (int) (bound.centerY() - (side / 2)),
                (int) (-bound.centerX() + (3 * width / 2)),
                (int) (bound.centerY() + (side / 2)));
    }

    mPath.rewind();
    mPath.moveTo(triangle[0].x, triangle[0].y);
    mPath.lineTo(triangle[1].x, triangle[1].y);
    mPath.lineTo(triangle[2].x, triangle[2].y);
    mPath.close();
    mPaint.setColor(Color.parseColor("#378585"));
    canvas.drawPaint(mPaint);
    mPaint.setColor(Color.parseColor("#FF0400"));
    canvas.rotate(180 * interpolator.getInterpolation(mProgress), 0,
            bound.centerY());
    canvas.drawPath(mPath, mPaint);
    canvas.drawRect(rec1, mPaint);
    canvas.drawRect(rec2, mPaint);
    canvas.restore();
    canvas.drawBitmap(bitmap, 0, 0, xferpaint);
}

@Override
public int getOpacity() {
    return mPaint.getAlpha();
}

@Override
public void setAlpha(int alpha) {
    mPaint.setAlpha(alpha);
}

@Override
public void setColorFilter(ColorFilter filter) {
    mPaint.setColorFilter(filter);
}

public float getProgress() {
    return mProgress;
}

public void setProgress(float progress) {
    mProgress = progress;
    invalidateSelf();
}

}

【问题讨论】:

  • canvas.restore() 没用,因为没有 canvas.save/saveLayer
  • 确实有效。没有它,画布将保持其旋转状态,并且在绘制蒙版时也会旋转蒙版。
  • restore(0 docs: "此调用平衡了先前对 save() 的调用,并用于删除自上次保存调用以来对矩阵/剪辑状态的所有修改。调用 restore() 的次数比调用 save() 的次数多的错误。”,请参见 bold 中的最后一句
  • 它不会在 restore() 上抛出错误。之前一定有过保存的电话。自上次保存以来,它会删除矩阵和剪辑的 mod,但不会删除我在画布上绘制的内容。如果我没有删除我对矩阵所做的修改(在本例中为旋转),那么修改也会影响蒙版。你也可以使用我的代码。我尝试删除 restore() 但它使问题恶化。
  • 你看到我的回答了吗?

标签: android android-canvas drawable xfermode


【解决方案1】:

这是你的简化 Drawable:

class CustomDrawable extends Drawable implements ValueAnimator.AnimatorUpdateListener {
    private float mProgress = 0;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Path mPath;
    private Path mClipPath;

    public CustomDrawable() {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(0xffFF0400);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        mClipPath = new Path();
        int cx = bounds.centerX();
        int cy = bounds.centerY();
        int radius = Math.min(cx, cy) - 5;
        mClipPath.addCircle(cx, cy, radius, Path.Direction.CCW);

        final float rootTwo = (float) Math.sqrt(2);
        final float rootThree = (float) Math.sqrt(3);

        float side = rootTwo * radius;
        mPath = new Path();
        mPath.moveTo(cx + (side / rootThree), cy);
        mPath.lineTo(cx - side / (2 * rootThree), cy - side / 2);
        mPath.lineTo(cx - side / (2 * rootThree), cy + side / 2);
        mPath.close();

        float width = side / 4;
        addRect(-cx - (3 * width / 2), cy - (side / 2), width, side);
        addRect(-cx + (width / 2), cy - (side / 2), width, side);
    }

    private void addRect(float l, float t, float dx, float dy) {
        mPath.addRect(l, t, l + dx, t + dy, Path.Direction.CCW);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.clipPath(mClipPath);

        canvas.drawColor(0xff378585);
        Rect bounds = getBounds();
        canvas.rotate(mProgress, 0, bounds.centerY());
        canvas.drawPath(mPath, mPaint);
    }

    public void switchIcons() {
        float[] values = { 0, 180 };
        if (mProgress != 0) {
            values[0] = mProgress;
            values[1] = 0;
        }
        ValueAnimator animator = ValueAnimator.ofFloat(values).setDuration(2000);
        animator.setInterpolator(new AnticipateOvershootInterpolator());
        animator.addUpdateListener(this);
        animator.start();
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mProgress = (Float) animation.getAnimatedValue();
        invalidateSelf();
    }

    @Override
    public int getOpacity() {
        return mPaint.getAlpha();
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter filter) {
        mPaint.setColorFilter(filter);
    }
}

编辑:这是draw() 方法,没有Canvas.clipPath,也没有创建“掩码”Bitmap

    @Override
    public void draw(Canvas canvas) {
        canvas.drawColor(0xff378585);

        canvas.save();
        canvas.rotate(mProgress, 0, cy);
        canvas.drawPath(mPath, mPaint);
        canvas.restore();

        canvas.saveLayer(null, mDstInPaint, 0);
        canvas.drawCircle(cx, cy, radius, mPaint);
        canvas.restore();
    }

其中:mDstInPaint 是一个 Paint 对象,在 Drawable 的构造函数中设置了 xfermode:

mDstInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));

【讨论】:

  • 我的代码也能正常工作。我只需要拥有我没有的 Canvas.save() 。顺便说一句,您的代码看起来更干净。我也是这个画布的新手。非常感谢朋友。
  • 所以这就是我从一开始就说的:没有匹配的 save() 调用调用了 restore()