Glide加载图片实现不同大小圆角图片

当我们用Glide图片加载器加载图片的时候,只要使用Glide 的 apply() 方法就可以实现各种类型的图片。

Glide.with(mContext).load(imgUrl).apply(mTransitionOptions).into(iv)

目前,Glide 提供了矩形,圆形,圆角的通用实现方法。
那么,如果要实现这样呢:

例1:不同圆角图片

通过查找Glide源码,我们可以发现,圆角变换
RequestOptions.bitmapTransform(new RoundedCorners(radius))
只能统一设置四个角的弧度半径,不能单独设置其中的某一个。所以要实现例1的那些效果,我们就需要构造一个Transformation转换器了。
参考圆形转换器 CircleCrop .class

public class CircleCrop extends BitmapTransformation {}

和圆角转换器 RoundedCorners.class

public final class RoundedCorners extends BitmapTransformation {}

可以发现,它们都继承于 BitmapTransformation 父类,那么我们就可以开始构造一个不同大小圆角转换器 RoundBitmapTransformation.class


import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.support.annotation.NonNull;

import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Util;

import java.nio.ByteBuffer;
import java.security.MessageDigest;

/**
 * A {@link BitmapTransformation} which rounds the corners of a bitmap.
 */
public final class RoundBitmapTransformation extends BitmapTransformation {
    private static final String ID = "com.bumptech.glide.load.resource.bitmap.RoundedCorners";
    private static final byte[] ID_BYTES = ID.getBytes(CHARSET);
    private final int leftTopRadius;
    private final int rightTopRadius;
    private final int leftBottomRadius;
    private final int rightBottomRadius;


    /**
     * @param leftTopRadius     the corner radius of Left (in device-specific pixels).
     * @param rightTopRadius    the corner radius of Top (in device-specific pixels).
     * @param leftBottomRadius  the corner radius of Right (in device-specific pixels).
     * @param rightBottomRadius the corner radius of Bottom (in device-specific pixels).
     * @throws IllegalArgumentException if rounding radius is 0 or less.
     */
    public RoundBitmapTransformation(int leftTopRadius, int rightTopRadius,
                                     int leftBottomRadius, int rightBottomRadius) {
        Preconditions.checkArgument(leftTopRadius >= 0, "leftTopRadius must be greater than 0.");
        Preconditions.checkArgument(rightTopRadius >= 0, "rightTopRadius must be greater than 0.");
        Preconditions.checkArgument(leftBottomRadius >= 0, "leftBottomRadius must be greater than 0.");
        Preconditions.checkArgument(rightBottomRadius >= 0, "rightBottomRadius must be greater than 0.");
        this.leftTopRadius = leftTopRadius;
        this.rightTopRadius = rightTopRadius;
        this.leftBottomRadius = leftBottomRadius;
        this.rightBottomRadius = rightBottomRadius;
    }

    @Override
    protected Bitmap transform(
        @NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) {
        return RoundTransformationUtils.roundedCorners(pool, toTransform,
            leftTopRadius, rightTopRadius, leftBottomRadius, rightBottomRadius);
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof RoundBitmapTransformation) {
            RoundBitmapTransformation other = (RoundBitmapTransformation) o;
            return (leftTopRadius == other.leftTopRadius) && (rightTopRadius == other.rightTopRadius) &&
                (leftBottomRadius == other.leftBottomRadius) && (rightBottomRadius == other.rightBottomRadius);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return
            Util.hashCode(ID.hashCode(),
                Util.hashCode(leftTopRadius,
                    Util.hashCode(rightTopRadius,
                        Util.hashCode(leftBottomRadius,
                            Util.hashCode(rightBottomRadius)))));
    }

    @Override
    public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
        messageDigest.update(ID_BYTES);

        byte[] leftRadiusData = ByteBuffer.allocate(4).putInt(leftTopRadius).array();
        messageDigest.update(leftRadiusData);
        byte[] topRadiusData = ByteBuffer.allocate(4).putInt(rightTopRadius).array();
        messageDigest.update(topRadiusData);
        byte[] rightRadiusData = ByteBuffer.allocate(4).putInt(leftBottomRadius).array();
        messageDigest.update(rightRadiusData);
        byte[] bottomRadiusData = ByteBuffer.allocate(4).putInt(rightBottomRadius).array();
        messageDigest.update(bottomRadiusData);
    }

}

这里最核心的就是transform()方法了,实现的是矩形图片转换成圆角图片过程。参照TransformationUtils类的实现原则,我们构造一个圆角转换工具类 RoundTransformationUtils.class


import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import android.graphics.Shader;
import android.os.Build;
import android.support.annotation.NonNull;

import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Synthetic;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A class with methods to efficiently resize Bitmaps.
 */
// Legacy Public APIs.
@SuppressWarnings("WeakerAccess")
public final class RoundTransformationUtils {

    // See #738.
    private static final Set<String> MODELS_REQUIRING_BITMAP_LOCK =
        new HashSet<>(
            Arrays.asList(
                // Moto X gen 2
                "XT1085", "XT1092", "XT1093", "XT1094", "XT1095",
                "XT1096", "XT1097", "XT1098",
                // Moto G gen 1
                "XT1031", "XT1028", "XT937C", "XT1032", "XT1008",
                "XT1033", "XT1035", "XT1034", "XT939G", "XT1039",
                "XT1040", "XT1042", "XT1045",
                // Moto G gen 2
                "XT1063", "XT1064", "XT1068", "XT1069", "XT1072",
                "XT1077", "XT1078", "XT1079"
            )
        );

    /**
     * https://github.com/bumptech/glide/issues/738 On some devices, bitmap drawing is not thread
     * safe.
     * This lock only locks for these specific devices. For other types of devices the lock is always
     * available and therefore does not impact performance
     */
    private static final Lock BITMAP_DRAWABLE_LOCK =
        MODELS_REQUIRING_BITMAP_LOCK.contains(Build.MODEL)
            ? new ReentrantLock() : new NoLock();


    private RoundTransformationUtils() {
        // Utility class.
    }


    private static Bitmap getAlphaSafeBitmap(
        @NonNull BitmapPool pool, @NonNull Bitmap maybeAlphaSafe) {
        Config safeConfig = getAlphaSafeConfig(maybeAlphaSafe);
        if (safeConfig.equals(maybeAlphaSafe.getConfig())) {
            return maybeAlphaSafe;
        }

        Bitmap argbBitmap =
            pool.get(maybeAlphaSafe.getWidth(), maybeAlphaSafe.getHeight(), safeConfig);
        new Canvas(argbBitmap).drawBitmap(maybeAlphaSafe, 0 /*left*/, 0 /*top*/, null /*paint*/);

        // We now own this Bitmap. It's our responsibility to replace it in the pool outside this method
        // when we're finished with it.
        return argbBitmap;
    }

    @NonNull
    private static Config getAlphaSafeConfig(@NonNull Bitmap inBitmap) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Avoid short circuiting the sdk check.
            if (Config.RGBA_F16.equals(inBitmap.getConfig())) { // NOPMD
                return Config.RGBA_F16;
            }
        }

        return Config.ARGB_8888;
    }

    /**
     * Creates a bitmap from a source bitmap and rounds the corners.
     *
     * <p>This method does <em>NOT</em> resize the given {@link Bitmap}, it only rounds it's corners.
     * To both resize and round the corners of an image, consider
     * {@link com.bumptech.glide.request.RequestOptions#transforms(Transformation[])} and/or
     * {@link com.bumptech.glide.load.MultiTransformation}.
     *
     * @param inBitmap          the source bitmap to use as a basis for the created bitmap.
     * @param leftTopRadius     the corner radius of Left (in device-specific pixels).
     * @param rightTopRadius    the corner radius of Top (in device-specific pixels).
     * @param leftBottomRadius  the corner radius of Right (in device-specific pixels).
     * @param rightBottomRadius the corner radius of Bottom (in device-specific pixels).
     * @return a {@link Bitmap} similar to inBitmap but with rounded corners.
     * @throws IllegalArgumentException if roundingRadius, width or height is 0 or less.
     */
    public static Bitmap roundedCorners(
        @NonNull BitmapPool pool, @NonNull Bitmap inBitmap, int leftTopRadius, int rightTopRadius,
        int leftBottomRadius, int rightBottomRadius) {

        Preconditions.checkArgument(leftTopRadius >= 0,
            "leftTopRadius must be greater than 0.");
        Preconditions.checkArgument(rightTopRadius >= 0,
            "rightTopRadius must be greater than 0.");
        Preconditions.checkArgument(leftBottomRadius >= 0,
            "leftBottomRadius must be greater than 0.");
        Preconditions.checkArgument(rightBottomRadius >= 0,
            "rightBottomRadius must be greater than 0.");

        // Alpha is required for this transformation.
        Config safeConfig = getAlphaSafeConfig(inBitmap);
        Bitmap toTransform = getAlphaSafeBitmap(pool, inBitmap);
        Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), safeConfig);

        result.setHasAlpha(true);

        BitmapShader shader = new BitmapShader(toTransform, Shader.TileMode.CLAMP,
            Shader.TileMode.CLAMP);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(shader);
        RectF rect = new RectF(0, 0, result.getWidth(), result.getHeight());
        BITMAP_DRAWABLE_LOCK.lock();
        try {
            Canvas canvas = new Canvas(result);
            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

            Path mPath = new Path();
            float[] mRadii = new float[]{
                leftTopRadius, leftTopRadius,
                rightTopRadius, rightTopRadius,
                rightBottomRadius, rightBottomRadius,
                leftBottomRadius, leftBottomRadius
            };
            mPath.addRoundRect(rect, mRadii, Path.Direction.CW);
            canvas.drawPath(mPath, paint);
            clear(canvas);
        } finally {
            BITMAP_DRAWABLE_LOCK.unlock();
        }

        if (!toTransform.equals(inBitmap)) {
            pool.put(toTransform);
        }

        return result;
    }

    // Avoids warnings in M+.
    private static void clear(Canvas canvas) {
        canvas.setBitmap(null);
    }

    private static final class NoLock implements Lock {

        @Synthetic
        NoLock() {
        }

        @Override
        public void lock() {
            // do nothing
        }

        @Override
        public void lockInterruptibly() throws InterruptedException {
            // do nothing
        }

        @Override
        public boolean tryLock() {
            return true;
        }

        @Override
        public boolean tryLock(long time, @NonNull TimeUnit unit) throws InterruptedException {
            return true;
        }

        @Override
        public void unlock() {
            // do nothing
        }

        @NonNull
        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException("Should not be called");
        }
    }
}

这里是使用图像的 Alpha 合成模式,即 PorterDuff 来实现的,☞官方文档。整个过程就是先绘制目标图像,也就是图片;再绘制原图像,即一个透明的圆角矩形,这样最终目标图像只显示和原图像重合的区域绘制路径点的方式来构造不同大小圆角矩形图片的。

或者你也可以用canvas 的 clipPath()方法先将画布裁剪成指定形状,然后再绘制,这样就能让图片按指定形状显示了。但是由于clipPath()方法不支持抗锯齿,图片边缘会有明显的毛糙感,体验并不理想。

最终使用的时候,只需要跟Glide的其他实现方式一样就可以了,

    /**
     * 加载图片, 转变为圆角图片
     *
     * @param path              图片路径或网址
     * @param iv                图片控件
     * @param leftTopRadius     左上角半径
     * @param rightTopRadius    右上角半径
     * @param leftBottomRadius  左下角半径
     * @param rightBottomRadius 右下角半径
     */
    public static void displayImageRound(Object imgUrl, ImageView iv, int leftTopRadius,
                                         int rightTopRadius, int leftBottomRadius, int rightBottomRadius) {
        Context mContext = iv.getContext();
        RequestOptions mTransitionOptions = RequestOptions.bitmapTransform(
            new RoundBitmapTransformation(leftTopRadius, rightTopRadius, leftBottomRadius, rightBottomRadius));
        Glide.with(mContext).load(imgUrl).apply(mTransitionOptions).into(iv);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容