Kotlin实现图片选择器的关键技术点总结

作者:vivo祁同伟 时间:2023-03-05 02:31:57 

目录
  • 如何快速获取 相册分类

  • 一些异常情况的处理

  • Recycleview-CursorAdapter

  • 还有必要用LoaderManager吗

  • 总结

如何快速获取 相册分类

所谓的相册分类,其实就是将媒体数据库中的所有媒体文件 进行处理,按照文件夹名称来区分开,这样用户选择图片的时候可以按照 文件夹名称快速选择

例如我们可以看下微信的图片选择:

Kotlin实现图片选择器的关键技术点总结

显然媒体数据库中 是不会自带这种分类表的,需要你自己手动处理,在一些低版本的android手机上可以在sql语句中 加入 GROUP BY 来处理,但是高版本 已经无法通过此方法来处理。

必须手动处理你原始数据的cursor,比如说 我们查询媒体数据库时:

通常的projection如下:


val PROJECTION = arrayOf(
   MediaStore.Files.FileColumns._ID,
   COLUMN_BUCKET_ID,
   COLUMN_BUCKET_DISPLAY_NAME,
   MediaStore.MediaColumns.MIME_TYPE
)

可以看出来 我们只有4列,分别是文件id ,文件所属的文件夹id,所属的文件夹名称,文件类型

那怎么快速方便的对这个原始的4列cursor 进行转换呢?

我们可以创建一个虚拟的cursor MatrixCursor 来对我们之前的原始cursor 进行扩展


val MATRIX_COLUMNS = arrayOf(
   MediaStore.Files.FileColumns._ID, COLUMN_BUCKET_ID, COLUMN_BUCKET_DISPLAY_NAME, MediaStore.MediaColumns.MIME_TYPE, COLUMN_URI, COLUMN_COUNT
)

其实就是新增了两列,一列是文件的uri,还有一列是文件夹下面有多少张图片,

这样我们只需要将这个cursor 传到Cursoradapter中 就可以迅速完成 相册分类的列表了

那怎么进行转换? 直接上下代码吧:


{
       return withContext(Dispatchers.Default) {
           // key是对应的文件夹id value 是文件夹下面有几个图片
           val bucketMap = hashMapOf<Long, Long>()
           while (cursor.moveToNext()) {
               val bucketId = cursor.getBucketId()
               if (bucketMap.containsKey(bucketId)) {
                   val count = bucketMap[bucketId]
                   bucketMap[bucketId] = count!! + 1
               } else {
                   bucketMap[bucketId] = 1L
               }
           }

// 创建虚拟的cursor 从cursor的第二条记录开始
           val matrixCursor = MatrixCursor(MATRIX_COLUMNS)
           // 总文件夹的 cursor 一般取第一张图 作为封面图
           val allAlbumCursor = MatrixCursor(MATRIX_COLUMNS)

var allAlbumUri: Uri? = null
           var fileId: Long? = null
           if (cursor.moveToFirst()) {
               //取第一张图的 uri
               allAlbumUri = cursor.getUri()
               //取第一张图的fileId
               fileId = cursor.getFileId()
               Log.v("wuyue", "allAlbumUri:$allAlbumUri")
               val bucketIdSet = hashSetOf<Long>()
               do {
                   // 如果之前已经有这个 图片文件夹 则放弃 直接看下一个
                   if (bucketIdSet.contains(cursor.getBucketId())) {
                       continue
                   }

var bucketDisplayName = ""
                   if (cursor.getType(cursor.getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME)) == FIELD_TYPE_STRING) {
                       bucketDisplayName = cursor.getBucketDisplayName()
                   }
                   bucketIdSet.add(cursor.getBucketId())
                   matrixCursor.addRow(arrayOf(cursor.getFileId().toString(), cursor.getBucketId().toString(), bucketDisplayName, cursor.getFileMimeType(), cursor.getUri().toString(), bucketMap[cursor.getBucketId()].toString()))

} while (cursor.moveToNext())
           }

allAlbumCursor.addRow(arrayOf(fileId ?: "", "-1", "All", allAlbumUri?.toString() ?: "", "", cursor.count))

MergeCursor(arrayOf(allAlbumCursor, matrixCursor))
       }
   }

简述下思路‘;

1.通过原始的cursor 来算出一张map,map的key就是文件夹的id value就是这个文件夹下有多少张图片

2.开始遍历原始的cursor,然后将新的row add到我们虚拟的matrixCursor中,因为我们只需要展示封面图,

所以对于每个相册,我们仅仅需要他的最新的一张图片就可以,其余的直接跳过不处理

一些异常情况的处理

主要是对于一些图片来说,取bucketDisplayName会取不到


/**
* 获取图片文件夹的名称
*/
fun Cursor.getBucketDisplayName(): String = getString(getColumnIndex(COLUMN_BUCKET_DISPLAY_NAME))

比如说这张图片:

Kotlin实现图片选择器的关键技术点总结

对于直接存储在手机根目录下的图片来说,bucketDisplayName 这个值压根取不到 而且你如果直接用上面的扩展函数取还会崩溃

所以一般情况下我们要判断这个cursor的Type ,如果返回的不是string 是个null的话,直接跳过就行了,对于这种图片的 文件夹名称 我们是放个空字符串还是直接写上 手机存储 就看个人了。

Recycleview-CursorAdapter

CursorAdapter 目前没有针对Recycleview做,只有listview的版本,所以我们如果要用Recycleview 需要自定义一个


abstract class RecyclerViewCursorAdapter<VH : RecyclerView.ViewHolder?> internal constructor(c: Cursor?) :
   RecyclerView.Adapter<VH?>() {
   private var mCursor: Cursor? = null
   private var mRowIDColumn = 0
   protected abstract fun onBindViewHolder(holder: VH, cursor: Cursor?)
   override fun onBindViewHolder(holder: VH, position: Int) {
       if (!isDataValid(mCursor)) {
           throw IllegalStateException("Cannot bind view holder when cursor is in invalid state.")
       }
       if (!mCursor!!.moveToPosition(position)) {
           throw IllegalStateException(
               "Could not move cursor to position " + position + " when trying to bind view holder"
           )
       }
       onBindViewHolder(holder, mCursor)
   }

override fun getItemViewType(position: Int): Int {
       if (!mCursor!!.moveToPosition(position)) {
           throw IllegalStateException(
               ("Could not move cursor to position " + position + " when trying to get item view type.")
           )
       }
       return getItemViewType(position, mCursor)
   }

protected abstract fun getItemViewType(position: Int, cursor: Cursor?): Int
   override fun getItemCount(): Int {
       return if (isDataValid(mCursor)) {
           mCursor!!.count
       } else {
           0
       }
   }

override fun getItemId(position: Int): Long {
       if (!isDataValid(mCursor)) {
           throw IllegalStateException("Cannot lookup item id when cursor is in invalid state.")
       }
       if (!mCursor!!.moveToPosition(position)) {
           throw IllegalStateException(
               ("Could not move cursor to position " + position + " when trying to get an item id")
           )
       }
       return mCursor!!.getLong(mRowIDColumn)
   }

fun swapCursor(newCursor: Cursor?) {
       if (newCursor === mCursor) {
           return
       }
       if (newCursor != null) {
           mCursor = newCursor
           mRowIDColumn = mCursor!!.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
           // notify the observers about the new cursor
           notifyDataSetChanged()
       } else {
           notifyItemRangeRemoved(0, itemCount)
           mCursor = null
           mRowIDColumn = -1
       }
   }

fun getCursor(): Cursor? {
       return mCursor
   }

private fun isDataValid(cursor: Cursor?): Boolean {
       return cursor != null && !cursor.isClosed
   }

init {
       setHasStableIds(true)
       swapCursor(c)
   }
}

还有必要用LoaderManager吗

大部分开源的图片选择都是基于LoaderManager来做,其实没有必要,现在都2021年了,这么难用的被谷歌自己废弃掉的LoaderManager 为啥还要自己用

其实只要用下viewModel 里面放个cursor的livedata 即可。

来源:https://juejin.cn/post/7006502455303208990

标签:kotlin,图片,选择器
0
投稿

猜你喜欢

  • 详解在spring boot中消息推送系统设计与实现

    2023-05-28 04:17:00
  • android实现将位置信息写入JPEG图片文件

    2023-04-16 21:26:00
  • springboot @ConfigurationProperties和@PropertySource的区别

    2023-06-12 07:18:14
  • Java Socket实现传输压缩对象的方法示例

    2022-12-18 06:52:30
  • Mybatis Plus中的流式查询案例

    2023-08-18 16:35:13
  • Android开发中requestfocus()无效的原因及解决办法

    2023-05-25 07:15:00
  • C#实现多线程的同步方法实例分析

    2021-11-01 11:56:42
  • C#使用DropDownList绑定添加新数据的方法汇总

    2023-05-08 03:20:56
  • C#中using语句的用法

    2023-07-02 05:33:24
  • Android获取经纬度计算距离介绍

    2022-04-30 20:00:04
  • Java实现监听文件变化的三种方案详解

    2022-07-18 11:20:34
  • android自定义进度条渐变色View的实例代码

    2023-07-01 20:58:57
  • C# 16进制与字符串、字节数组之间的转换

    2021-07-13 08:08:10
  • Java中Jackson快速入门

    2023-01-18 10:24:44
  • pagehelper踩坑记之分页乱套问题解决

    2021-11-14 14:19:34
  • Java传入用户名和密码并自动提交表单实现登录到其他系统的实例代码

    2023-09-20 00:40:46
  • Javassist用法详解

    2023-06-26 17:11:54
  • Java 详解循环屏障CyclicBarrier如何实现多线程分段等待执行完成

    2022-02-16 04:50:49
  • Android 列表形式的切换的示例代码

    2022-12-17 07:38:24
  • Android教你如何发现APP卡顿的实现

    2022-06-10 22:33:45
  • asp之家 软件编程 m.aspxhome.com