34

我正在尝试创建一个简单的自定义视图:有一个由弧路径显示的位图 - 从 0° 到 360°。度数会随着一些 FPS 而变化。

所以我用重写的onDraw()方法制作了一个自定义视图:

@Override
protected void onDraw(Canvas canvas) {

    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    arcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    canvas.drawArc(arcRectF, -90, currentAngleSweep, true, arcPaint);
    arcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, circleSourceRect, circleDestRect, arcPaint);
}

arcPaint初始化如下:

arcPaint = new Paint();
arcPaint.setAntiAlias(true);
arcPaint.setColor(Color.RED); // Color doesn't matter

现在,一切都画得很好,但是……整个视图中的背景都是黑色的。

如果我设置canvas.drawColor(..., PorterDuff.Mode.DST)并省略canvas.drawBitmap()- 在透明背景上正确绘制弧线。

我的问题是 - 如何设置PorterDuff模式以使其具有透明度?

当然bitmap是带有 alpha 通道的 32 位 PNG。

4

7 回答 7

68

PorterDuff.Mode.CLEAR不适用于硬件加速。刚设置

view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 

非常适合我。

于 2017-06-17T18:14:49.290 回答
16

在视图初始化期间使用此语句

setLayerType(LAYER_TYPE_HARDWARE, null);
于 2017-02-20T04:57:51.817 回答
2

除了一件事之外,您的代码中的一切都很好:您的背景是黑色的,因为您的窗口是不透明的。要获得透明结果,您应该在另一个位图上绘制。在您的 onDraw 方法中,请创建一个新的位图并在其上完成所有工作。之后在画布上绘制此位图。

有关详细信息和示例代码,请阅读我的答案

于 2013-11-13T20:03:13.717 回答
1

要解决不希望的PorterDuff效果
,首先使用最简单的方法,就像OP的问题一样,aPath.arcTo(*, *, *, *, false)就足够了——注意它是arcTo,不是addArc,以及添加arc之前的false手段no forceMoveTo——不需要PorterDuff

Path arcPath = new Path();
@Override
protected void onDraw(Canvas canvas) {
    arcPath.rewind();
    arcPath.moveTo(arcRectF.centerX, arcRectF.centerY);
    arcPath.arcTo(arcRectF, -90, currentAngleSweep, false);
    arcPath.close();
    canvas.clipPath(arcPath, Region.Op.DIFFERENCE);
    canvas.drawBitmap(bitmap, circleSourceRect, circleDestRect, arcPaint);
}

如果您确实需要 PorterDuff,主要用于复杂的颜色变形,例如混合渐变,请不要将具有 PorterDuff 过滤效果的颜色或形状或位图直接绘制到提供的默认画布上onDraw(Canvas),请使用一些带有 alpha 通道的缓冲/目标位图 [s]- - 并且 - 存储PorterDuffsetHasAlpha(true)过滤的结果,最后将位图绘制到默认画布上,除了矩阵更改之外不应用任何过滤。
这是一个创建边界模糊圆形图像的工作示例:

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.ImageView;

/**
* Created by zdave on 6/22/17.
*/

public class BlurredCircleImageViewShader extends ImageView {
private Canvas mCanvas;
private Paint mPaint;
private Matrix matrix;
private static final float GRADIENT_RADIUS = 600f;  //any value you like, but should be big enough for better resolution.
private Shader gradientShader;
private Bitmap bitmapGradient;
private Bitmap bitmapDest;
private Canvas canvasDest;
public BlurredCircleImageViewShader(Context context) {
    this(context, null);
}

public BlurredCircleImageViewShader(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}

public BlurredCircleImageViewShader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    matrix = new Matrix();
    int[] colors = new int[]{Color.BLACK, Color.BLACK, Color.TRANSPARENT};
    float[] colorStops = new float[]{0f, 0.5f, 1f};
    gradientShader = new RadialGradient(GRADIENT_RADIUS, GRADIENT_RADIUS, GRADIENT_RADIUS, colors, colorStops, Shader.TileMode.CLAMP);
    mPaint.setShader(gradientShader);

    bitmapGradient = Bitmap.createBitmap((int)(GRADIENT_RADIUS * 2), (int)(GRADIENT_RADIUS * 2), Bitmap.Config.ARGB_8888);
    bitmapDest = bitmapGradient.copy(Bitmap.Config.ARGB_8888, true);

    Canvas canvas = new Canvas(bitmapGradient);
    canvas.drawRect(0, 0, GRADIENT_RADIUS * 2, GRADIENT_RADIUS * 2, mPaint);

    canvasDest = new Canvas(bitmapDest);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = getMeasuredWidth();
    setMeasuredDimension(width, width);
}

@Override
protected void onDraw(Canvas canvas){
    /*uncomment each of them to show the effect, the first and the third one worked, the second show the same problem as OP's*/
    //drawWithLayers(canvas);  //unrecommended.
    //drawWithBitmap(canvas);  //this shows transparent as black
    drawWithBitmapS(canvas);   //recommended.
}
@SuppressLint("WrongCall")
private void drawWithLayers(Canvas canvas){
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    float width = canvas.getWidth();
    float hWidth = width / 2;
    //both saveLayerAlpha saveLayer worked here, and if without either of them,
    //the transparent area will be black.
    //int count = canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 255, Canvas.ALL_SAVE_FLAG);
    int count = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
    super.onDraw(canvas);
    float scale = hWidth/GRADIENT_RADIUS;
    matrix.setTranslate(hWidth - GRADIENT_RADIUS, hWidth - GRADIENT_RADIUS);
    matrix.postScale(scale, scale, hWidth, hWidth);
    gradientShader.setLocalMatrix(matrix);

    canvas.drawRect(0, 0, width, width, mPaint);

    canvas.restoreToCount(count);
}
@SuppressLint("WrongCall")
private void drawWithBitmap(Canvas canvas){
    super.onDraw(canvas);
    float scale = canvas.getWidth() / (GRADIENT_RADIUS * 2);
    matrix.setScale(scale, scale);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawBitmap(bitmapGradient, matrix, mPaint);  //transparent area is still black.
}
@SuppressLint("WrongCall")
private void drawWithBitmapS(Canvas canvas){
    float scale = canvas.getWidth() / (GRADIENT_RADIUS * 2);
    int count = canvasDest.save();
    canvasDest.scale(1/scale, 1/scale); //tell super to draw in 1/scale.
    super.onDraw(canvasDest);
    canvasDest.restoreToCount(count);

    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvasDest.drawBitmap(bitmapGradient, 0, 0, mPaint);

    matrix.setScale(scale, scale);  //to scale bitmapDest to canvas.
    canvas.drawBitmap(bitmapDest, matrix, null);
    }
 }

一些注意事项: 1、此视图扩展ImageViewView,存在一些差异。
2、为什么drawWithLayers--saveLayersaveLayerAlpha--不推荐: a、它们不确定,有时不能正常工作(显示透明为黑色),特别是对于ViewwhoesonDraw(Canvas)是空的,而ImageView.onDraw(Canvas)用aDrawable来画一些;b、价格昂贵,分配off-screen bitmap存储临时绘制结果,没有任何资源回收机制的明确线索。
3、使用自己的bitmap[s],更适合自定义资源回收。

有人说,每次绘制都不能使用PorterDuff,不分配bitmap[s],因为在绘制/布局/测量之前无法确定位图的宽高。
您可以使用缓冲位图 [s] 进行 PorterDuff 绘图:
首先,分配一些足够大的位图 [s]。
然后,使用一些矩阵在位图[s] 上绘制。
然后,将位图 [s] 绘制到带有一些矩阵的 dest 位图中。
最后,用一些矩阵将 dest 位图绘制到画布中。

有些人推荐 setLayerType(View.LAYER_TYPE_SOFTWARE, null),这对我来说不是一个选项,因为它会导致 onDraw(Canvas) 在循环中被调用。

结果图像 源图像

于 2017-06-22T13:50:14.537 回答
0

Jetpack Compose中,您可以使用blendMode

Canvas(modifier = Modifier.fillMaxSize()) {
    drawIntoCanvas { it.saveLayer(Rect(Offset.Zero, size), Paint()) }
    drawRect(color = Color.Blue, size = size)
    
    val clearSize = Size(200.dp.toPx(), 300.dp.toPx())
    drawRect(
        color = Color.Transparent,
        topLeft = Offset((size.width - clearSize.width) / 2, (size.height - clearSize.height) / 2),
        size = clearSize,
        blendMode = BlendMode.Clear
    )
}
于 2021-09-10T22:39:24.277 回答
0

只需保存画布并在之后恢复

canvas.saveLayer(clipContainer, null, Canvas.ALL_SAVE_FLAG);
canvas.drawRoundRect(rectTopGrey, roundCorners, roundCorners, greyPaint);
canvas.drawRoundRect(rectTopGreyClip, roundCorners, roundCorners, clipPaint);
canvas.restore();

在这种情况下,第一个矩形将作为https://developer.android.com/reference/android/graphics/PorterDuff.Mode的目标,第二个矩形作为源

于 2018-10-02T05:48:35.560 回答
0

如果您有纯色背景,您需要做的就是将 Paint color 设置为您的背景颜色。例如,如果您有白色背景,您可以执行以下操作:

paint.setColor(Color.WHITE);

但是,如果您需要擦除具有透明背景的线条,请尝试以下操作:为了使用透明颜色进行绘制,您必须使用 Paint setXfermode,这仅在您为画布设置位图时才有效。如果您按照以下步骤操作,您应该会得到想要的结果。

创建一个画布并设置它的位图。

mCanvas = new Canvas();
mBitmap= Bitmap.createBitmap(scrw, scrh, Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
When you want to erase something you just need to use setXfermode.

public void onClickEraser() 
{ 
   if (isEraserOn)
      mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
   else
      mPaint.setXfermode(null);
}

现在你应该可以使用透明颜色绘制:

mCanvas.drawPath(path, mPaint);
于 2015-12-08T10:00:23.950 回答