Android如何在ImageView上应用遮罩?

34

我尝试了这里的代码:创建带有遮罩的ImageView。我使用以下图像作为原始和遮罩:

enter image description hereenter image description here

然而,我得到的结果是这样的:

enter image description here

请注意,窗口背景不是黑色,而是holo light(在Galaxy Nexus上看起来像非常淡的灰色,不完全是白色)。第二张图片是当列表视图中选择一个项目时所得到的结果。

如果我改为使用相同算法创建一个新的Bitmap,然后将其传递给ImageView而不是覆盖onDraw(),那么它会正确地进行绘制:

Canvas canvas = new Canvas();
Bitmap mainImage = //get original image
Bitmap maskImage = //get mask image
Bitmap result = Bitmap.createBitmap(mainImage.getWidth(), mainImage.getHeight(), Bitmap.Config.ARGB_8888);

canvas.setBitmap(result);
Paint paint = new Paint();
paint.setFilterBitmap(false);

canvas.drawBitmap(mainImage, 0, 0, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(maskImage, 0, 0, paint);
paint.setXfermode(null);

imageView.setImageBitmap(result);

我得到了预期的结果:

enter image description here

注意,淡入淡出效果被正确应用。当进行选择时,这更为明显。

那么在ImageView的onDraw方法中发生了什么,导致黑色背景代替让窗口背景显示出来?有趣的是,如果原始图像本身具有一定的透明度,则会尊重该透明度,例如:

enter image description here

我自己无法弄清楚。我宁愿能够在onDraw上完成它,而不是预先创建位图,因为它仅适用于位图作为源和蒙版。我希望能够在其他可绘制对象上执行此操作,例如渐变和纯色,但在这些情况下宽度和高度没有设置。


1
这里有一个更新。在阅读了https://dev59.com/wHA75IYBdhLWcg3wH1JB之后,我发现如果将Bitmap配置设置为RGB_565而不是ARGB_8888,则可以在工作示例中复制相同的黑色行为。我还可以将问题缩小到传递给onDraw方法的画布上。如果使用onDraw画布,则会出现黑色边框。由于传递给onDraw的画布没有关联的位图,因此可能本地不支持透明度?我发现的另一件事是,在列表视图滚动时,黑色边框会消失。 - AngraX
1
好的。在阅读了这篇文章之后:https://dev59.com/MVXTa4cB1Zd3GeqPxgFS,似乎问题实际上是当我将它应用于传递给onDraw()的画布时,我会使窗口画布的像素透明化,而在窗口之后有一个黑色背景。看来除非我能找到另一种有效的DST模式,否则我别无选择,只能使用临时位图并对其进行渲染。 - AngraX
我正在尝试实现类似的功能,其中我有一个带有图像的ImageView和第二个带有黑色遮罩的ImageView,在触摸时,它应该像您展示的那样在圆形中显示后面的ImageView,您能帮我解决这个问题吗? - usr30911
3个回答

29

在查看所有stackoverflow帖子后,我找到了完美的组合,可以创建没有黑色边框的遮罩。它非常适合我的需求。

目前,我正在使用一个普通图像和一个掩膜图像(一种具有透明度的png)创建可拖动视图,因此我需要覆盖onDraw函数。

private Bitmap mImage = ...;
private Bitmap mMask = ...;  // png mask with transparency
private int mPosX = 0;
private int mPosY = 0;

private final Paint maskPaint;
private final Paint imagePaint;

public CustomView (final Context context) {
    maskPaint = new Paint();
    maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));

    imagePaint = new Paint();
    imagePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
}

/* TODO
 if you have more constructors, make sure you initialize maskPaint and imagePaint
 Declaring these as final means that all your constructors have to initialize them.
 Failure to do so = your code won't compile.
*/

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();      
    canvas.drawBitmap(mMask, 0, 0, maskPaint);
    canvas.drawBitmap(mImage, mPosX, mPosY, imagePaint);
    canvas.restore();
}

1
嗨。我在阅读了一些关于xfer模式的文章后,刚刚检查了逻辑,并且我相信你的解决方案非常有效。我接受它作为官方答案。有关xfer模式的更多详细信息,请参见:http://www.piwai.info/transparent-jpegs-done-right/和http://ssp.impulsetrain.com/2013-03-17_Porter_Duff_Compositing_and_Blend_Modes.html - AngraX
它可以在CLEAR和DST_OUT两种模式下工作。有趣的是,从我所读的有关xfer模式的文档中,CLEAR应该清除整个内容,但在Android上,它的作用与DST_OUT完全相同。顺便说一句,我现在使用着色器代替xfer模式,这样我就可以少进行一次绘制,使我的过度绘制从红色变成绿色 :)。 - AngraX
我正在使用DST输出,因为这更有意义。当您说如果将 android:anyDensity 设置为 false,则 CLEAR 不起作用时,您是指它现在表现正常了(清空整个内容)吗? - AngraX
是的,它清除了所有内容。DST_OUT应该更准确。但不知何故,当我测试时,DST_OUT + anyDensity:true对我无效。目前我将其设置为CLEAR + anyDensity:true(目前在Android 4.1.1和4.1.2上进行测试)。需要进一步实验。 - morph85
1
@morph85 我建议你避免在onDraw方法中实例化paint对象。因为你总是会实例化完全相同的两个对象,这意味着你的帧率会受到影响。在你的类中将它们都声明为finals,你应该能够获得更高的帧率。通常要避免在layout、measure和draw方法中实例化对象。 - copolii
显示剩余5条评论

2
回答自己的问题。Xfermode正在按预期工作。绘画使画布的结果区域透明(这是窗口活动使用的画布)。由于画布本身被设置为透明,窗口显示了其后面的内容:黑色背景。
要正确执行此操作,确实需要创建一个新的位图来容纳alpha掩码的结果。我更新了代码以考虑所有类型的可绘制对象。

0

在这段代码中应用:

 mask_over = BitmapFactory.decodeResource(
            getResources(), mask_over1[0]);
    icon = Bitmap.createScaledBitmap(icon, screenwidth, screenwidth, false);
    mask_over = Bitmap.createScaledBitmap(mask_over, screenwidth, screenwidth, false);
             back_img=createBitmap_ScriptIntrinsicBlur(Bitmap.createScaledBitmap(cropview.croppedImage, screenwidth, screenwidth, false),25.0f);
    LinearLayout.LayoutParams layoutParams111 = new LinearLayout.LayoutParams(screenwidth, screenwidth);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接