项目中想使用一个框架实现常见的所有图片格式的加载,并且对代码的影响降到最低,Glide框架提供了很好的扩展,这里使用Glide+androidsvg+fresco实现加载GIF、SVG、WebP等多种格式的图片和动画文件。
实现原理
具体步骤
1、添加依赖
dependencies {
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
implementation 'com.caverock:androidsvg-aar:1.3'
implementation "com.facebook.fresco:animated-webp:1.9.0"
}
2、SVG
1、自定义ResourceDecoder,将InputStream转为SVG对象:
import android.support.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.ResourceDecoder;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.resource.SimpleResource;
import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGParseException;
import java.io.IOException;
import java.io.InputStream;
/**
* Decodes an SVG internal representation from an {@link InputStream}.
*/
public class SvgDecoder implements ResourceDecoder {
@Override
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
// TODO: Can we tell?
return true;
}
public Resource decode(@NonNull InputStream source, int width, int height,
@NonNull Options options)
throws IOException {
try {
SVG svg = SVG.getFromInputStream(source);
return new SimpleResource<>(svg);
} catch (SVGParseException ex) {
throw new IOException("Cannot load SVG from stream", ex);
}
}
}
2、自定义ResourceTranscoder,将SVG转为Drawable对象
import android.graphics.Picture;
import android.graphics.drawable.PictureDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.resource.SimpleResource;
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
import com.caverock.androidsvg.SVG;
/**
* Convert the {@link SVG}'s internal representation to an Android-compatible one
* ({@link Picture}).
*/
public class SvgDrawableTranscoder implements ResourceTranscoder {
@Nullable
@Override
public Resource transcode(@NonNull Resource toTranscode,
@NonNull Options options) {
SVG svg = toTranscode.get();
Picture picture = svg.renderToPicture();
PictureDrawable drawable = new PictureDrawable(picture);
return new SimpleResource<>(drawable);
}
}
3、SVG格式不能硬解码,自定义RequestListener,在onResourceReady时设置ImageView为软解码
import android.graphics.drawable.PictureDrawable;
import android.widget.ImageView;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.ImageViewTarget;
import com.bumptech.glide.request.target.Target;
/**
* Listener which updates the {@link ImageView} to be software rendered, because
* {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on
* a hardware backed {@link android.graphics.Canvas Canvas}.
*/
public class SvgSoftwareLayerSetter implements RequestListener {
@Override
public boolean onLoadFailed(GlideException e, Object model, Target target,
boolean isFirstResource) {
ImageView view = ((ImageViewTarget>) target).getView();
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
return false;
}
@Override
public boolean onResourceReady(PictureDrawable resource, Object model,
Target target, DataSource dataSource, boolean isFirstResource) {
ImageView view = ((ImageViewTarget>) target).getView();
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
return false;
}
}
3、WebP
1、WebpFrameLoader,加载每帧图片
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.gifdecoder.GifDecoder;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Util;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class WebpFrameLoader {
final RequestManager requestManager;
private final GifDecoder gifDecoder;
private final Handler handler;
private final List callbacks;
private final BitmapPool bitmapPool;
private boolean isRunning;
private boolean isLoadPending;
private boolean startFromFirstFrame;
private RequestBuilder requestBuilder;
private DelayTarget current;
private boolean isCleared;
private DelayTarget next;
private Bitmap firstFrame;
private Transformation transformation;
public WebpFrameLoader(Glide glide, GifDecoder gifDecoder, int width, int height, Transformation transformation, Bitmap firstFrame) {
this(glide.getBitmapPool(), Glide.with(glide.getContext()), gifDecoder, null, getRequestBuilder(Glide.with(glide.getContext()), width, height), transformation, firstFrame);
}
WebpFrameLoader(BitmapPool bitmapPool, RequestManager requestManager, GifDecoder gifDecoder, Handler handler, RequestBuilder requestBuilder, Transformation transformation, Bitmap firstFrame) {
this.callbacks = new ArrayList();
this.isRunning = false;
this.isLoadPending = false;
this.startFromFirstFrame = false;
this.requestManager = requestManager;
if (handler == null) {
handler = new Handler(Looper.getMainLooper(), new FrameLoaderCallback());
}
this.bitmapPool = bitmapPool;
this.handler = handler;
this.requestBuilder = requestBuilder;
this.gifDecoder = gifDecoder;
this.setFrameTransformation(transformation, firstFrame);
}
private static RequestBuilder getRequestBuilder(RequestManager requestManager, int width, int height) {
return requestManager.asBitmap().apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE).skipMemoryCache(true).override(width, height));
}
void setFrameTransformation(Transformation transformation, Bitmap firstFrame) {
this.transformation = Preconditions.checkNotNull(transformation);
this.firstFrame = Preconditions.checkNotNull(firstFrame);
this.requestBuilder = this.requestBuilder.apply((new RequestOptions()).transform(transformation));
}
Transformation getFrameTransformation() {
return this.transformation;
}
Bitmap getFirstFrame() {
return this.firstFrame;
}
void subscribe(FrameCallback frameCallback) {
if (this.isCleared) {
throw new IllegalStateException("Cannot subscribe to a cleared frame loader");
} else {
boolean start = this.callbacks.isEmpty();
if (this.callbacks.contains(frameCallback)) {
throw new IllegalStateException("Cannot subscribe twice in a row");
} else {
this.callbacks.add(frameCallback);
if (start) {
this.start();
}
}
}
}
void unsubscribe(FrameCallback frameCallback) {
this.callbacks.remove(frameCallback);
if (this.callbacks.isEmpty()) {
this.stop();
}
}
int getWidth() {
return this.getCurrentFrame().getWidth();
}
int getHeight() {
return this.getCurrentFrame().getHeight();
}
int getSize() {
return this.gifDecoder.getByteSize() + this.getFrameSize();
}
int getCurrentIndex() {
return this.current != null ? this.current.index : -1;
}
private int getFrameSize() {
return Util.getBitmapByteSize(this.getCurrentFrame().getWidth(), this.getCurrentFrame().getHeight(), t