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
猜你喜欢
- 使用PHP开发的同学都知道array_chunk函数,其作用是将数据进行切割分段,但是在 java中却找不到合适的给List和Map分段的函
- 前言假设项目打包后,项目结构为:此时如果需要再windows环境中进行项目的启动或关闭,需要频繁的手敲命令,很不方便。此时可以编写.bat脚
- 1.springboot 2.0 默认连接池就是Hikari了,所以引用parents后不用专门加依赖2.贴我自己的配置(时间单位都是毫秒)
- 内存泄露,是Android开发者最头疼的事。可能一处小小的内存泄露,都可能是毁千里之堤的蚁穴。 怎么才能检测内存泄露呢? AndroidSt
- The error simply says, “you've made changes in files in your works
- 目录推荐教程正文创建-服务端-生成代码创建客户端,生成客户端代码先下载soapUI工具推荐教程idea2021以下最新安装j ihuo 教程
- 一. 关于变量在之前的文章中,已经给大家详细地介绍过变量相关的内容,比如变量的概念、命名规范、变量的定义及底层原理等内容。但其实变量还有作用
- 声明式事务回顾事务事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!事务管理是企业级应用程序开发中必备技术,用来确保数据的完整
- 1. 前言无论是自我学习中,还是在工作中,固然会遇到与前端搭配实现分页的功能,发现有几种方式,特此记录一下。2. 先说结论分页功能直接交给前
- 本文实例为大家分享了android实现简易计算器展示的具体代码,供大家参考,具体内容如下效果图:一、如图,首先布局计算器主页显示activi
- 0.导入命名空间:using Microsoft.Office.Core;using Microsoft.Office.Interop.Ex
- 我相信现在绝大部分App几乎避免不了消息推送,其实原理还是使用了长连接,通过服务端将消息推给客户端。市面上也有不少三方库,例如极光、友盟、个
- gateway网关与前端请求的跨域问题最近因项目需要,引入了gateway网关。可是发现将前端请求的端口指向网关后,用postman发送请求
- 本文实例为大家分享了Java测试网络连通性的方法,供大家参考,具体内容如下第一种方式:利用java运行时: Java代码 /** * tes
- 本文实例讲述了Java连接redis及基本操作。分享给大家供大家参考,具体如下:点击此处:本站下载安装。解压安装启动redis:使用cd命令
- 继承和多态派生类具有基类所有非私有数据和行为以及新类自己定义的所有其他数据或行为,即子类具有两个有效类型:子类的类型和它继承的基类的类型。对
- 本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文《详解Android中Handler的使用方
- 一、项目简述功能: 一套完整的网上花店商场系统,系统支持前台会员的注册 登陆系统留言,花朵的品种选择,详情浏览,加入购物 车,购买花朵等;后
- 借用@Caching实现入参是基本类型的:@Caching(evict={@CacheEvict(value = Cache.CONSTAN
- C# 获取硬件参数的实现方法示例代码:private static string GetIdentifier(string wmiClass