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的话,直接跳过就行了,对于这种图片的 文件夹名称 我们是放个空字符串还是直接写上 手机存储 就看个人了。

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 即可。

总结

到此这篇关于Kotlin实现图片选择器的文章就介绍到这了,更多相关Kotlin实现图片选择器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-09-13

浅谈谈Android 图片选择器

ImageSelector 简介 Android自定义相册,实现了拍照.图片选择(单选/多选).ImageLoader无绑定 任由开发者选择 https://github.com/YancyYe/ImageSelector Demo Download Apk 更新内容 UI重改 所有功能可配置 解决OOM情况 图片手动选择 支持汉语和英语 截图展示 使用说明 步骤一: 通过Gradle抓取 dependencies { compile 'com.yancy.imageselector:image

Android图片选择器 丰富的配置选项

最近也是刚好项目用到,于是就动手写了一个Android 图片选择器的库.支持图库多选/单选/图片裁剪/拍照/自定义图片加载库,极大程度的简化使用. 截图 优点 1.通过实现ImageLoader接口,可以实现自定义图片加载器的功能.例如可以用Glide.Picasso.ImageLoader,暂不支持Fresco,因为SimpleDraweeView本身并不属于ImageView.当然,也可用相同的思路来实现. 2.可配置的ImgSelConfig.方便进行扩展. 3.简化使用 项目地址:htt

Android 高仿QQ图片选择器

当做一款APP,需要选择本地图片时,首先考虑的无疑是系统相册,但是Android手机五花八门,再者手机像素的提升,大图无法返回等异常因数,导致适配机型比较困难,微信.QQ都相继的在自己的APP里集成了图片选择功能,放弃了系统提供的图片选择器,这里仿造QQ做了一个本地图片选择器,PS:之前有人说"仿"写成"防"了,今儿特意注意了下,求不错. 先上一张效果图,无图无真相啊~~~ 实现的效果大概是这样的: 1.单选:跳转到本地图片选择文件夹,选择文件夹后,进入到该文件夹下

路由器的关键技术

近年来,互联网的发展异常迅猛,应用日益商业化,网上用户数的发展难以预测.此外,越来越多的用户需要高速接入.有关资料表明,在我国,上网速度慢是众多网民抱怨的首要问题.因此,提高网络带宽.网络服务质量.路由器上的网络管理系统变得日益重要.在保证质量的前提下,最大限度地利用带宽,及早发现并诊断设备故障,迅速方便地根据需要改变配置等网络管理功能,成为直接影响网络用户和网络运营商利益的重要因素.总地来说,路由器的结构正朝着速度更快.服务质量更好和更易于综合化管理三个方向发展. 路由器的两大功能 数据通路功

Android仿微信图片选择器ImageSelector使用详解

今天给大家介绍一个仿微信的图片选择器:ImageSelector.ImageSelector支持图片的单选.限数量的多选和不限数量的多选.支持图片预览和图片文件夹的切换.在上一篇文章 <Android 实现一个仿微信的图片选择器> 中我介绍了ImageSelector的实现思路和分析了它的核心代码,有兴趣的同学可以看一下.完整的代码放在了GitHub,欢迎大家下载和使用.本篇文章为大家介绍ImageSelector的具体使用方式. 先上效果图: 1.引入依赖 在Project的build.gr

Android ImageSelector微信图片选择器

前言 现在绝大多数的App都上传图片的功能,比如设置用户头像.聊天发送图片.发表动态.论坛帖子等.上传图片需要先从选择手机中选择要上传的图片,所以图片选择器在App中是很常见的组件,一般的手机都会自带一个图片选择器.不过很多App并不喜欢用手机自带的选择器,而是自己实现一个图片选择器. 比如微信的图片选择器就做的很好.没办法,谁让微信这么强大,我不超抄袭你,但是,我可以模仿你. 效果图 是不是和真的一样,哈哈,不过,作者的唯一缺陷就是没有提供拍照,唉,有一点遗憾,但是,这个就够用了! 思路 1.

Python爬虫图片懒加载技术 selenium和PhantomJS解析

一.什么是图片懒加载? - 案例分析:抓取站长素材http://sc.chinaz.com/中的图片数据 #!/usr/bin/env python # -*- coding:utf-8 -*- import requests from lxml import etree if __name__ == "__main__": url = 'http://sc.chinaz.com/tupian/gudianmeinvtupian.html' headers = { 'User-Agen

Android自定义图片选择器简单版

前言:图片选择器基本上是每个App必备的东西,用公认好的第三方也可以,但是自己写的改起来方便,用起来顺手,而且这东西想想可能没动手之前想想比较难,实际操作起来就很简单了,这次先主要写流程,具体优化的细节以后在写. 难点:动手之前最困惑的问题就是怎么获取到手机里所有的图片,获取到之后,显示出来,处理逻辑这些就简单了. 步骤1:动态申请权限 private boolean permissionOpen() { if (ContextCompat.checkSelfPermission(this, M

Android实现图片选择器功能

本文实例为大家分享了Android实现图片选择器功能的具体代码,供大家参考,具体内容如下 图片选择功能用的是GitHub上的依赖库,网址 先来看下我运行的效果图如下所示: 该依赖库是Android平台上拍照/录像,图片/视频选择,编辑和压缩的一站式解决方案. 添加依赖,在app->build.gradle里面添加依赖 //图片/视频选择.预览.编辑与拍照 implementation 'com.github.guoxiaoxing:phoenix:1.0.15' 初始化: public clas

Android实现微信的图片选择器

现在大部分的App都上传图片的功能,比如设置用户头像.聊天发送图片.发表动态.论坛帖子等.上传图片需要先从选择手机中选择要上传的图片,所以图片选择器在App中是很常见的组件,一般的手机都会自带一个图片选择器.不过很多App并不喜欢用手机自带的选择器,而是自己实现一个图片选择器.比如微信的图片选择器就做的很好.所以我也仿照微信的样式和交互效果,自己做了一个图片选择器:ImageSelector.ImageSelector支持图片的单选.限数量的多选和不限数量的多选.支持图片预览和图片文件夹的切换.