解决不需要PorterDuff
效果
使用最简单的方法,首先,像OP的问题,一个Path.arcTo(*, *, *, *, false)
是不够的 - 注意,这是arcTo
,不addArc
和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)
提供的默认画布,用一些缓冲/目标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 ,这个观点延伸ImageView
不View
,存在一定的差异。
2,为什么drawWithLayers
- saveLayer
或saveLayerAlpha
- 是不推荐的:一,他们是不确定的,有时候不工作的权利(表演透明黑色),尤其是对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
让你找到了解决办法?我是一半的方式,但是当我试图保存在删除区域给我黑色部分的位图时。 –
如果不涉及OpenGL或更改SDK,则无法解决此问题。我终于决定不画位图了。 – cadavre