Androidcanvas:在图像上绘制透明的圆圈

我正在创build一个像素狩猎游戏。 所以我的活动显示一个ImageView。 我想创build一个提示“告诉我在哪里是对象”。 为此,我需要模糊整个图像,除了围绕对象所在点的圆。 我可以显示一个半透明的黑色背景,而不是模糊。 在canvas上绘制半透明的矩形是没有问题的。 但是我不知道如何从中挖出一个透明的圆圈。 结果应该是这样的: 在这里输入图像描述

请帮助我在Android SDK上获得相同的结果。

所以最后我设法做到了这一点。

首先,我画了一个半透明的黑色矩形整体视图。 之后使用PorterDuff.Mode.CLEAR我剪了一个透明的圆圈来显示猫的位置。

我有PorterDuff.Mode.CLEAR问题:首先,我得到一个黑色的圆圈,而不是一个透明的。

感谢Romain Guy在这里的评论: 评论在这里我明白,我的窗口是不透明的,我应该画另一个位图。 而且只能在View的canvas上绘制。

这是我的onDraw方法:

 private Canvas temp; private Paint paint; private Paint p = new Paint(); private Paint transparentPaint; private void init(){ temp = new Canvas(bitmap); Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888); paint = new Paint(); paint.setColor(0xcc000000); transparentPaint = new Paint(); transparentPaint.setColor(getResources().getColor(android.R.color.transparent)); transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); } protected void onDraw(Canvas canvas) { temp.drawRect(0, 0, temp.getWidth(), temp.getHeight(), paint); temp.drawCircle(catPosition.x + radius / 2, catPosition.y + radius / 2, radius, transparentPaint); canvas.drawBitmap(bitmap, 0, 0, p); } 

我通过创build自定义的LinearLayout来完成这个工作:

检查屏幕截图

在这里输入图像描述

CircleOverlayView.java

 import android.annotation.TargetApi; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; import android.widget.LinearLayout; /** * Created by hiren on 10/01/16. */ public class CircleOverlayView extends LinearLayout { private Bitmap bitmap; public CircleOverlayView(Context context) { super(context); } public CircleOverlayView(Context context, AttributeSet attrs) { super(context, attrs); } public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CircleOverlayView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (bitmap == null) { createWindowFrame(); } canvas.drawBitmap(bitmap, 0, 0, null); } protected void createWindowFrame() { bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas osCanvas = new Canvas(bitmap); RectF outerRectangle = new RectF(0, 0, getWidth(), getHeight()); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(getResources().getColor(R.color.colorPrimary)); paint.setAlpha(99); osCanvas.drawRect(outerRectangle, paint); paint.setColor(Color.TRANSPARENT); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)); float centerX = getWidth() / 2; float centerY = getHeight() / 2; float radius = getResources().getDimensionPixelSize(R.dimen.radius); osCanvas.drawCircle(centerX, centerY, radius, paint); } @Override public boolean isInEditMode() { return true; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); bitmap = null; } } 

CircleDrawActivity.java

 public class CircleDrawActivity extends AppCompatActivity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_circle_draw); } } 

activity_circle_draw.xml

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rlParent" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/lighthouse" android:scaleType="fitXY" /> <common.customview.CircleOverlayView android:id="@+id/cicleOverlay" android:layout_width="match_parent" android:layout_height="match_parent"> </common.customview.CircleOverlayView> </RelativeLayout> 

colors.xml

 <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> </resources> 

dimens.xml

 <resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="nav_header_vertical_spacing">16dp</dimen> <dimen name="nav_header_height">160dp</dimen> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="fab_margin">16dp</dimen> <dimen name="radius">50dp</dimen> </resources> 

希望这会帮助你。

我find了一个没有位图绘制和创build的解决scheme。 这是我执行的结果: 覆盖图的例子

你需要创build一个自定义的FrameLayout并用Clear paint绘制drawCircle

  mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 

另外不要忘记禁用硬件加速并调用setWillNotDraw(false)因为我们将重写onDraw方法

  setWillNotDraw(false); setLayerType(LAYER_TYPE_HARDWARE, null); 

完整的例子在这里:

 public class TutorialView extends FrameLayout { private static final float RADIUS = 200; private Paint mBackgroundPaint; private float mCx = -1; private float mCy = -1; private int mTutorialColor = Color.parseColor("#D20E0F02"); public TutorialView(Context context) { super(context); init(); } public TutorialView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public TutorialView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public TutorialView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { setWillNotDraw(false); setLayerType(LAYER_TYPE_HARDWARE, null); mBackgroundPaint = new Paint(); mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); } @Override public boolean onTouchEvent(MotionEvent event) { mCx = event.getX(); mCy = event.getY(); invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(mTutorialColor); if (mCx >= 0 && mCy >= 0) { canvas.drawCircle(mCx, mCy, RADIUS, mBackgroundPaint); } } } 

PS:这个实现只是在自己的内部绘制了一个洞,你需要在你的布局中放置背景,并在上面添加这个TutorialView

@罗伯特的答案实际上给了我如何解决这个问题,但他的代码不起作用。 所以我已经更新了他的解决scheme,并使其工作:

 public class CaptureLayerView extends View { private Bitmap bitmap; private Canvas cnvs; private Paint p = new Paint(); private Paint transparentPaint = new Paint();; private Paint semiTransparentPaint = new Paint();; private int parentWidth; private int parentHeight; private int radius = 100; public CaptureLayerView(Context context) { super(context); init(); } public CaptureLayerView(Context context, AttributeSet attrs) { super(context, attrs); init(); } private void init() { transparentPaint.setColor(getResources().getColor(android.R.color.transparent)); transparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); semiTransparentPaint.setColor(getResources().getColor(R.color.colorAccent)); semiTransparentPaint.setAlpha(70); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); bitmap = Bitmap.createBitmap(parentWidth, parentHeight, Bitmap.Config.ARGB_8888); cnvs = new Canvas(bitmap); cnvs.drawRect(0, 0, cnvs.getWidth(), cnvs.getHeight(), semiTransparentPaint); cnvs.drawCircle(parentWidth / 2, parentHeight / 2, radius, transparentPaint); canvas.drawBitmap(bitmap, 0, 0, p); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { parentWidth = MeasureSpec.getSize(widthMeasureSpec); parentHeight = MeasureSpec.getSize(heightMeasureSpec); this.setMeasuredDimension(parentWidth, parentHeight); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } 

现在在这样的布局中使用这个视图:

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <SurfaceView android:id="@+id/surfaceView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"> </SurfaceView> <com.example.myapp.CaptureLayerView android:layout_width="match_parent" android:layout_height="match_parent" /> <Button android:id="@+id/btnTakePicture" android:layout_width="match_parent" android:layout_height="80dp" android:onClick="onClickPicture" android:text="@string/take_picture"> </Button> 

在这里我想要在SurfaceView上的半透明图层,中间是透明圆。 PS这个代码没有被优化,因为它在onDraw方法中创build了Bitmap,这是因为我不能在init方法中获得父视图的宽度和高度,所以我只能在onDraw中知道它们。

我没有太多的东西要添加到你的答案,但如果任何人的兴趣我把位图分配和所有onSizeChanged,所以它是更好的性能明智的。

在这里你可以find一个在它的中间有一个“孔”的FrameLayout;)

 import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.widget.FrameLayout; /** * Created by blackvvine on 1/1/16. */ public class SteroidFrameLayout extends FrameLayout { private Paint transPaint; private Paint defaultPaint; private Bitmap bitmap; private Canvas temp; public SteroidFrameLayout(Context context) { super(context); __init__(); } public SteroidFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); __init__(); } public SteroidFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); __init__(); } private void __init__() { transPaint = new Paint(); defaultPaint = new Paint(); transPaint.setColor(Color.TRANSPARENT); transPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); setWillNotDraw(false); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); temp = new Canvas(bitmap); } @Override protected void dispatchDraw(Canvas canvas) { temp.drawColor(Color.TRANSPARENT); super.dispatchDraw(temp); temp.drawCircle(cx, cy, getWidth()/4, transPaint); canvas.drawBitmap(bitmap, 0, 0, defaultPaint); if (p < 1) invalidate(); else animRunning = false; } } 

ps:尽pipe它比原来的答案要高效得多,但是在draw()方法中还是比较繁重的工作,所以如果你在像我这样的animation中使用这种技术,不要期望60.0fps的光滑的

这对我工作:

 canvas.drawCircle(x,y,radius,new Paint(Color.TRANSPARENT))