设计一个图片选择器(下篇)

在前两篇中已经完成了数据的展示,就剩下图片显示功能。可以自己写图片加载库,或者使用当前使用比较广泛的图片加载库例如Picasso,Glide或Fresco。自己写的话还是比较麻烦,需要处理内存管理,性能,图片处理等方面,而且如gif等动图就无法加载。选择库的话Picasso有些弱了,Fresco又比较大 ,暂时就用Glide为例来处理。

缩略图的加载

图片选择页面,文件夹列表以及媒体文件的列表都是缩略图。使用glide加载图片和视频的缩略图的代码

        GlideApp.with(view.getContext())
                .asBitmap()
                .override(width, height)
                .load(path)
                .placeholder(R.drawable.ic_image_default)
                .error(R.drawable.ic_image_default)
                .fallback(R.drawable.ic_image_default)
                .into(view);

不管是png,gif还是webp格式的图片都可以加载。而对于视频,Glide的加载方式是尝试从数据库中查找缩略图,如果没有会使用MediaMetadataRetriever加载视频的第一帧(也可以设置RequestOptions中的frameTimeMicros参数来设置取第几秒的封面)。

而如果是音频的话则需要自定义了,方法如下

首先定义一个AudioCoverModel类

public class AudioCoverModel {
    private final String path;

    public AudioCoverModel(String path) {
        this.path = path;
    }

    public String getPath() {
        return path;
    }

    @Override
    public int hashCode() {
        int code = 1;
        if (!TextUtils.isEmpty(path)){
            code += path.hashCode();
        }
        return code;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof AudioCoverModel) {
            AudioCoverModel item = (AudioCoverModel) obj;
            return TextUtils.equals(item.path, path);
        }
        return false;
    }
}

注意的是需要重写equals方法和hashCode方法。

然后定义一个AudioCoverLoader,可以 注册到Glide中,代码如下

public class AudioCoverLoader implements ModelLoader<AudioCoverModel, InputStream> {
    private final Context context;

    public AudioCoverLoader(Context context) {
        this.context = context;
    }

    @NonNull
    @Override
    public LoadData<InputStream> buildLoadData(@NonNull AudioCoverModel coverModel, int width, int height, @NonNull Options options) {
        return new LoadData<>(new ObjectKey(coverModel), new AudioCoverFetcher(coverModel, context));
    }

    @Override
    public boolean handles(@NonNull AudioCoverModel AudioCoverModel) {
        return true;
    }

    public static class Factory implements ModelLoaderFactory<AudioCoverModel, InputStream> {
        private final Context context;

        public Factory(Context context) {
            this.context = context;
        }

        @NonNull
        @Override
        public ModelLoader<AudioCoverModel, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
            return new AudioCoverLoader(context);
        }

        @Override
        public void teardown() {

        }
    }
}

最后需要定义AudioCoverFetcher类来进行封面的加载,代码如下

public class AudioCoverFetcher implements DataFetcher<InputStream> {

    private final AudioCoverModel model;
    private InputStream stream;
    private final Context context;

    AudioCoverFetcher(AudioCoverModel model, Context context) {
        this.model = model;
        this.context = context;
    }

    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataFetcher.DataCallback<? super InputStream> callback) {
        String thumbnail = FileUtil.getAudioThumbnail(context, model.getPath());
        if (!TextUtils.isEmpty(thumbnail)) {
            try {
                File file = new File(thumbnail);
                if (file.exists() && file.length() > 0) {
                    stream = new FileInputStream(thumbnail);
                    callback.onDataReady(stream);
                    return;
                }
            } catch (Exception e) {
                LogUtil.d("getAudioThumbnail", "path is not null:" + e.getMessage() + ", path:" + model.getPath());
            }
        }
        MediaMetadataRetriever retriever = null;
        try {
            retriever = new MediaMetadataRetriever();
            retriever.setDataSource(model.getPath());
            byte[] picture = retriever.getEmbeddedPicture();
            if (null != picture) {
                stream = new ByteArrayInputStream(picture);
                callback.onDataReady(stream);
            } else {
                callback.onLoadFailed(new FileNotFoundException());
                LogUtil.d("getEmbeddedPicture", "is null, path:" + model.getPath());
            }
        } catch (Exception e) {
            callback.onLoadFailed(e);
            LogUtil.d("onLoadFailed", e.getMessage() + ", path:" + model.getPath());
        } finally {
            if (retriever != null) {
                retriever.release();
            }
        }
    }

    @Override
    public void cleanup() {
        try {
            if (null != stream) {
                stream.close();
            }
        } catch (IOException ignore) {
        }
    }

    @Override
    public void cancel() {
        // cannot cancel
    }


    @NonNull
    @Override
    public Class<InputStream> getDataClass() {
        return InputStream.class;
    }

    @NonNull
    @Override
    public DataSource getDataSource() {
        return DataSource.LOCAL;
    }

}

其实是参考了视频加载的逻辑,先从媒体库中加载专辑封面,如果没有的话则使用MediaMetadataRetriever加载。最后需要自定义一个AppGlideModule,在registerComponents方法中进行注册。

@GlideModule
public class MyGlideModule extends AppGlideModule {

    @Override
    public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
        registry.append(AudioCoverModel.class, InputStream.class, new AudioCoverLoader.Factory(context));
    }

}

这样加载音频时就会走自定义的代码,与视频都有封面图不同的是,不是所有的音频都有封面,加载音频的代码是这样的,

        GlideApp.with(view.getContext())
                .asBitmap()
                .override(width, height)
                .load(new AudioCoverModel(path))
                .placeholder(R.drawable.ic_audio_default)
                .error(R.drawable.ic_audio_default)
                .fallback(R.drawable.ic_audio_default)
                .into(view);

加载大图(预览图)

在预览功能中,需要缩放功能,常见的开源库有PhotoView,subsampling-scale-image-view(简称SSIV)等等,其中SSIV对于大图处理比较好,不会模糊,占用内存小,但是却无法显示GIF等动图。

那么预览时就需要使用两种图片,如果是静态图则使用SSIV,如果是GIF等动图就使用ImageView,只是无法进行放大缩小。

SSIV直接加载静态图的代码如下

ssiv.setImage(ImageSource.uri(Uri.fromFile(new File(path))));

而对于音视频封面则需要使用Glide加载出bitmap后再加载到SSIV中,

GlideApp.with(view.getContext())
                .asBitmap()
                .load(isVideo ? path : new AudioCoverModel(path))
                .into(new CustomTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
                        ssiv.setImage(ImageSource.cachedBitmap(resource));
                    }

                });

对于GIF来说Glide可以加载,加载GIF的代码

        GlideApp.with(imageView.getContext())
                .asGif()
                .load(path)
                .placeholder(R.drawable.ic_image_default)
                .error(R.drawable.ic_image_default)
                .fallback(R.drawable.ic_image_default)
                .into(imageView);

而APNG,动画webp等动态图片的加载需要引入另外的库,如果没有则只能使用静态图了。

这样大图的加载和预览功能也完成了。到此整个媒体文件选择库就完成了,源码已经分享到github,源码地址是https://github.com/jklwan/MediaPicker

还待解决的问题,当前设计的是support库版本,还需一个使用androidx的版本;android 10的兼容问题;gif因为使用的是普通ImageView不能缩放;这些问题还要慢慢来解决。

发布了53 篇原创文章 · 获赞 17 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/jklwan/article/details/100785584