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

本文详细介绍如何使用Glide库加载音视频封面图,包括自定义AudioCoverModel类、AudioCoverLoader类及AudioCoverFetcher类,实现从媒体库加载封面或使用MediaMetadataRetriever获取第一帧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在前两篇中已经完成了数据的展示,就剩下图片显示功能。可以自己写图片加载库,或者使用当前使用比较广泛的图片加载库例如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不能缩放;这些问题还要慢慢来解决。

参考资源链接:[Android仿微信朋友圈图片选择器:3张图片上传与编辑](https://wenku.youkuaiyun.com/doc/4r53iqsfpn?utm_source=wenku_answer2doc_content) 要实现一个类似微信朋友圈的图片选择器,你需要考虑到多方面的技术和细节,包括图片选择逻辑、用户界面设计和设备兼容性处理。下面是一些关键步骤和建议: 1. **图片选择逻辑**:你可以使用`Intent.ACTION_PICK`来调用系统的图片选择器,或者使用`startActivityForResult`结合自定义的图片选择界面来实现。关键在于处理用户的选择结果,将图片路径或URI存储起来。对于拍照功能,可以使用`Camera`类或Camera2 API,处理权限请求,并在拍照完成后保存图片到指定位置。 2. **适配不同分辨率屏幕**:在`res`目录下创建不同的布局文件夹,比如`layout`、`layout-large`、`layout-xlarge`等,以便为不同尺寸的屏幕提供合适的布局。 3. **用户界面设计**:使用`GridView`来展示选中的图片,并且利用`universal-image-loader`框架来优化图片的加载和显示。确保为每个图片项提供删除功能,以及为拍照按钮提供明确的用户提示。 4. **适配器的使用**:创建一个自定义适配器继承自`BaseAdapter`,在适配器中管理图片列表,并为每个图片项提供一个视图。当图片被添加或删除时,适配器应能够响应变化并更新视图。 5. **设备兼容性**:针对不同版本的Android系统和不同设备,你可能需要使用兼容性库和检查运行时权限。例如,从Android 6.0开始,需要动态请求摄像头和存储权限。 6. **性能优化**:在图片较多时,使用`universal-image-loader`可以有效管理内存和磁盘缓存,减少应用崩溃的风险。同时,考虑异步处理图片加载,避免阻塞主线程。 通过以上步骤,你可以构建一个功能完善且用户体验良好的图片选择器。如果你希望获取更深入的技术细节和代码实现,可以参考《Android仿微信朋友圈图片选择器:3张图片上传与编辑》这篇文章。它详细介绍了如何在Android Studio 1.4和SDK 23环境下开发一个图片选择器,并且涉及到了拍照上传和图片移除等核心功能的实现。 参考资源链接:[Android仿微信朋友圈图片选择器:3张图片上传与编辑](https://wenku.youkuaiyun.com/doc/4r53iqsfpn?utm_source=wenku_answer2doc_content)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值