Android画布:在图像上绘制透明圆

Android画布:在图像上绘制透明圆,android,image,android-canvas,Android,Image,Android Canvas,我正在创建一个像素搜索游戏。因此,我的活动显示了一个ImageView。我想创建一个提示“告诉我对象在哪里”。为此,我需要模糊整个图像,除了围绕对象所在点的圆。我可以显示半透明的黑色背景,而不是模糊。 在画布上绘制半透明矩形没有问题。 但我不知道如何从中裁剪出一个透明的圆。 结果应该如下所示: 请帮助我在Android SDK上实现相同的结果。所以我最终做到了这一点 首先,我在整个视图上绘制一个半透明的黑色矩形。 之后,使用PorterDuff.Mode.CLEAR我切了一个透明的圆圈来显示猫

我正在创建一个像素搜索游戏。因此,我的活动显示了一个ImageView。我想创建一个提示“告诉我对象在哪里”。为此,我需要模糊整个图像,除了围绕对象所在点的圆。我可以显示半透明的黑色背景,而不是模糊。 在画布上绘制半透明矩形没有问题。 但我不知道如何从中裁剪出一个透明的圆。 结果应该如下所示:


请帮助我在Android SDK上实现相同的结果。

所以我最终做到了这一点

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

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

感谢Romain Guy在这里的评论:我知道我的窗口是不透明的,我应该在另一个位图上绘制。只有在
视图的画布上绘制之后

以下是我的
onDraw
方法:

private Canvas temp;
private Paint paint;
private Paint p = new Paint();
private Paint transparentPaint;

private void init(){
    Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
    temp = new Canvas(bitmap);
    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);
}

我没有什么要补充的,但如果有人感兴趣,我将位图分配和所有内容移动到onSizeChanged,这样性能会更好

这里你可以找到一个框架,中间有个“洞”;p>

导入android.content.Context;
导入android.graphics.Bitmap;
导入android.graphics.Canvas;
导入android.graphics.Color;
导入android.graphics.Paint;
导入android.graphics.PorterDuff;
导入android.graphics.PorterDuffXfermode;
导入android.os.Handler;
导入android.util.AttributeSet;
导入android.util.Log;
导入android.widget.FrameLayout;
/**
*由blackvvine于2016年1月1日创建。
*/
公共类FrameLayout扩展了FrameLayout{
私人油漆透明;
私人涂料;
私有位图;
私人帆布临时工;
公共框架布局(上下文){
超级(上下文);
__初始值();
}
公共框架布局(上下文、属性集属性){
超级(上下文,attrs);
__初始值();
}
公共框架布局(上下文上下文、属性集属性、int-defStyleAttr){
super(上下文、attrs、defStyleAttr);
__初始值();
}
私有void\uuuu init\uuuuu(){
透明漆=新漆();
defaultPaint=新绘制();
透明.设置颜色(颜色.透明);
transPaint.setXfermode(新的PorterDuffXfermode(PorterDuff.Mode.CLEAR));
setWillNotDraw(假);
}
@凌驾
已更改尺寸的受保护空心(整数w、整数h、整数oldw、整数oldh){
super.onSizeChanged(w,h,oldw,oldh);
bitmap=bitmap.createBitmap(w,h,bitmap.Config.ARGB_8888);
temp=新画布(位图);
}
@凌驾
受保护的void dispatchDraw(画布){
温度drawColor(颜色透明);
super.dispatchDraw(临时);
临时绘图循环(cx、cy、getWidth()/4、透明);
drawBitmap(位图,0,0,defaultPaint);
if(p<1)
使无效();
其他的
a=假;
}
}
p、 s:虽然它比原始答案效率高很多,但在draw()方法中,它仍然是一项相对繁重的任务,因此,如果您在像我这样的动画中使用此技术,不要期望60.0fps的平滑速度

这对我很有效:

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

我通过创建自定义线性布局来实现这一点:

public class CircleDrawActivity  extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_draw);
    }
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<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>
检查屏幕截图

public class CircleDrawActivity  extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_draw);
    }
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<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>

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);
    }
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<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>
活动\u圈\u画.xml

public class CircleDrawActivity  extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_draw);
    }
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<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>

colors.xml

public class CircleDrawActivity  extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_draw);
    }
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<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>

#3F51B5
#303F9F
#FF4081
dimens.xml

public class CircleDrawActivity  extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_draw);
    }
}
<?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>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
<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>

16dp
160dp
16dp
16dp
16dp
50dp

希望这能对你有所帮助。

@Robert的回答实际上向我展示了如何解决这个问题,但他的代码不起作用。因此,我更新了他的解决方案并使其生效:

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上有一个半透明层,中间有一个透明的圆圈。
P.S.这段代码不是优化的,因为它在onDraw方法中创建位图,这是因为我无法在init方法中获得父视图的宽度和高度,所以我只能在onDraw中知道它们。

我找到了一个没有位图绘制和创建的解决方案。以下是我实施的结果:

您需要使用
Clear
paint创建自定义的
FrameLayout
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

如果您在使用不透明背景的视图上获取透明圆形剪切图时遇到困难。为了让它工作,我将自定义布局视图设置为具有XML透明背景,然后使用线条绘制布局所需的背景色

cv.drawColor(Color.BLUE); //replace with your desired background color

我上面链接的答案中的完整OnDraw方法:

@Override
protected void onDraw(Canvas canvas) {

    int w = getWidth();
    int h = getHeight();
    int radius = w > h ? h / 2 : w / 2;

    bm.eraseColor(Color.TRANSPARENT);
    cv.drawColor(Color.BLUE);
    cv.drawCircle(w / 2, h / 2, radius, eraser);
    canvas.drawBitmap(bm, 0, 0, null);
    super.onDraw(canvas);
}
公共类CircleBlur扩展活动实现View.OnTouchListener{
SeekBar-SeekBar;
ImageView图像,image1;
私人油漆;
位图圆,模糊;
私有矩阵=新矩阵();
私有矩阵savedMatrix=新矩阵();
私有静态final int NONE=0;
专用静态最终整数拖动=1;
私有静态最终整数缩放=2;
私有int模式=无;
private PointF start=new PointF();
私有点f mid=新点f();
私有浮动oldDist=1f;
浮动newRot=0f;
专用浮点数d=0f;
私有浮点[]lastEvent=null;
脉波重复间隔