这篇文章主要讲解了“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<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的话,直接跳过就行了,对于这种图片的 文件夹名称 我们是放个空字符串还是直接写上 手机存储 就看个人了。
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来做,其实没有必要,现在都2021年了,这么难用的被谷歌自己废弃掉的LoaderManager 为啥还要自己用
其实只要用下viewModel 里面放个cursor的livedata 即可。
感谢各位的阅读,以上就是“Kotlin实现图片选择器的关键技术点讲解”的内容了,经过本文的学习后,相信大家对Kotlin实现图片选择器的关键技术点讲解这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。