再见二丁目 | yitimo的个人日志

再见二丁目

Android 编写一个图片选择器

发布于: 2018-10-02 08:37

此文章久未修订,请自行甄别内容准确性。

笔者项目中遇到需要选择图片的情况,一开始直接使用的是知乎开源的Matisse,(下文要讲述的自己实现的图片选择工具也从中参考了一些实现)。至于之后为什么要选择自己来实现一个选择器,原因大概如下:

  1. 难度不高(很大的原因是因为把图片渲染工作交给了Glide)
  2. 想要自己深度定制
  3. 第2点的定制内容比如:第一次选择好图片后,再次进入图片选择器支持记录上次已选中的图片

实现思路

整个图片选择器工具的实现思路大致如下:

  1. 项目准备
  2. 解藕Glide
  3. 实现列表浏览
  4. 实现原图浏览
  5. 与原页面交互实现选择

准备工作

笔者使用的是AndroidStudio + kotlin开发(Java是不可能Java的),并且目标是一个第三方库而不是完整应用。

首先新建一个Project

新建Project

新建自带的这个应用作为试例应用取名叫Sample

新建Project

然后File -> New -> New Module创建出工具类自己的模块:

新建Module

最终得到了这样一个目录结构:

基本目录结构

图片引擎

Android中图片的处理是个危险操作,很容易浪费性能甚至导致应用崩溃,笔者因此深度依赖了Glide这个库来处理图片,不过作为第三方库想要使用Glide,自己再引入一个Glide就会显得多余,而应该在最外层应用中配置好Glide,引入的第三方库若以来Glide,也应该使用外层的Glide,这就需要将第三方库与Glide的耦合给解开。笔者最终采用的思路类似Matisse,就是外层应用需要配置一个代码片段来传入并告诉我们的图片选择工具如何使用Glide

图片选择器需要准备一个结构体,来放置一些图片渲染的回调方法:

var setOrigin: ((context: Context, src: File, width: Int, height: Int, callback: (Bitmap) -> Unit) -> Unit)? = null
var setCommon: ((context: Context, imageView: ImageView, src: File) -> Unit)? = null
var setThumb: ((context: Context, imageView: ImageView, src: File, size: Int, fade: Int, holderRes: Int) -> Unit)? = null
var pauseGlide: ((context: Context) -> Unit)? = null
var resumeGlide: ((context: Context) -> Unit)? = null

其中setOrigin用于设置原图图片,setCommon用于直接设置图片作为通用方法,setThumb用于设置封面图,pauseGlideresumeGlide方法用于帮助在页面滚动时暂停和继续Glide的渲染工作以避免此类卡顿情况。

最终应用中必须在某处(比如BaseApplication)执行一个代码片段,来将Glide配置给图片选择工具:

Ymager.setOrigin = fun (context: Context, src: File, width: Int, height: Int, callback: (Bitmap) -> Unit) {
    GlideApp.with(context).asBitmap().load(src).override(width, height).fitCenter().into(object: SimpleTarget<Bitmap>() {
        override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
            callback(resource)
        }
    })
}
Ymager.setCommon = fun (context: Context, imageView: ImageView, src: File) {
    GlideApp.with(context).load(src).into(imageView)
}
Ymager.setThumb = fun (context: Context, imageView: ImageView, src: File, size: Int, fade: Int, holderRes: Int) {
    GlideApp.with(context)
            .load(src)
            .error(holderRes)
            .transition(DrawableTransitionOptions.withCrossFade(fade))
            .override(size, size)
            .into(imageView)
}
Ymager.pauseGlide = fun (context: Context) {
    GlideApp.with(context).pauseRequests()
}
Ymager.resumeGlide = fun (context: Context) {
    GlideApp.with(context).resumeRequests()
}

对于笔者的这个图片选择器来说,应用中配置的方法内容很固定,每个渲染方法各自用于选择器中的一处需要渲染图片的地方,包括列表图片、相册封面以及原图。

图片数据库工具类

图片选择器使用的数据源是系统相册数据库,这就需要执行数据库查询语句了,所以需要准备这么一个数据库工具类,包含了如下方法:

列表展示

列表展示需要以每行固定格子数的形式来展示比较小的图片列表,所以要使用的是RecyclerView搭配GridLayoutManager,列表数据传入Cursor来动态的从数据库中取出图片数据,而不是一口气取出成百上千张图片数据,再一口气传入Adapter中。

与通常RecyclerView使用的adapter的不同在于,列表使用的adapter传入的是一个数据库查询得到的Cursor

具体代码实现在这里

原图展示

原图展示需要全屏显示某张图片,并支持缩放和左右切换图片。所以使用了ViewPager搭配PagerAdapter来实现。同样是传入Cursor,动态根据position取出图片信息并渲染。

具体代码实现在这里

总结

剩余的工作包含了:

  1. Bucket列表的展示与切换,即根据BucketId来查询数据并使用第一张图作为封面。切换相册即只查询指定BucketId图片列表的Cursor并更新到ListAdapter
  2. 选好图片后,通过intent传递选中的图片数据。
  3. 发布到bintray的流程,这个网上有很多了,大体就是注册 -> 创建项目 -> 配置自己的项目 -> 执行项目这几步完事。

完整项目的Github地址在这里