如何创建一个带有圆角的ImageView?

708

在Android中,ImageView默认是矩形的。如何将它变成圆角矩形(剪切Bitmap的四个角成为圆角矩形)?


请注意,从2021年起,只需使用ShapeableImageView即可。


这可能会有帮助 https://dev59.com/UoXca4cB1Zd3GeqPCw48 - Mangesh
在更老、更复杂的答案下面,我认为现在应该接受的答案是:RoundedBitmapDrawable,它在v4支持库修订版21中添加。 - Jonik
你可以最简单的方法使用CardView,里面放一个ImageView - 在这里查看示例https://dev59.com/xHE95IYBdhLWcg3wGZ9_#41479670 - Taras Vovkovych
4
Material Design 1.2.0引入了ShapeableImageView,可能会很有用。 - Ali
使用Material Component Library中的ShapeableImageView。请查看此答案 https://dev59.com/rbvoa4cB1Zd3GeqPxR7e#61960983 - Khaled Saifullah
显示剩余2条评论
58个回答

579

虽然回应有些晚了,但对于其他需要这个功能的人来说,你可以使用以下代码手动调整图像边角。

http://www.ruibm.com/?p=184

这不是我的代码,但我已经使用过它,效果非常好。我将其用作ImageHelper类中的一个帮助器,并稍微扩展了一下,以传入针对给定图像所需的模糊量。

最终代码看起来像这样:

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;
    }
}

4
它并不适用于所有设备。我需要做任何更改吗? - Spring Breaker
15
对于一张200*200的图片来说,完成这个操作需要近0.03秒的时间,因此我认为这不是最好的解决方案。 - LiangWang
2
它只会将左上角和右上角的角落四舍五入。为什么? - Prateek
2
@vinc3m1的解决方案在这里 https://github.com/makeramen/RoundedImageView 很好用!还可以看看他的答案 (https://dev59.com/xHE95IYBdhLWcg3wGZ9_#15032283) - nommer
18
尝试设置ImageView的ScaleType时出现问题,只有fitXY起作用,centerCrop和其他选项会显示不可预测的结果,这里有人遇到相同的问题吗? - Shivansh
显示剩余12条评论

376

另一种简单的方法是使用带有圆角和内部 ImageView 的 CardView:

  <androidx.cardview.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:cardCornerRadius="8dp"
            android:layout_margin="5dp"
            android:elevation="10dp">

            <ImageView
                android:id="@+id/roundedImageView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/image"
                android:background="@color/white"
                android:scaleType="centerCrop"
                />
        </androidx.cardview.widget.CardView>

输入图像描述


10
看起来是个不错的解决方法。但在安卓5.0以下版本无法剪切图片。 - Nikolai
6
如果我们只想在左上角和右上角拥有圆角而不是所有的角落,该怎么办? - Pratik Singhal
3
在任何视图中应用曲线边框的方法非常简单且智能。 - AMI CHARADAVA
1
好的解决方案,但仅支持 API 级别 21 及以上的高程。 - Saad Bilal
2
要禁用卡片高度,使用app:cardElevation="0dp"(而不是android:elevation) - Johnny Five
显示剩余5条评论

275

API 21新增了基于圆角形状进行剪辑的功能,可在View类中使用。

只需按照以下步骤:

  • 创建一个圆角形状的可绘制对象,例如:

res/drawable/round_outline.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <corners android:radius="10dp" />
    ...
</shape>
  • 将Drawable设置为ImageView的背景:android:background="@drawable/round_outline"
  • 根据这份文档,你只需要添加android:clipToOutline="true"即可实现。

不幸的是,存在一个Bug,该XML属性无法被识别。幸运的是,我们仍然可以在Java中设置剪辑:

  • 在你的Activity或Fragment中:ImageView.setClipToOutline(true)

它看起来会像这样:

enter image description here

注意:

此方法适用于任何 Drawable形状(不仅仅是圆形)。它将把ImageView剪裁成您在Drawable xml中定义的任何形状轮廓。

关于ImageView的特殊说明

setClipToOutline()仅在View的背景设置为形状Drawable时才适用。如果存在此背景形状,则视图将此形状的轮廓用作剪辑和阴影的边框。

这意味着,如果您想使用setClipToOutline()来使ImageView的角落变圆,您必须使用android:src而不是android:background设置图片(因为背景必须设置为您的圆形形状)。如果你一定要使用背景来设置你的图像而不是src,你可以使用以下解决方法:

  • 创建一个布局并将其背景设置为您的形状Drawable
  • 将该布局包裹在ImageView周围(没有填充)
  • ImageView(包括布局中的任何其他内容)现在将显示为带有圆角的布局形状。

13
错误:(x) 包 'android' 中找不到属性 'clipToOutline' 的资源标识符。 - Anu Martin
16
要替代android:clipToOutline,必须使用android:outlineProvider="background" - WindRider
11
我非常“喜欢”谷歌的另一个原因是,这个漏洞几乎和我同龄,但它仍然存在于它的小世界中。谷歌似乎对此毫不在意。)) - Farid
5
当我仅将背景形状的左上角和右上角设置为圆角时,似乎无法成功使用这种方法。 - Peterdk
1
在2022年,大家应该遵循这个答案。非常感谢提到在活动或片段中使用ImageView.setClipToOutline(true) - Khaled Saifullah
显示剩余7条评论

221

Material Components Library的版本1.2.0-alpha03开始,新增了ShapeableImageView

您可以使用类似以下的代码:

<com.google.android.material.imageview.ShapeableImageView
    ...
    app:shapeAppearanceOverlay="@style/roundedImageView"
    app:srcCompat="@drawable/ic_image" />

在你的themes.xml文件中:

<style name="roundedImageView" parent="">
    <item name="cornerFamily">rounded</item>
    <item name="cornerSize">8dp</item>
</style>

或者以编程方式:

float radius = getResources().getDimension(R.dimen.default_corner_radius);
imageView.setShapeAppearanceModel(imageView.getShapeAppearanceModel()
    .toBuilder()
    .setAllCorners(CornerFamily.ROUNDED,radius)
    .build());

enter image description here


使用 Jetpack Compose,您可以使用 clip Modifier 并应用 RoundedCornerShape
Image(
    painter = painterResource(R.drawable.xxxx),
    contentDescription = "xxxx",
    contentScale = ContentScale.Crop,            
    modifier = Modifier
        .size(64.dp)
        .clip(RoundedCornerShape(8.dp))             
)

1
如果图像具有背景颜色,则当前会在圆角下弹出背景颜色。这也忽略了图像上的scaleType属性。 - inokey
当我们在程序中增加大小时,它无法正常工作。 - Arpit Patel
1
简单的解决方案,适用于根布局背景。 - Rick Robin
我可以请求具体的文档吗?我没有找到它。 - Kakaranara
我曾经花了3年时间寻找像这样的解决方案,感谢您。 - Tam Huynh

217

虽然上面的答案可行,但Android核心开发人员Romain Guy在他的博客中展示了一种更好的方法,通过使用着色器而不是创建位图副本来使用更少的内存。其功能的基本要点如下:

BitmapShader shader;
shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(shader);

RectF rect = new RectF(0.0f, 0.0f, width, height);

// rect contains the bounds of the shape
// radius is the radius in pixels of the rounded corners
// paint contains the shader that will texture the shape
canvas.drawRoundRect(rect, radius, radius, paint);

这种方法的优点在于:
  • 不会创建位图的副本,因此在处理大型图像时使用大量内存的问题得到解决 [相对于其他答案]
  • 支持抗锯齿 [相对于clipPath方法]
  • 支持透明度 [相对于xfermode+porterduff方法]
  • 支持硬件加速 [相对于clipPath方法]
  • 仅绘制一次到画布上 [相对于xfermode和clippath方法]

我已经基于此代码创建了RoundedImageView,将此逻辑封装到一个ImageView中,并添加了适当的ScaleType支持和可选的圆角边框。


1
没有其他人报告内存不足的问题,所以这一定是你在代码之外做的一些错误操作。示例正确地在适配器中保留了有限的位图集合而不在每次绘制视图时重新创建它们。这里展示的示例是Drawable中draw()方法的片段,它使用对原始位图的引用并正常工作。它意味着要更改原始位图,而只是用圆角渲染它。在示例中,居中裁剪也可以正常工作。 - vinc3m1
1
如果您能提供屏幕截图或设备,说明它无法正常工作,甚至可以提供修复和拉取请求,那将是最有效的。我已经在我的设备上多次测试了旋转,内存保持不变。 - vinc3m1
如果我只想圆一些边角,而不是全部怎么办? - CQM
@CQM 我的库由于画布API的限制无法直接支持此功能:http://developer.android.com/reference/android/graphics/Canvas.html#drawRoundRect(android.graphics.RectF, float, float, android.graphics.Paint) 您需要使用另一种方法,可能会或可能不会进行硬件加速。 - vinc3m1
12
请注意,如果图像尺寸超过2048像素,则此方法将无法使用。着色器不支持纹理大于此尺寸。 - Gábor
显示剩余12条评论

146
在Support库的v21版本中,现在有一种解决方案:它被称为RoundedBitmapDrawable。它基本上就像普通的Drawable,只是你可以为其指定一个用于剪裁的角半径:
setCornerRadius(float cornerRadius)

因此,从Bitmap src和目标 ImageView 开始,它看起来会像这样:

RoundedBitmapDrawable dr = RoundedBitmapDrawableFactory.create(res, src);
dr.setCornerRadius(cornerRadius);
imageView.setImageDrawable(dr);

3
非常感谢,下一步是:https://dev59.com/d2Af5IYBdhLWcg3wOQm2 - David d C e Freitas
实际上有 android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory,所以 v4 也支持它。 - deadfish
2
@deadfish 是的,它在v4支持库中,但是直到支持库的REVISION 21版本才有。 - tyczj
3
这个解决方案简单且更新。它编写的代码最少且可扩展性强。可以使用来自本地文件、缓存的可绘制资源,甚至 Volley 的 NetworkImageView 来处理图像。正如 @Jonik 指出的那样,我非常同意这应该是现在应该采纳的答案。 - katie
3
很遗憾,这个功能不支持scaleTypecenterCrop (支持库v25.3.1)。 - rocknow
你可以对图像视图进行居中裁剪。 - user158

107
一个快速的xml解决方案 -
<android.support.v7.widget.CardView
            android:layout_width="40dp"
            android:layout_height="40dp"
            app:cardElevation="0dp"
            app:cardCornerRadius="4dp">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rounded_user_image"
        android:scaleType="fitXY"/>

</android.support.v7.widget.CardView>

您可以在 CardView 上设置所需的宽度、高度和半径,并在 ImageView 上设置 scaleType。

使用 AndroidX,使用 <androidx.cardview.widget.CardView>


6
这个方法在我的情况下适用于 androidx.cardview.widget.CardView。 - Ivan
对我没用,我正在使用androidx.cardview.widget.CardView和scaleType centerCrop。 - xanexpt
@xanexpt 在你的情况下,图像的尺寸可能小于卡片视图的尺寸。 - Chirag Mittal
@YellowJ 你是否将“centerInside”设置为你的scaleType了? - Chirag Mittal
@ChiragMittal 我正在使用centerCrop - YellowJ
显示剩余5条评论

84

我已经使用自定义ImageView完成了:

public class RoundRectCornerImageView extends ImageView {

    private float radius = 18.0f;
    private Path path;
    private RectF rect;

    public RoundRectCornerImageView(Context context) {
        super(context);
        init();
    }

    public RoundRectCornerImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RoundRectCornerImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        path = new Path();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        rect = new RectF(0, 0, this.getWidth(), this.getHeight());
        path.addRoundRect(rect, radius, radius, Path.Direction.CW);
        canvas.clipPath(path);
        super.onDraw(canvas);
    }
}

使用方法:

<com.mypackage.RoundRectCornerImageView
     android:id="@+id/imageView"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@drawable/image"
     android:scaleType="fitXY" />

输出:

在此输入图片描述

希望这对你有所帮助。


2
即使使用android:scaleType="centerCrop",它仍然可以正常工作,简单易上手,并且不会创建新的位图。谢谢! - ernazm
2
@Hiren 这个解决方案对于带有背景图像的ImageView运行良好。但是对于仅具有背景颜色而没有图像的ImageView则无法正常工作。您能告诉我为什么会出现这种情况吗? - user2991413
2
这个解决方案仅适用于设置了android:background的图像集,而不适用于android:src - CoolMind
1
我已经改进了这段代码 CircularImage - S Kumar
完美!如果有人需要圆角(不是四个角都圆),请查看此答案:https://dev59.com/H57ha4cB1Zd3GeqPoMFi - Thomas Pires
显示剩余4条评论

59

我发现这两种方法对于得出一个可行的解决方案非常有帮助。下面是我的综合版本,它是像素无关的,并且允许您在一些方角中使用相同的半径而其余的圆角则具有相同的半径(这是通常的用例)。感谢上面两种解决方案。

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, h/2, w/2, h, paint);
    }
    if (squareTR ){
        canvas.drawRect(w/2, h/2, w, h, paint);
    }
    if (squareBL ){
        canvas.drawRect(0, 0, w/2, h/2, paint);
    }
    if (squareBR ){
        canvas.drawRect(w/2, 0, w, h/2, 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);
}

希望这能有所帮助!


4
非常棒,特别是扩展ImageView部分。 - Someone Somewhere
2
保持ImageView#onDraw()逻辑的简单方法是将圆角位图设置为ImageView的可绘制对象,并将super.onDraw()留给绘制位图。 我创建了一个类[RoundedCornerImageView](http://code.google.com/p/android-batsg/source/browse/android-batsg/src/com/chauhai/android/batsg/widget/RoundedCornerImageView.java),其使用示例在[此处](http://code.google.com/p/android-batsg/wiki/RoundedCornerImageView)。 请注意,我使用的getRoundedCornerBitmap()不是像素独立的。 - umbalaconmeogia
这里是umba的RoundedCornerImageView的源代码:http://code.google.com/p/android-batsg/source/browse/trunk/android-batsg/src/com/chauhai/android/batsg/widget/RoundedCornerImageView.java - Someone Somewhere
@Caspar Harmer,工作良好,只需要进行一次编辑...有四个条件...只需将它们更改为如果我设置为true,true,false,false,则会设置底部角而不是顶部角...否则代码正常工作。因此,squareTL和squareTR分别是squareBL和squareBR的条件..反之亦然。 - TheFlash
在onDraw方法中,当执行Bitmap b = ((BitmapDrawable)drawable).getBitmap();时,我遇到了NullPointerException异常。有什么解决方案吗? - Geek Guy
显示剩余2条评论

50

圆形图像使用 ImageLoader,可以在这里找到。

创建DisplayImageOptions

DisplayImageOptions options = new DisplayImageOptions.Builder()
    // this will make circle, pass the width of image 
    .displayer(new RoundedBitmapDisplayer(getResources().getDimensionPixelSize(R.dimen.image_dimen_menu))) 
    .cacheOnDisc(true)
    .build();

imageLoader.displayImage(url_for_image,ImageView,options);

您也可以使用Square提供的Picasso库。

Picasso.with(mContext)
    .load(com.app.utility.Constants.BASE_URL+b.image)
    .placeholder(R.drawable.profile)
    .error(R.drawable.profile)
    .transform(new RoundedTransformation(50, 4))
    .resizeDimen(R.dimen.list_detail_image_size, R.dimen.list_detail_image_size)
    .centerCrop()
    .into(v.im_user);

您可以在此处下载RoundedTransformation文件:这里

3
毕加索图书馆是不错的选择,并且非常易于实现,非常感谢!+1 - Hitesh
3
Picasso库似乎无法转换“placeholder”和“error”图像,所以如果您的图像加载失败(出现错误)或最初加载需要一段时间(占位符),它将不会显示成圆形图像。 - Pelpotronic
这个库提供了其他有用的Picasso转换,其中还包括RoundedCornersTransformation:https://github.com/wasabeef/picasso-transformations - Massimo
1
在 Univ. Image Loader 中裁剪图像无法正常工作。 - Yar

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