用圆角背景来遮罩ImageView

34

我有一个包含ImageViewTextView自定义ListView。一切都运行良好。

我想要的是在列表中显示的图片是圆角的。从Web服务获取到的图像是矩形的,但我想将其显示为下面的圆角ImageView

enter image description here

有谁能向我展示如何将图像遮罩成圆角?

我已经尝试通过创建以下可绘制文件并将其应用为ImageView中的src来实现,但对我没有任何作用。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
   <item>
      <shape android:shape="oval" >
         <solid android:color="#FFFFFF" />
         <padding
            android:bottom="10dp"
            android:left="10dp"
            android:right="10dp"
            android:top="10dp" />
         <corners android:radius="5dp" />
      </shape>
   </item>
   <item>
      <shape android:shape="oval" >
         <padding
            android:bottom="5dp"
            android:left="5dp"
            android:right="5dp"
            android:top="5dp" />
         <solid android:color="#FFFFFF" />
      </shape>
   </item>
</layer-list>

已编辑:

enter image description here

我已应用以下解决方案:

<FrameLayout
    android:id="@+id/imagemaskframe"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/op_ivpic"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_gravity="center"
        android:scaleType="fitXY" />

    <ImageView
        android:id="@+id/iv_mask_op"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_gravity="center"
        android:adjustViewBounds="true"
        android:scaleType="fitXY"
        android:src="@drawable/imgmask" />

</FrameLayout>

11
这不是重复的内容。这是关于对图像进行遮蔽,而不是程序化地改变图像的内容。 - aleb
@GrlsHu,你是如何解决那个问题的?能否帮忙提供一些帮助? - Ali Ashiq
@AliAshiq 我已经标记了能够帮助我解决问题的答案为正确答案。 - GrIsHu
1
@GrIsHu,你是用哪个解决方案的?我也需要一个圆形图像,请帮帮我。 - Ali Ashiq
请查看我的问题。您也可以按照Abhishek Pandey的解决方案。 - GrIsHu
显示剩余3条评论
11个回答

33

最好的方法是使用CanvasPorterDuff操作和/或Shaders来完成。假设您有一个可用并存储在mBitmap中的Bitmap

选项1:使用Shaders。

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

    // Load the bitmap as a shader to the paint.
    final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    final Shader shader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
    paint.setShader(shader);

    // Draw a circle with the required radius.
    final float halfWidth = canvas.getWidth()/2;
    final float halfHeight = canvas.getHeight()/2;
    final float radius = Math.max(halfWidth, halfHeight);
    canvas.drawCircle(halfWidth, halfHeight, radius, paint);
}

选项2:使用PorterDuff模式。

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

    // Create a circular path.
    final float halfWidth = canvas.getWidth()/2;
    final float halfHeight = canvas.getHeight()/2;
    final float radius = Math.max(halfWidth, halfHeight);
    final Path path = new Path();
    path.addCircle(halfWidth, halfHeight, radius, Path.Direction.CCW);

    final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawPath(path, paint);
}

注意:

  1. 在onDraw()方法中创建对象不是一个好的做法。因此,你应该在其他地方初始化你的paint和shader。这可能可以在将图像位图设置到视图时完成。
  2. 如果Canvas没有由硬件纹理支持,则可能需要保存和还原。它的一般思想在这里没有提到。
  3. 记得在构造函数中添加setWillNotDraw(false);

附加参考资料:

  1. https://sriramramani.wordpress.com/2012/12/21/shaders/ 提供有关Shaders的信息。
  2. http://mxr.mozilla.org/mozilla-central/source/mobile/android/base/ShapedButton.java 在Firefox for Android中使用Path来制作曲线按钮。
  3. http://sriramramani.wordpress.com/2012/08/27/constructing-squishy-buttons/ 提供了关于Canvas保存、还原以及ICS之前版本的特殊情况的信息。

2
非常好的答案! 非常感谢! :) 只需设置XferMode即可 paint.setXfermode(新的PorterDuffXfermode(PorterDuff.Mode.DST_IN)); - dst
1
选项1效果不佳,我认为应该是canvas.drawCircle(halfWidth, halfHeight, radius, >>paint<<);。 - marmor
选项2非常完美,额外的参考资料非常有用,谢谢。 - r4m
3
选项2对我来说很好,但它有一个黑色背景。你有什么办法可以解决这个问题吗?http://stackoverflow.com/q/24054326/513413 - Hesam
3
@Hesam,附加参考文献#3解决了暗背景的问题。 - oddmeter
显示剩余3条评论

21

我绝对推荐像其他人一样使用 Picasso。这段代码是为我其中一个 Activity 类所写的,它解决了我的问题。它利用了我在 color.xml 中定义的颜色和一个简单的布局(如下所示)。

       ImageView profile_image = (ImageView) findViewById(R.id.profile_image);
       mContext = profile_image.getContext();

        // ----------------------------------------------------------------
        // apply rounding to image
        // see: https://github.com/vinc3m1/RoundedImageView
        // ----------------------------------------------------------------
        Transformation transformation = new RoundedTransformationBuilder()
                .borderColor(getResources().getColor(R.color.my_special_orange))
                .borderWidthDp(5)
                .cornerRadiusDp(50)
                .oval(false)
                .build();

        Picasso.with(mContext)
                .load("http://{some_url}.jpg")
                .fit()
                .transform(transformation)
                .into(profile_image);

对应的布局文件为:

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="120dp"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:padding="12dp">


        <ImageView
            android:id="@+id/profile_image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_gravity="center"/>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:layout_gravity="center"
            android:padding="12dp">


            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="First-Name"
                android:id="@+id/profile_first_name"
                />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceSmall"
                android:text="Lastname"
                android:id="@+id/profile_last_name" />
        </LinearLayout>



        </LinearLayout>
</RelativeLayout>
这是结果:

image


@DavidGeller 你好,我想问一下这个怎么实现?我有一个白色的图标,想把它放在圆形的上面。所以基本上应该是设置圆形的背景而不是边框。 - QWERTY

17

我建议您使用另一种方法:

一个 FrameLayout 和两个 ImageView 就可以做到。

<FrameLayout>
    <ImageView />  your image 
    <ImageView />  put a image which has a transparent circle in it
</FrameLayout>

然后你的图片可以通过透明圆形被看到。


2
这会使图像遮挡住其背后的正方形区域,对吗? - Parker
43
不好的解决方案,如果你想要封锁区域背后的透明度。 - Jacek Kwiecień
12
顺序不正确。在安卓系统中,代码越晚出现,视图就越靠近用户。应该将顺序反过来。很好的解决方案! - albertpeiro
这是一个非常聪明的解决方案。感谢您提供它。 - Aritra Roy
我仍然能看到完整的矩形图像。以下是我的代码-<FrameLayout android:id="@+id/ic_img_upload" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="selectPhoto"> - Sudip
如果视图还不可见,请尝试提高视图高度:app:elevation:"10dp"。 - Muahmmad Tayyib

16

可以使用v4 Support Library中的RoundedBitmapDrawable将其应用于ImageView以实现所需效果:

ImageView imageView = (ImageView)findViewById(R.id.imageView);
Bitmap avatar = BitmapFactory.decodeResource(getResources(), R.drawable.avatar);
RoundedBitmapDrawable roundDrawable = RoundedBitmapDrawableFactory.create(getResources(), avatar);
roundDrawable.setCircular(true);
imageView.setImageDrawable(roundDrawable);

5
一种简单的解决方案,且不需要黑色背景
public class CircleImageView extends ImageView
{
    public CircleImageView(Context context)
    {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        // Create a circular path.
        final float halfWidth = canvas.getWidth()/2;
        final float halfHeight = canvas.getHeight()/2;
        final float radius = Math.max(halfWidth, halfHeight);
        final Path path = new Path();
        path.addCircle(halfWidth, halfHeight, radius, Path.Direction.CCW);

        canvas.clipPath(path);

        super.onDraw(canvas);
    }
}

如果开启了硬件加速,clipPath 将无法正常工作。 - PatrickNLT

3
请参考以下代码,它恰好可以满足你的需求。 https://github.com/vinc3m1/RoundedImageView 如果你不想使用自定义视图,请尝试以下方法:
创建一个圆角和透明背景的九宫格.png图像(如相框)。
然后创建一个相对布局/框架布局,其中包含两个图像视图,具体如下:
<RelativeLayout ....>
   <ImageView android:id="@+id/myImage" ..>
   <ImageView android:id="@+id/myRoundCorner" android:src="@drawable/myRoundCornerdFrame">
</RelativeLayout>

确保两个图像视图除了图像源之外具有相同的属性,还要确保具有myRoundCornerFrame源的图像视图应该位于另一个图像视图的上方。


我不想使用自定义视图来完成这个任务。我希望能在不使用任何自定义库的情况下实现它。 - GrIsHu

2

对于已经使用Glide的用户,您可以通过RequestOptions实现该效果:

RequestOptions requestOptions = new RequestOptions();
requestOptions = requestOptions.transforms(new CenterCrop(), new RoundedCorners(radiusInPixels));
Glide.with(context)
    .load(imageUrl)
    .apply(requestOptions)
    .into(imageView);

如果您正在使用Glide 4,则可以使用.apply(RequestOptions.circleCropTransform())。 - Alex

2

0

在sriramramani的答案基础上进行扩展(包括黑色背景的修复),这是我简单的ImageView的全部代码:

public class ExampleImageView extends ImageView {
  public ExampleImageView(Context context) {
    super(context);

    init();
  }

  public ExampleImageView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);

    init();
  }

  public ExampleImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    init();
  }

  void init() {
    setWillNotDraw(false);
  }

  @Override
  protected void onDraw(Canvas canvas) {

    int count = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
            Canvas.MATRIX_SAVE_FLAG |
                    Canvas.CLIP_SAVE_FLAG |
                    Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
                    Canvas.FULL_COLOR_LAYER_SAVE_FLAG |
                    Canvas.CLIP_TO_LAYER_SAVE_FLAG);

    super.onDraw(canvas);

    // Create a circular path.
    final float halfWidth = canvas.getWidth()/2;
    final float halfHeight = canvas.getHeight()/2;
    final float radius = Math.max(halfWidth, halfHeight);
    final Path path = new Path();
    path.addCircle(halfWidth, halfHeight, radius, Path.Direction.CCW);

    final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawPath(path, paint);

    canvas.restoreToCount(count);
  }
}

有一件事——如果我刪除這行:

setWillNotDraw(false);

一切仍然正常。不确定是否需要?


0
在drawable hdpi文件夹中创建名为roundimage.xml的xml文件,并尝试以下代码。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
android:shape="rectangle">
        <solid android:color="@android:color/darker_gray" />
       <corners android:bottomRightRadius="50dip"
        android:bottomLeftRadius="50dip"  
        android:topRightRadius="50dip"
        android:topLeftRadius="50dip"/>
   </shape>

然后将文件包含到您的图像背景中,如下所示:

android:background="@drawable/roundimage"

请根据您的要求更改尺寸和颜色。

请使用此代码

package com.company.app.utils;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;

public class ImageHelper {
    public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, int pixels) {
        Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap
                .getHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(output);

        final int color = 0xff424242;
        final Paint paint = new Paint();
        final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
        final RectF rectF = new RectF(rect);
        final float roundPx = pixels;

        paint.setAntiAlias(true);
        canvas.drawARGB(0, 0, 0, 0);
        paint.setColor(color);
        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(bitmap, rect, rect, paint);

        return output;
    }
}

使用这个,因为它有效。

如果你想要方形边角,那么使用这个。

public static Bitmap getRoundedCornerBitmap(Context context, Bitmap input, int pixels ,

int w , int h , boolean squareTL, boolean squareTR, boolean squareBL, boolean squareBR)
{

Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final float densityMultiplier = context.getResources().getDisplayMetrics().density;

final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, w, h);
final RectF rectF = new RectF(rect);

//make sure that our rounded corner is scaled appropriately
final float roundPx = pixels*densityMultiplier;

paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);


//draw rectangles over the corners we want to be square
if (squareTL ){
    canvas.drawRect(0, 0, w/2, h/2, paint);
}
if (squareTR ){
    canvas.drawRect(w/2, 0, w, h/2, paint);
}
if (squareBL ){
    canvas.drawRect(0, h/2, w/2, h, paint);
}
if (squareBR ){
    canvas.drawRect(w/2, h/2, w, h, paint);
}

paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(input, 0,0, paint);

return output;
}

此外,我重写了ImageView以便在xml中定义它。你可能想添加一些超级调用的逻辑,但我已经注释掉了,因为在我的情况下没有帮助。
@Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
    Drawable drawable = getDrawable();

    Bitmap b =  ((BitmapDrawable)drawable).getBitmap() ;
    Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);

    int w = getWidth(), h = getHeight();


    Bitmap roundBitmap=CropImageView.getRoundedCornerBitmap( getContext(), 
    bitmap,10, w, h , true, false,true, false);
    canvas.drawBitmap(roundBitmap, 0,0 , null);
}

我已经尝试过这样做,但是图片仍然以矩形形式显示。请查看我的编辑答案。 - GrIsHu

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