如何使自定义画笔的背景透明?

问题描述

我想用我的自定义画笔使用下面的代码在画布上绘图,但正如您在图片中看到的,我的画笔的背景是黑色的,尽管没有颜色。

尽管我将画笔颜色指定为 Color.TRANSPARENT 或 Color.parseColor ("# 00000000"),但画笔背景仍然变为黑色。

如何使画笔的背景颜色透明?

click to see the picture

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import androidx.annotation.ColorInt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;

import java.util.Stack;

public class BrushDrawingView extends View {

    static final float DEFAULT_Brush_SIZE = 50.0f;
    static final float DEFAULT_ERASER_SIZE = 50.0f;
    static final int DEFAULT_OPACITY = 255;

    private float mBrushSize = DEFAULT_Brush_SIZE;
    private float mBrushEraserSize = DEFAULT_ERASER_SIZE;
    private int mOpacity = DEFAULT_OPACITY;

    private final Stack<BrushLinePath> mDrawnPaths = new Stack<>();
    private final Stack<BrushLinePath> mRedoPaths = new Stack<>();
    private final Paint mDrawPaint = new Paint();

    private Canvas mDrawCanvas;
    private boolean mBrushDrawMode;
    private Bitmap brushBitmap;

    private Path mPath;
    private float mTouchX,mTouchY;
    private static final float TOUCH_TOLERANCE = 4;

    private BrushViewchangelistener mBrushViewchangelistener;

    public BrushDrawingView(Context context) {
        this(context,null);
    }

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

    public BrushDrawingView(Context context,AttributeSet attrs,int defStyle) {
        super(context,defStyle);
        setupBrushDrawing();
    }

    private void setupBrushDrawing() {
        //Caution: This line is to disable hardware acceleration to make eraser feature work properly
        setupPathAndPaint();
        setVisibility(View.GONE);
    }

    private void setupPathAndPaint() {
        mPath = new Path();
        mDrawPaint.setAntiAlias(true);
        mDrawPaint.setStyle(Paint.Style.stroke);
        mDrawPaint.setstrokeJoin(Paint.Join.ROUND);
        mDrawPaint.setstrokeCap(Paint.Cap.ROUND);
        mDrawPaint.setstrokeWidth(mBrushSize);
        mDrawPaint.setAlpha(mOpacity);
    }

    private void refreshBrushDrawing() {
        mBrushDrawMode = true;
        setupPathAndPaint();
    }

    void brushEraser() {
        mBrushDrawMode = true;
        mDrawPaint.setstrokeWidth(mBrushEraserSize);
        mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    }

    public void setBrushDrawingMode(boolean brushDrawMode) {
        this.mBrushDrawMode = brushDrawMode;
        if (brushDrawMode) {
            this.setVisibility(View.VISIBLE);
            refreshBrushDrawing();
        }
    }

    public Bitmap getBrushBitmap() {
        return brushBitmap;
    }

    public void setBrushBitmap(Bitmap brushBitmap) {
        this.brushBitmap = brushBitmap;
    }

    public void setopacity(@IntRange(from = 0,to = 255) int opacity) {
        this.mOpacity = (int) (opacity * 2.55f);
        setBrushDrawingMode(true);
    }

    public int getopacity() {
        return mOpacity;
    }

    boolean getBrushDrawingMode() {
        return mBrushDrawMode;
    }

    public void setBrushSize(float size) {
        mBrushSize = 5 + (int) (size);
        setBrushDrawingMode(true);
    }

    void setBrushColor(@ColorInt int color) {
        mDrawPaint.setColor(color);
        setBrushDrawingMode(true);
    }

    void setBrushEraserSize(float brushEraserSize) {
        this.mBrushEraserSize = brushEraserSize;
        setBrushDrawingMode(true);
    }

    void setBrushEraserColor(@ColorInt int color) {
        mDrawPaint.setColor(color);
        setBrushDrawingMode(true);
    }

    float geteraserSize() {
        return mBrushEraserSize;
    }

    public float getBrushSize() {
        return mBrushSize;
    }

    int getBrushColor() {
        return mDrawPaint.getColor();
    }

    public void clearall() {
        mDrawnPaths.clear();
        mRedoPaths.clear();
        if (mDrawCanvas != null) {
            mDrawCanvas.drawColor(0,PorterDuff.Mode.CLEAR);
        }
        invalidate();
    }

    void setBrushViewchangelistener(BrushViewchangelistener brushViewchangelistener) {
        mBrushViewchangelistener = brushViewchangelistener;
    }

    @Override
    protected void onSizeChanged(int w,int h,int oldw,int oldh) {
        super.onSizeChanged(w,h,oldw,oldh);
        Bitmap canvasBitmap = Bitmap.createBitmap(w,Bitmap.Config.ARGB_8888);
        mDrawCanvas = new Canvas(canvasBitmap);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (BrushLinePath linePath : mDrawnPaths) {
            canvas.drawPath(linePath.getDrawPath(),linePath.getDrawPaint());
        }
        canvas.drawPath(mPath,mDrawPaint);
        /////
        final Bitmap scaledBitmap = getScaledBitmap();

        final float centerX = scaledBitmap.getWidth() / 2;
        final float centerY = scaledBitmap.getHeight() / 2;

        final PathMeasure pathMeasure = new PathMeasure(mPath,false);

        float distance = scaledBitmap.getWidth() / 2;

        float[] position = new float[2];
        float[] slope = new float[2];

        float slopeDegree;

        while (distance < pathMeasure.getLength())
        {
            pathMeasure.getPosTan(distance,position,slope);
            slopeDegree = (float)((Math.atan2(slope[1],slope[0]) * 180f) / Math.PI);
            canvas.save();
            canvas.translate(position[0] - centerX,position[1] - centerY);
            canvas.rotate(slopeDegree,centerX,centerY);
            canvas.drawBitmap(scaledBitmap,mDrawPaint);
            canvas.restore();
            distance += scaledBitmap.getWidth() + 10;
        }

    }

    /////

    private Bitmap getScaledBitmap()
    {
        // width / height of the bitmap[
        float width = brushBitmap.getWidth();
        float height = brushBitmap.getHeight();

        // ratio of the bitmap
        float ratio = width / height;

        // set the height of the bitmap to the width of the path (from the paint object).
        float scaledHeight = mDrawPaint.getstrokeWidth();

        // to maintain aspect ratio of the bitmap,use the height * ratio for the width.
        float scaledWidth = scaledHeight * ratio;

        // return the generated bitmap,scaled to the correct size.
        return Bitmap.createScaledBitmap(brushBitmap,(int)scaledWidth,(int)scaledHeight,true);
    }

    /**
     * Handle touch event to draw paint on canvas i.e brush drawing
     *
     * @param event points having touch info
     * @return true if handling touch events
     */
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        if (mBrushDrawMode) {
            float touchX = event.getX();
            float touchY = event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    touchStart(touchX,touchY);
                    break;
                case MotionEvent.ACTION_MOVE:
                    touchMove(touchX,touchY);
                    break;
                case MotionEvent.ACTION_UP:
                    touchUp();
                    break;
            }
            invalidate();
            return true;
        } else {
            return false;
        }
    }

    boolean undo() {
        if (!mDrawnPaths.empty()) {
            mRedoPaths.push(mDrawnPaths.pop());
            invalidate();
        }
        if (mBrushViewchangelistener != null) {
            mBrushViewchangelistener.onViewRemoved(this);
        }
        return !mDrawnPaths.empty();
    }

    boolean redo() {
        if (!mRedoPaths.empty()) {
            mDrawnPaths.push(mRedoPaths.pop());
            invalidate();
        }

        if (mBrushViewchangelistener != null) {
            mBrushViewchangelistener.onViewAdd(this);
        }
        return !mRedoPaths.empty();
    }


    private void touchStart(float x,float y) {
        mRedoPaths.clear();
        mPath.reset();
        mPath.moveto(x,y);
        mTouchX = x;
        mTouchY = y;
        if (mBrushViewchangelistener != null) {
            mBrushViewchangelistener.onStartDrawing();
        }
    }

    private void touchMove(float x,float y) {
        float dx = Math.abs(x - mTouchX);
        float dy = Math.abs(y - mTouchY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mTouchX,mTouchY,(x + mTouchX) / 2,(y + mTouchY) / 2);
            mTouchX = x;
            mTouchY = y;
        }
    }

    private void touchUp() {
        mPath.lineto(mTouchX,mTouchY);
        // Commit the path to our offscreen
        mDrawCanvas.drawPath(mPath,mDrawPaint);
        // kill this so we don't double draw

        mDrawnPaths.push(new BrushLinePath(mPath,mDrawPaint));

        /////

        final Bitmap scaledBitmap = getScaledBitmap();

        final float centerX = scaledBitmap.getWidth() / 2;
        final float centerY = scaledBitmap.getHeight() / 2;

        final PathMeasure pathMeasure = new PathMeasure(mPath,slope[0]) * 180f) / Math.PI);
            mDrawCanvas.save();
            mDrawCanvas.translate(position[0] - centerX,position[1] - centerY);
            mDrawCanvas.rotate(slopeDegree,centerY);
            mDrawCanvas.drawBitmap(scaledBitmap,mDrawPaint);
            mDrawCanvas.restore();
            distance += scaledBitmap.getWidth() + 10;
        }

        /////

        mPath = new Path();
        if (mBrushViewchangelistener != null) {
            mBrushViewchangelistener.onStopDrawing();
            mBrushViewchangelistener.onViewAdd(this);
        }
    }

    @VisibleForTesting
    Paint getDrawingPaint() {
        return mDrawPaint;
    }

    @VisibleForTesting
    Pair<Stack<BrushLinePath>,Stack<BrushLinePath>> getDrawingPath() {
        return new Pair<>(mDrawnPaths,mRedoPaths);
    }
}

public interface BrushViewchangelistener {
    void onViewAdd(BrushDrawingView brushDrawingView);

    void onViewRemoved(BrushDrawingView brushDrawingView);

    void onStartDrawing();

    void onStopDrawing();
}

class BrushLinePath {
    private final Paint mDrawPaint;
    private final Path mDrawPath;

    BrushLinePath(final Path drawPath,final Paint drawPaints) {
        mDrawPaint = new Paint(drawPaints);
        mDrawPath = new Path(drawPath);
    }

    Paint getDrawPaint() {
        return mDrawPaint;
    }

    Path getDrawPath() {
        return mDrawPath;
    }
}

解决方法

发生这种情况的原因是因为 Paint 没有默认设置的 alpha 组合模式。因此,当您尝试在画布上绘制位图时,它将用您的画笔像素替换目标像素,在您的情况下为 #00000000。这将导致像素显示为黑色。查看此文档:https://developer.android.com/reference/android/graphics/PorterDuff.Mode

乍一看,您似乎在寻找 PorterDuff.Mode.SRC_OVERPorterDuff.Mode.SRC_ATOP - 这样源图像(您的画笔)中的透明像素不会过度绘制目标(画布)中的像素)。如果您的背景始终不透明,您将看不到 SRC_OVERSRC_ATOP 之间的区别,但如果不是,请选择适合您需要的背景。然后您可以通过在其末尾添加这一行来修改 setupPathAndPaint 方法:

    mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));