Kotlin实现图片选择器的关键技术点总结
作者:vivo祁同伟 发布时间:2023-03-05 02:31:57
目录
如何快速获取 相册分类
一些异常情况的处理
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<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))
比如说这张图片:
对于直接存储在手机根目录下的图片来说,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


猜你喜欢
- 在系统的管理员有着实际的应用,对于一个数据库管理系统来说,数据库安全还是挺重要的,所以在存入到数据库的密码通常都是加密的。即使有着hack攻
- java类的方法,我特别喜欢《java编程思想》里面的描述,这本书说java类之间的相互通信是通过消息。比如顾客类的对象调用一个eat方法,
- 本文所述为一个由C#编写的音乐播放器的主Form代码,里面有一些小技巧还是不错的,现共享给大家参考一下。里面有播放器背景设置、线程定义、调用
- float是单精度类型,精度是8位有效数字,取值范围是10的-38次方到10的38次方,float占用4个字节的存储空间double是双精度
- 最近用了Stream流,感觉超好用,记录一下。1、快速创建List比如我有个实体类User,User有个属性Namepublic class
- 组合模式及其在JDK源码中的运用 前言组合和聚合什么是组合模式示例透明组合模式透明组合模式的缺陷安全组合模式 组合模式角色组合模式在JDK源
- 目录问题为每个request设置超时值Http Handler给Request加上超时处理抛出正确的异常使用Handler总结HttpCli
- 目录LinkedHashMap 实现继承 LinkedHashMap组合 LinkedHashMap链表 + HashMap 实现LRU,即
- 概述本文基于示例的方式解释控制反转,再看控制反转之前,我们先看下常规控制流程,以数据库访问为例示例并没有实际访问数据,而是基于service
- 一、java内存区域Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和
- ProgressDialog的基本用法ProgressDialog为进度对话框。android手机自带的对话框显得比较单一,我们可以通过Pr
- 本文实例为大家分享了OpenGL实现多段Bezier曲线拼接的具体代码,供大家参考,具体内容如下运行程序的交互方式有点类似corelDraw
- JSTL条件行为和遍历行为JSTL的条件行为标签有四个:if,choose,when,otherwise标签1、if标签是对某一个条件进行测
- AI 平台:http://ai.baidu.com/开发工具:vs 2017准备工作1、注册百度账号2、登录百度 AI 开发平台,http:
- Talk is cheap, show me the code!先来看代码:public class TestEval {public st
- 一个错误:多线程使用单一消费者下图显现了一种错误的使用KafkaConsumer的方法创建多个线程用来消费kafka数据多线程使用同一个Ka
- 前言回想写过的图书管理系统、租房系统、电影院卖票系统都是基于原生的JavaSE、OOP,没有用到任何框架,在层与层的关系中一个类要想获得与其
- 多个线程访问共享对象和数据的方式有两种情况:1、每个线程执行的代码相同,例如,卖票:多个窗口同时卖这100张票,这100张票需要多个线程共享
- 初始化sp 内部将数据放到 xml 文件中,加载时首先会将硬盘中文件读取到内存中,这样加快了访问速度这次从源码开始,看看里面具体做了什么//
- 【诞生背景】最近在做某配置中心的时候,配置中心采用properties格式进行配置的(如下图)。而我们工程的项目配置文件是yml格式的(如下