【问题标题】:How to create a partially-rounded-corners-rectangular drawable with center-crop and without creating new bitmap?如何在不创建新位图的情况下创建带有中心裁剪的部分圆角矩形可绘制对象?
【发布时间】:2016-07-22 02:58:44
【问题描述】:

背景

我已经了解了如何从位图创建圆形可绘制对象,以及如何在其周围添加轮廓(AKA 笔划),here。

问题

我不知道如何在不创建新位图的情况下仅对位图的某些角进行舍入,而不创建新的位图,并且仍然为中心裁剪的 ImageView 执行类似的任务。

我发现了什么

这是我发现的,但它确实创建了一个新的位图,并且在带有 center-crop 的 imageView 中使用它时(来源 here):

/**
 * Create rounded corner bitmap from original bitmap.
 *
 * @param input                               Original bitmap.
 * @param cornerRadius                        Corner radius in pixel.
 * @param squareTL,squareTR,squareBL,squareBR where to use square corners instead of rounded ones.
 */
public static Bitmap getRoundedCornerBitmap(final Bitmap input, final float cornerRadius, final int w, final int h,
                                            final boolean squareTL, final boolean squareTR, final boolean squareBL, final boolean squareBR) {
    final Bitmap output = Bitmap.createBitmap(w, h, Config.ARGB_8888);
    final Canvas canvas = new Canvas(output);
    final int color = 0xff424242;
    final Rect rect = new Rect(0, 0, w, h);
    final RectF rectF = new RectF(rect);
    // make sure that our rounded corner is scaled appropriately
    Paint paint = new Paint();
    paint.setXfermode(null);
    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, 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(PORTER_DUFF_XFERMODE_SRC_IN);
    canvas.drawBitmap(input, 0, 0, paint);
    return output;
}

而且,这是我发现的用于创建作用于所有角的圆角可绘制对象:

public static class RoundedCornersDrawable extends Drawable {
    private final float mCornerRadius;
    private final RectF mRect = new RectF();
    private final BitmapShader mBitmapShader;
    private final Paint mPaint;

    public RoundedCornersDrawable(final Bitmap bitmap, final float cornerRadius) {
        mCornerRadius = cornerRadius;
        mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP,
                Shader.TileMode.CLAMP);
        mPaint = new Paint();
        mPaint.setAntiAlias(false);
        mPaint.setShader(mBitmapShader);
        mRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
    }

    @Override
    protected void onBoundsChange(final Rect bounds) {
        super.onBoundsChange(bounds);
        mRect.set(0, 0, bounds.width(), bounds.height());
    }

    @Override
    public void draw(final Canvas canvas) {
        canvas.drawRoundRect(mRect, mCornerRadius, mCornerRadius, mPaint);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public void setAlpha(final int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(final ColorFilter cf) {
        mPaint.setColorFilter(cf);
    }
}

但这种解决方案只有在 imageView 显示内容的同时保持与位图相同的纵横比并且预先确定其大小时才有效。

问题

如何创建一个居中裁剪的可绘制对象,它显示位图、具有特定角的圆角,并且还能够在其周围显示轮廓/笔划?

我想在不创建新位图或扩展 ImageView 的情况下执行此操作。仅使用将位图作为输入的可绘制对象。

【问题讨论】:

  • 你为什么不合并我的两种方法?请参阅原始线程以获取示例代码(几分钟前制作,因此可能存在一些小错误,但您有一个想法)
  • @pskink 我试过了,但它并不适合我。你的代码(这里:pastebin.com/CsuDbx3w)仍然没有像我写的那样工作,所以我发表了这篇文章。轮廓不允许图像有圆角,即使我使用 setStrokeWidth(0) 时,它不仅显示轮廓,而且不会让图像居中裁剪,并且必须具有恒定大小(使用 wrap_content 和 match_parent 不会让 imageView 显示)
  • “不允许图像有圆角”你可以有 1、2、3 或 4 个圆角,所以我不知道你的意思
  • @pskink 你错过了我写的“大纲”部分。如果没有轮廓,图像会有圆角,但如果有,则不会。即使我添加了 floats 数组,图像看起来也完全是矩形,例如: D roundedCornersDrawable = new D(b, new float[]{20, 20, 0, 0, 40, 40, 0, 0}) 。这里唯一得到圆角的是轮廓本身。当我删除轮廓时,它仍然不能很好地显示,正如我所写的。如果你愿意,我可以给你看一些截图。

标签: android bitmap drawable rounded-corners


【解决方案1】:

我总是使用这个库来实现你正在寻找的东西。你可以圆任何你想要的角,也可以添加描边。

https://github.com/vinc3m1/RoundedImageView

您可以使用它或查看它的源代码来获得灵感。

编辑

无需使用 Image View 并自己制作位图或可绘制并在 Image View 中显示。 只需将 Image View 替换为 Rounded Image View ,它就会为您处理所有事情,而无需任何额外的代码工作! 这是示例:

<com.makeramen.roundedimageview.RoundedImageView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/imageView1"
    android:scaleType="centerCrop"
    app:riv_corner_radius="8dp"
    app:riv_border_width="2dp"
    app:riv_border_color="#333333"
    app:riv_oval="false" />

在代码中,只需将任何图像资源传递给它或使用任何图像加载器。

RoundedImageView myRoundedImage=(RoundedImageView)findViewById(R.id.imageView1);
myRoundedImage.setImageResource(R.drawable.MY_DRAWABLE);
// OR
ImageLoader.getInstance().displayImage(YOUR_IMAGE_URL, myRoundedImage);

如果你只想让特定的角变圆,试试这个:

 <com.makeramen.roundedimageview.RoundedImageView
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/imageView1"
    android:scaleType="centerCrop"
    app:riv_corner_radius_top_right="8dp"
    app:riv_corner_radius_bottom_right="8dp"
    app:riv_border_width="2dp"
    app:riv_border_color="#333333"
    app:riv_oval="false" />

【讨论】:

  • 好的,我试过了,但它不像我写的那样工作。我使用带有 RoundedDrawable 的普通 ImageView,但结果仍然很糟糕:github.com/vinc3m1/RoundedImageView/issues/152
  • 获得圆形可绘制对象或仅在视图中显示圆角图像是否重要? @androiddeveloper
  • 我不明白这个问题。我想要一个可以在普通 ImageView 中使用的圆形可绘制对象(包括特定角和轮廓)。
  • 我编辑了我的答案,看看它现在是否对你有帮助。 @androiddeveloper
  • 您写道,我需要扩展 ImageView。问题是关于 Drawable,没有扩展 ImageView。我已编辑问题以使其更清楚。
【解决方案2】:

好吧,您可以创建一个名为 my_background 的新 .xml 可绘制对象并将此代码粘贴到下面:

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle"
    >
    <solid android:color="#00000000"/>
    <corners
        android:bottomLeftRadius="12dp"
        android:bottomRightRadius="12dp"
        android:topLeftRadius="12dp"
        android:topRightRadius="12dp" />
    <stroke
        android:width="1dp"
        android:color="#000000"
        />
</shape>

然后,将 ImageButton、ImageView 的背景设置为新的可绘制对象,如下所示:

android:background="@drawable/my_background"
android:scaleType="centerCrop"

或以编程方式:

myView.setBackground(R.drawable.my_background);

希望对你有帮助!

编辑:

要以编程方式创建类似的可绘制对象,您可以使用它:

GradientDrawable shape = new GradientDrawable();
    shape.setShape(GradientDrawable.RECTANGLE);
    shape.setCornerRadii(new float[] { 8, 8, 8, 8, 0, 0, 0, 0 });
    shape.setColor(Color.TRANSPARENT);
    shape.setStroke(3, Color.BLACK);
    v.setBackgroundDrawable(shape);

【讨论】:

  • 这是一个不错的解决方案,但我问的是编程解决方案。我通过代码创建可绘制对象,它将位图作为输入,并且不强制我为 imageView 设置不同的背景。另外,正如我所见(如果我错了,请纠正我),您的解决方案迫使我在后面设置白色,而我想要的是在后面设置透明色。
【解决方案3】:

好的,这是我的尝试。唯一的问题是“int corners”是一组标志。比如0b1111,其中每个1代表一个要倒圆角的角,0则相反。顺序是 TOP_LEFT、TOP_RIGHT、BOTTOM_LEFT、BOTTOM_RIGHT。

示例用法优先,为便于阅读而格式化:

((ImageView) findViewById(R.id.imageView)).setImageDrawable(
    new RoundedRectDrawable(
        getResources(),
        bitmap,
        (float) .15,
        0b1101,
        8,
        Color.YELLOW
    )
);

代码:

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.SystemClock;

/**
 * Created by mliu on 4/15/16.
 */
public class RoundedRectDrawable extends BitmapDrawable {
private final BitmapShader bitmapShader;
private final Paint p;
private final RectF rect;
private final float borderRadius;
private final float outlineBorderRadius;
private final int w;
private final int h;
private final int corners;
private final int border;
private final int bordercolor;

public RoundedRectDrawable(final Resources resources, final Bitmap bitmap, float borderRadiusSeed, int corners, int borderPX, int borderColor) {
    super(resources, bitmap);
    bitmapShader = new BitmapShader(getBitmap(),
            BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
    final Bitmap b = getBitmap();
    p = getPaint();
    p.setAntiAlias(true);
    p.setShader(bitmapShader);
    w = b.getWidth();
    h = b.getHeight();
    rect = new RectF(0,0,w,h);
    borderRadius = borderRadiusSeed * Math.min(w, h);
    border = borderPX;
    this.corners = corners;
    this.bordercolor = borderColor;
    outlineBorderRadius = borderRadiusSeed * Math.min(w+border,h+border);
}

@Override
public void draw(final Canvas canvas) {
    if ((corners&0b1111)==0){
        if (border>0) {
            Paint border = new Paint();
            border.setColor(bordercolor);
            canvas.drawRect(rect, border);
        }
        canvas.drawRect(rect.left + border, rect.top + border, rect.width() - border, rect.height() - border, p);
    } else {
        if (border >0) {
            Paint border = new Paint();
            border.setColor(bordercolor);
            canvas.drawRoundRect(rect, outlineBorderRadius, outlineBorderRadius, border);

            if ((corners & 0b1000) == 0) {
                //top left
                canvas.drawRect(rect.left, rect.top, rect.width() / 2, rect.height() / 2, border);
            }
            if ((corners & 0b0100) == 0) {
                //top right
                canvas.drawRect(rect.width() / 2, rect.top, rect.width(), rect.height() / 2, border);
            }
            if ((corners & 0b0010) == 0) {
                //bottom left
                canvas.drawRect(rect.left, rect.height() / 2, rect.width() / 2, rect.height(), border);
            }
            if ((corners & 0b0001) == 0) {
                //bottom right
                canvas.drawRect(rect.width() / 2, rect.height() / 2, rect.width(), rect.height(), border);
            }
        }
        canvas.drawRoundRect(new RectF(rect.left + border, rect.top + border, rect.width() - border, rect.height() - border), borderRadius, borderRadius, p);
        if ((corners & 0b1000) == 0) {
            //top left
            canvas.drawRect(rect.left + border, rect.top + border, rect.width() / 2, rect.height() / 2, p);
        }
        if ((corners & 0b0100) == 0) {
            //top right
            canvas.drawRect(rect.width() / 2, rect.top + border, rect.width() - border, rect.height() / 2, p);
        }
        if ((corners & 0b0010) == 0) {
            //bottom left
            canvas.drawRect(rect.left + border, rect.height() / 2, rect.width() / 2, rect.height() - border, p);
        }
        if ((corners & 0b0001) == 0) {
            //bottom right
            canvas.drawRect(rect.width() / 2, rect.height() / 2, rect.width() - border, rect.height() - border, p);
        }
    }
}

}

因此,如果需要,它首先处理轮廓,然后是位图。它首先用圆角矩形标记画布,然后“平方”您不想圆角的每个角。似乎非常低效,并且可能是,但在 S7 上,最小优化(角 = 0b0000,10 次 canvas.draw 调用)之前的平均案例运行时间需要约 200us。而且,根据电话使用情况,那个时间是超级不一致的。我得到了低至 80us 和高至 1.5ms。

注意/警告:我不擅长这个。这不是最优的。 SO上可能已经有更好的答案了。主题有点不常见,很难搜索。我原本不打算发布这个,但在撰写本文时,这仍然没有标记为已回答,并且库 OP 由于他们的Drawable 的问题而不想使用,实际上使用与我糟糕的解决方案非常相似的方法。所以,现在我不那么尴尬地分享这个了。此外,虽然我今天发布的 95% 是昨天写的,但我知道我从教程或 SO 帖子中获得了一些代码,但我不记得该归因于谁,因为我最终没有在我的项目中使用它.向你道歉。

【讨论】:

  • 我不知道代码的工作情况如何,但它使用“drawRoundRect”,它只能从 API 21 中获得。我没有提到它,但我需要它从 API 中工作14. 你应该尝试使用另一个“drawRoundRect”函数。另外,您能否将角设为半径的浮点数,而不是使用布尔掩码?
  • re: "floats of their radius",你的意思是你想为每个角落指定不同的半径?这不适用于此代码。即使有重大改变也没有。我可以把一些东西放在一起,但它可能会变得更加丑陋。 drawRoundedRect api 21 主要只接受单个值,而不是仅将 Rect 作为输入。因此,实际上您可以添加一个步骤,在其中使用 api 21 调用的前 4 个字段中的值创建一个 Rect,然后用该 Rect 替换这些字段。不在我的编码机 atm 上,至少稍后会更新。
【解决方案4】:

SMART 方式是使用PorterDuff 混合模式。创建任何花哨的阴影、混合、“裁剪”效果非常简单和流畅。你可以找到很多关于 PorterDuff 的好教程。这里是good one。

【讨论】:

  • 请给出一些关于drawable应该如何工作的代码,给出位图和每个角使用的半径,以及轮廓。
  • 你看一下网址了吗?详细解释了如何使用可绘制对象。
  • 我在链接中看到的是静态掩码。我要求知道如何在可绘制对象中动态地做到这一点,并能够设置具有颜色和宽度的轮廓......我该如何使用你的方法来做到这一点?
  • 如果您正在寻找开箱即用的解决方案而不进行自定义触摸或增强,只是为了获得圆角,我建议您使用 RoundedImageView。您可以设置需要圆角的角、笔触、颜色。这是网址github.com/vinc3m1/RoundedImageView
  • 这已经在多篇文章中提出,正如我所写的,我正在寻找一个 RoundedDrawable 解决方案。查看他们的 Drawable 类,它有缺陷:github.com/vinc3m1/RoundedImageView/issues/152
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多