0

I am inspired by the the new Material Design animations and I worked to create a similar drawable that is used in new support v7 Action Bar Drawer Toggle.

I created a CustomDrawable. All I actually did is that I created a Play triangle on canvas and pause logo on the left of the left margin of the visible canvas. I rotate the canvas according to the progress and restore it. Then I used Xfermode to crop the rotated result into a circle.

I cant find the solution to the problem.

The problem is that the xFermode is not applied to the 180 degree rotated result(after calling canvas.restore()).

Here is the code of 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();

        }
    });
 }
}

And the code for the 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() is useless since there is no canvas.save/saveLayer – pskink Oct 25 '14 at 14:13
  • It does effect. Without it the canvas will retain its rotated state and the mask will be rotated as well when mask is drawn. –  Oct 25 '14 at 15:19
  • restore(0 docs: "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**.", see the last sentence in **bold** – pskink Oct 25 '14 at 15:22
  • It doesn't throw error on restore(). There must have been a call to save before. It removes the mod to matrix and clip since last save but doesnt remove what I draw on canvas.If I doesnt remove the modification I did to the matrix (in this case rotation) then the modification will affect the mask as well. You can use my code as well. I tried removing restore() but it worsened the problem. –  Oct 25 '14 at 17:04
  • have you seen my answer? – pskink Oct 25 '14 at 20:13
  • added `draw` method without using `Canvas.clipPath()`, have you seen it? – pskink Oct 26 '14 at 06:04

1 Answers1

0

this is your simplified 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);
    }
}

EDIT: this is draw() method without Canvas.clipPath and without creating a "mask" 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();
    }

where: mDstInPaint is a Paint object with xfermode set in Drawable's constructor:

mDstInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
pskink
  • 23,874
  • 6
  • 66
  • 77
  • My code worked as well. I just required to have Canvas.save() which I didn't. By the way Your code looks more clean. Also I am new to this canvas thing. Thanks a lot buddy. –  Oct 27 '14 at 14:21
  • 1
    so this is what i was saing from the beginning: there is restore() called without matching save() call – pskink Oct 27 '14 at 14:27