千家信息网

Kotlin实现图片选择器的关键技术点讲解

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章主要讲解了"Kotlin实现图片选择器的关键技术点讲解",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Kotlin实现图片选择器的关键技术点讲解
千家信息网最后更新 2026年11月01日Kotlin实现图片选择器的关键技术点讲解

这篇文章主要讲解了"Kotlin实现图片选择器的关键技术点讲解",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"Kotlin实现图片选择器的关键技术点讲解"吧!

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

  • 一些异常情况的处理

  • Recycleview-CursorAdapter

  • 还有必要用LoaderManager吗

如何快速获取 相册分类

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

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

显然媒体数据库中 是不会自带这种分类表的,需要你自己手动处理,在一些低版本的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()            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()                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))

比如说这张图片:

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

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

Recycleview-CursorAdapter

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

abstract class RecyclerViewCursorAdapter internal constructor(c: Cursor?) :    RecyclerView.Adapter() {    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 即可。

感谢各位的阅读,以上就是"Kotlin实现图片选择器的关键技术点讲解"的内容了,经过本文的学习后,相信大家对Kotlin实现图片选择器的关键技术点讲解这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

文件 图片 文件夹 选择 处理 原始 名称 就是 相册 分类 关键 技术 媒体 情况 数据 必要 手机 数据库 版本 学习 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司排名 互联网是不是新科技 互联网最新的科技有限公司 礼当家互联网科技 互联网科技公司官网 汉立互联网科技有限公司联系电话 华中科技有哪些互联网大佬 湖南洣水互联网科技有限公司扣款 恒银科技与互联网金融 福建科技产业互联网发展 世界互联网科技公司市值排名 浙江互联网科技研学营 用友工业互联网科技发展 沐思想互联网科技有限公司 皮皮虾互联网科技有限公司 江苏互联网智能科技软件 风投北京互联网科技 三亚科技创新互联网基地 亚光科技低轨卫星互联网 科技美学互联网 艺术 北京路客互联网科技有限公司 互联网科技上市公司 上海电发互联网科技有限公司 互联网和黑科技的看法 民营互联网公司国防科技 江苏互联网科技小贷 广州梦谷互联网科技有限公司领导 互联网时代高科技布局 安徽九吞吴互联网科技有限公司 瑞安市蟠桃会互联网科技有限公司
0