Android深入

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

2019-08-23  本文已影响0人  沐小木沐

当我们用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);
    }
上一篇下一篇

猜你喜欢

热点阅读