2013-08-22 220 views
15

我试图创建一个简单的自定义视图:有一个由弧路径显示的位图 - 从0deg到360deg。度数正在改变一些FPS。在画布上绘图 - PorterDuff.Mode.CLEAR绘制黑色!为什么?

所以我做了重写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。

+0

让你找到了解决办法?我是一半的方式,但是当我试图保存在删除区域给我黑色部分的位图时。 –

+0

如果不涉及OpenGL或更改SDK,则无法解决此问题。我终于决定不画位图了。 – cadavre

回答

2

除了一件事情之外,您的代码中的一切都可以使用:由于您的窗口不透明,因此会获得黑色背景。要获得透明的结果,你应该在另一个位图上绘制。在你的onDraw方法中,请创建一个新的位图并对其执行所有的工作。之后,在画布上绘制此位图。

有关详细信息和示例代码,请阅读this my answer

+1

现在想象你在每个onDraw()上创建一个新的位图并绘制它。听起来像杀了应用程序。最后很难说 - 但我想达到的是不可能的!有一些技巧,例如在窗口顶部设置视图绘制(这使得画布透明),但我使用视图重叠另一个视图,因此它不适合。 – cadavre

0

如果你有纯色背景,所有你需要做的就是设置漆膜颜色到背景色。举例来说,如果你有一个白色的背景,你可以这样做:

paint.setColor(Color.WHITE); 

但是,如果你需要删除一个符合你试试这个透明的背景: 为了用透明的颜色,你必须使用油漆画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); 
} 

现在,你应该能够战平使用透明色:视图

setLayerType(LAYER_TYPE_HARDWARE, null); 
4

使用这一说法不符合硬件加速工作。刚刚设置

view.setLayerType(View.LAYER_TYPE_SOFTWARE,null); 

完美适合我。

+0

它解决了颜色变化问题,但是现在我的线条变得异常了 – Godwin

11

PorterDuff.Mode.CLEAR的初始化过程中

mCanvas.drawPath(path, mPaint); 
+0

自从最近两天以来,我一直有这个黑色问题。谢谢老兄。它的作品像魅力:) –

+0

'setLayerType(LAYER_TYPE_HARDWARE,null);'在我的情况下,这种说法奏效。但设置完这些之后,线条和位图就会被销毁。 – Godwin

1

解决不需要PorterDuff效果
使用最简单的方法,首先,像OP的问题,一个Path.arcTo(*, *, *, *, false)是不够的 - 注意,这是arcTo,不addArcfalse加弧前指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)提供的默认画布,用一些缓冲/目标bitmap [s]与alpha通道 - 和setHasAlpha(true) - 存储来自PorterDuff过滤的结果,最后将位图绘制到默认画布,而不应用除矩阵更改之外的任何过滤。
这里有一个工作示例创建边界模糊的圆形图案:

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 - 是不推荐的:一,他们是不确定的,有时候不工作的权利(表演透明黑色),尤其是对View whoes onDraw(Canvas)是空的,而ImageView.onDraw(Canvas)使用Drawable画一些; b,他们很贵,他们分配off-screen bitmap来存储临时绘图结果,并没有任何资源回收机制的明确线索。
3,使用您自己的位图[s],更适合定制资源回收。

有人说,在绘制/ layout/measure之前,无法确定位图的宽度,高度,因此无法在不分配位图的情况下使用PorterDuff。
您可以使用缓冲位图[s]作为PorterDuff绘图:
首先,分配一些足够大的位图[s]。
然后,用一些矩阵绘制位图[s]。
和,用一些矩阵将位图[s]绘制到一个dest位图中。
最后,将dest位图绘制成带有矩阵的画布。

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

result image source image