Skip to content

Files

Latest commit

ee06d78 · Mar 28, 2016

History

History
168 lines (121 loc) · 6.17 KB

20160323_PorterDuff之Clear模式的使用.md

File metadata and controls

168 lines (121 loc) · 6.17 KB

Android上一些动画效果是通过实现自定义的View来实现的。自定义View中比较重要的部分就是在onDraw(Canvas canvas)里面实现自己的动画逻辑。想象这么一种场景:在一个已有的颜色A的背景上画一另外一种颜色B的圆形。如下图:

如果根据时间来改变圆的半径,那就会有一个动画的效果,比如下面这样:

onDraw()里面的主要代码是这样的:

// 画一个背景
canvas.drawRect(0, 0, getWidth(), getHeight(), mBgPaint);
// 计算每一个时刻圆的半径,mAnimtor.getAnimtedFraction()会根据时间从0 - 1
int curRadius = (int) (mMaxRadius * mAnimator.getAnimatedFraction());
// 画半径为curRadius的圆圈
canvas.drawCircle(getWidth() / 2, getHeight() / 2, curRadius, mPaint);
// 如果动画没结束,就继续调用invalide(),这样会重复调用这个onDraw()方法,让我们可以画下一时刻的圆圈
if (mAnimator.isRunning()) {
	invalidate();
}

上面的思路是比较简单的,总体上来说就是圆圈盖在矩形背景的上面,通过不断改变圆圈的半径来实现动画的效果。

不过当背景矩形颜色和矩形颜色是半透明的就会导致问题,像下面这样:

现在看到的圆圈颜色其实不是我们指定的颜色,而是圆圈本身的颜色和后面矩形背景颜色的叠加,之前没有这个问题,是因为这两种颜色都不是半透明,圆圈的颜色会完全遮住后面背景的颜色。如果去掉背景,我们可以看到圆圈本来的颜色,想下面这样:

那当背景色和上面圆圈的颜色是半透明的时候,有什么办法来避免这种情况呢?

出现问题的原因是圆圈颜色和背景颜色重叠,那在每次画圆圈的时候,先在背景上需要画的圆圈的区域清除背景颜色,然后再画圆圈就可以解决这个问题。

根据这个解决方案google了一下可能的解决方案,发现使用PortDuff.Mode.CLEAR可以解决这个问题。这个模式就是清除掉之前的颜色,刚好满足我的需求。

onDraw()里面的代码这样写:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mBgPaint);

        int curRadius = (int) (mMaxRadius * mAnimator.getAnimatedFraction());
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, curRadius, mClearPaint);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, curRadius, mPaint);
        if (mAnimator.isRunning()) {
            invalidate();
        }

    }

但这样写有一个问题,我们不仅清除掉了自定义View的背景色,连它下面View的颜色也清除掉了,而我们这个Demo里主题是黑色的,所以就看到了window的颜色也就是黑色和圆圈颜色的叠加效果:

出现这样的原因,是因为onDraw(Canvas canvas)参数canvas的背景颜色是当前屏幕已有view颜色的叠加,所以当你用PorterDuff.Mode.CLEAR就会清除掉下面view的背景色。

那这个问题的解决办法是什么呢?

通过Google,找到解决办法:自己创建一个临时Canvas,每次在这个canvas上完成需要的绘画操作,然后将结果以Bitmap的方式画在onDraw(Canvas canvas)提供的的Canvas上面。最终效果如下图:

看这个圆圈颜色是不是和背景是白色的时候一样,说明我们的解决办法有效果。下面贴出最终效果的实现代码:

public class CustomView extends View {
    private Paint mBgPaint;
    private Paint mPaint;
    private Paint mClearPaint;

    private Canvas mTmpCanvas;
    private Bitmap mTmpBmp;

    private ValueAnimator mAnimator;
    private int mMaxRadius;

    private boolean mDrawnBg;

    public CustomView(Context context) {
        this(context, null, 0);
    }

    public CustomView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mBgPaint = new Paint();
        mBgPaint.setColor(Color.parseColor("#4f0000ff"));
        mBgPaint.setAntiAlias(true);

        mPaint = new Paint();
        mPaint.setColor(Color.parseColor("#4fff0000"));
        mPaint.setAntiAlias(true);

        mClearPaint = new Paint();
        mClearPaint.setColor(Color.TRANSPARENT);
        mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

        mAnimator = ValueAnimator.ofFloat(0, 1f);
        mAnimator.setDuration(1000);


        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                startAnimator();
            }
        });

    }

    private void startAnimator() {
        mAnimator.start();
        invalidate();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMaxRadius = Math.min(w, h) / 4;

        // 当View大小变化时,创建mTmpBmp和mTmpCanvas,因为这个两个对象以来View的宽高w和h
        mTmpBmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mTmpCanvas = new Canvas(mTmpBmp);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawTmpCanvas();
        canvas.drawBitmap(mTmpBmp, 0, 0, null);
        if (mAnimator.isRunning()) {
            invalidate();
        }

    }

    private void drawTmpCanvas() {
        if (mTmpCanvas == null) {
            return;
        }

        // 背景颜色只需要画一次
        if (!mDrawnBg) {
            mDrawnBg = true;
            mTmpCanvas.drawRect(0, 0, getWidth(), getHeight(), mBgPaint);
        }

        int curRadius = (int) (mMaxRadius * mAnimator.getAnimatedFraction());
        mTmpCanvas.drawCircle(getWidth() / 2, getHeight() / 2, curRadius, mClearPaint);
        mTmpCanvas.drawCircle(getWidth() / 2, getHeight() / 2, curRadius, mPaint);
    }
}