使用”MediaStore”访问图片、视频的缩略图
需要动态申请权限
需要配置文件中添加要 申请的权限 “AndroidManifest.xml”
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
注册动态权限
需要自定义 “checkReadExternalStoragePermission()”方法
需要 “Build.VERSION.SDK_INT”方法获得当前系统的版本号
需要 “Build.VERSION_CODES.M”表示安卓6
需要 “ContextCompat.checkSelfPermission()”方法, 判断是否获得了权限.
参数1:上下文.参数2:要判断的权限.
返回值:PackageManager.PERMISSION_GRANTED 表示授予权限,PackageManager.PERMISSION_DENIED = -1 表示权限未开启;
- 需要 “shouldShowRequestPermissionRationale()”方法.
判断当前权限是否被拒绝,并且显示窗口,显示”运行”和”拒绝”选项.
第一次不显示”不再提醒”按钮,第二次窗口添加”不再显示选择框”。
返回值:拒绝返回 true ,允许返回 false 或者 不再提醒选中后 返回 false
- 需要 “requestPermission()”方法.
用来申请系统权限. 每次被调用时都会回调”onRequestPermissionsResult()”方法。
参数1:要申请的权限.
参数2:要申请权限的 code 以用来在申请方法回调时确定是当前的申请操作.
在需要注册权限的活动上添加
private fun checkReadExternalStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(
this,
permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
} else {
if (shouldShowRequestPermissionRationale(permission.READ_EXTERNAL_STORAGE)) {
Toast.makeText(this, "App needs to view thumbnails", Toast.LENGTH_LONG).show()
}
requestPermissions(
arrayOf(permission.READ_EXTERNAL_STORAGE),
READ_EXTERNAL_STORAGE_PERMISSION_RESULT
)
}
} else {
}
}
private val READ_EXTERNAL_STORAGE_PERMISSION_RESULT: Int = 0
- 需要重写 “onRequestPermissionsResult()”方法
申请权限时会调用它(申请权限时的回调)
参数1:申请权限时的Code,表示申请权限的操作是否相同.
参数2:这是要申请的权限永远不为空.
参数3:表示权限授权的结果永远封装在grantResult数组中
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
READ_EXTERNAL_STORAGE_PERMISSION_RESULT -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "now have access to view thumbs", Toast.LENGTH_LONG).show()
}
}
}
}
需要RecyclerView的适配器
初始化控件不再复述了,
- 需要变量”mMediaStoreCursor”
表示当前的Cursor
class MediaStoreAdapter(private val mActivity: Activity) :
RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {
private var mMediaStoreCursor: Cursor? = null
}
- 需要自定义”getBitmapFromMediaStore()”方法
获得系统中图片和视频的缩略图.通过Cursor获得照片和Video的缩略图
参数1:移动到对应Cursor的行.
- 需要使用”Cursor.getColumnIndex()”方法
获得当前MediaStoreCursor的”参数1”的索引
参数1:要索引的位置
返回值: 返回在当前 Cursor 中的位置,没有返回 -1
- 需要使用”Cursor.getInt()’方法
获得”Cursor”中索引(列)的数值.
参数1:你想要获得的列
返回值:返回指定列的数值
- 需要 “MediaStore.Images.Thumbnails.getThumbnail”方法
获得缩略图.
参数1:表示内容提供器. 参数2:当前”Cursor”缩略图的ID.
参数3:返回时的清晰度.MICRO_KIND,MINI_KIND字面意思为微型和迷你缩略模式,前者分辨率更低.
返回值:返回一个Bitmap格式
在适配器中添加
class MediaStoreAdapter(private val mActivity: Activity) :
RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {
private fun getBitmapFromMediaStore(position: Int): Bitmap? {
val idIndex: Int = mMediaStoreCursor!!.getColumnIndex(MediaStore.Files.FileColumns._ID)
val mediaTypeIndex: Int =
mMediaStoreCursor!!.getColumnIndex(MediaStore.Files.FileColumns.MEDIA_TYPE)
mMediaStoreCursor!!.moveToPosition(position)
when (mMediaStoreCursor!!.getInt(mediaTypeIndex)) {
// 当前类型为 IMAGE 时
MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> {
return MediaStore.Images.Thumbnails.getThumbnail(
mActivity.contentResolver,
mMediaStoreCursor!!.getLong(idIndex),
MediaStore.Images.Thumbnails.MICRO_KIND, null
)
}
// 当前类型为 VIDEO
MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> {
return MediaStore.Video.Thumbnails.getThumbnail(
mActivity.contentResolver,
mMediaStoreCursor!!.getLong(idIndex),
MediaStore.Video.Thumbnails.MICRO_KIND, null
)
}
}
return null
}
}
- 需要自定义方法”swapCursor()”
获得上一个Cursor.
- 需要 “notifyDataSetChanged()” 方法
检测当前UI线程,刷新UI
class MediaStoreAdapter(private val mActivity: Activity) :
RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {
private fun swapCursor(cursor: Cursor): Cursor? {
if (mMediaStoreCursor == cursor) {
return null
}
val oldCursor: Cursor? = mMediaStoreCursor
this.mMediaStoreCursor = cursor
if (cursor != null) {
this.notifyDataSetChanged()
}
return oldCursor
}
}
- 需要 自定义方法”changeCursor()”
检测上一个Cursor,用来关闭上个Cursor.
class MediaStoreAdapter(private val mActivity: Activity) :
RecyclerView.Adapter<MediaStoreAdapter.ViewHolder>() {
fun changeCursor(cursor: Cursor?) {
val oldCursor: Cursor? = swapCursor(cursor!!)
if (oldCursor != null)
oldCursor.close()
}
}
- 需要重写 “onBindViewHolder()”方法
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val bitmap: Bitmap? = getBitmapFromMediaStore(position)
if (bitmap != null) {
holder.mediastoreImageView.setImageBitmap(bitmap)
}
}
- 需要重写”getItemCount()”方法
override fun getItemCount(): Int {
return if (mMediaStoreCursor == null) 0 else mMediaStoreCursor!!.count
}
- 需要重写”onCreateViewHolder()”方法
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.media_image_view, parent, false)
return ViewHolder(view)
}
需要”LoaderManager.LoaderCallbacks”
- 需要实现LoaderManager.LoaderCallbacks
.(在MediaMainActivity)
在需要的Fragment或Activity中实现它。
class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
}
- 需要重写”onCreateLoader”
在调用initLoader()返回时创建一个Loader.
根据传入的ID,初始化并返回一个新的加载器.
- 需要返回的列
MediaStore.Files.FileColums.ID 返回Id列
MediaStore.Files.FIleColums.DATE_ADDED 图片被添加的时间
MediaStore.Files.FileColums.MEDIA_TYPE 媒体类型
MediaStore.Files.FileColums.MEDIA_TYPE_IMAGE 图片列(索引)
MediaStore.Files.FileColums.MEDIA_TYPE_VIDEO 视频列(索引)
- 需要”CursorLoader()”匿名类
它查询ContentResolver然后返回一个Cursor.
参数1:上下文. 参数2:要操作的URI.当前表示所有图片的URI.
参数3:要返回的列. 参数4:一个过滤器,表明哪些行要被返回.当前返回类型为IMAGE 或 VIDEO
参数5:用作过滤器的参数.
参数6:设置排序.相当于SQL语句中Order by.这里使用 DATA_ADDED + DESC 表示按照时间进行排序.
override fun onCreateLoader(p0: Int, p1: Bundle?): androidx.loader.content.Loader<Cursor> {
val projection = arrayOf<String>(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.Files.FileColumns.MEDIA_TYPE
)
// SQL命令
val selection =
MediaStore.Files.FileColumns.MEDIA_TYPE +
" = " + MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE + " or " + MediaStore.Files.FileColumns.MEDIA_TYPE +
" = " + MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
return object : CursorLoader(
this,
MediaStore.Files.getContentUri("external"),
projection,
selection,
null,
MediaStore.Files.FileColumns.DATE_ADDED + " DESC "
) {}
}
- 需要重写 “onLoadFinished()”方法
更新UI操作.当一个加载器完成了它的装载过程后被调用.
- 需要调用changeCursor()方法
刷新当前参数1,关闭上一个Cursor
参数1:当前的Cursor
class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
override fun onLoadFinished(loader: androidx.loader.content.Loader<Cursor>, data: Cursor?) {
mMediaStoreAdapter?.changeCursor(data!!)
}
}
- 需要重写”onLoaderReset()”方法
何时释放内存,当一个加载器完成了它的装载工作之后被调用
class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
override fun onLoaderReset(loader: androidx.loader.content.Loader<Cursor>) {
mMediaStoreAdapter?.changeCursor(null)
}
}
需要在用于权限和申请权限之后添加
需要在”onRequestPermissionsResult()”中调用”getSupportLoaderManager.initLoader()”
启动加载器
参数1:一个唯一的ID来标志加载器.
参数2:可选参数,用于加载初始化时(本例为null)
参数3:LoaderManager.LoaderCallbacks的实现.被LoaderManger调用以报告加载事件,在例子中是传递给自己”this”
when (requestCode) {
READ_EXTERNAL_STORAGE_PERMISSION_RESULT -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//添加 supportLoaderManager.initLoader(MEDIASTORE_LODAR_ID, null, this)
}
}
}
- 需要在”checkReadExternalStoragePermission()”中调用
- 需要在拥有权限后调用,在”checkSelfPermission()”判断拥有权限后调用
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(
this,
permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
//添加 supportLoaderManager.initLoader(MEDIASTORE_LODAR_ID, null, this)
}
} else {
//添加 supportLoaderManager.initLoader(MEDIASTORE_LODAR_ID, null, this)
}
ps:显示了在哪里添加其他的代码不用修改.
- 需要添加唯一的ID标志加载器.
class MediaMainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<Cursor> {
private val MEDIASTORE_LODAR_ID: Int = 0
}
- 需要GridLayoutManger,在 “onCreate()” 中添加
设置RecyclerView的布局
参数1:上下文 . 参数2:多少列
val gridLayoutManager: GridLayoutManager = GridLayoutManager(this, 3)
- 需要RecyclerView的setLayoutManager
设置 GridLayoutManager
thumbnailRecyclerView.layoutManager = gridLayoutManager
- 需要设置适配器省略..
使用 “Glide”的方式加载图片
- 需要添加依赖
- 需要“MediaStore.Files.FileColumns.DATA”(需要在onCreateLoader的projection修改)
表述索引在磁盘中位置
需要创建自定义返回“getUriFromMediaStore()”(在MediaStoreAdapter中添加)
获得缩略图在ContentResolver中的uri
参数1:media的那个行
需要“Uri.parse()” 方法
将字符串解析成uri对象
private fun getUriFromMediaStore(position: Int): Uri {
val dataIndex: Int = mMediaStoreCursor!!.getColumnIndex(MediaStore.Files.FileColumns.DATA)
mMediaStoreCursor!!.moveToPosition(position)
val mediaUri: Uri =
Uri.parse("file://" + mMediaStoreCursor!!.getString(dataIndex).toString())
return mediaUri
}
需要“Glide” - (在onBindViewHolder()中添加)
with(content:Content) - 需要上下文
load(uri:String) - 图片的uri
centerCrop() - 将图片按比例缩放到足以填充ImageView的尺寸,但是图片可能会显示不完整。
override(width,height) 重新改变图片大小。
into() - 你需要显示图片的目标。
Glide.with(mActivity).load(getUriFromMediaStore(position)).centerCrop().override(96, 96)
.into(holder.mediastoreImageView)
什么是Loader
Loader简化了activity和Fragment中异步加载数据的步骤.
特点:适用于每个Activity和Fragment,提供异步加载的实现方式,监听数据源,在数据发生改变时自动返回新的结果。当由于配置改变后被重新创建后,它们自动重新链接上一个加载游标,所以不必重新查数据.
loader API 说明
Class/Interface | 描述 |
---|---|
LoaderManager | 一个与Activity和Fragment相关联的抽象类,它管理一个或者多个Loader实例,帮助一个应用管理哪些与Activity或Fragment的声明周期相关的长时间操作.常见方式是与CursorLoader一起使用,然后应用也可以自己写一个加载其他数据类型或者数据源的loader。每个Activity或者Fragment只有一个LoaderManager.但是一个LoaderManager可以拥有多个加载器 |
LoaderManager.LoaderCallbacks | 用于一个客户端与LoaderManager交互的会调接口.例如,你使用回调方法onCreateLoader()来创建一个新的加载器. |
Loader | 一个执行异步数据加载的抽象类,它是加载器的基础类.你可以使用经典的CursorLoader,但是你也可以实现你的子类.一旦加载器被激活,它们将监听它的数据源并且在数据改变时发送新的结果. |
AsyncTaskLoader | 提供一个AsyncTask来执行异步加载工作的抽象类 |
CursorLoader | AsyncTaskLoader的子类,它查询ContentResolver然后返回一个Cursor.这个类为查询Cursor以标准的方式实现了加载器协议,它的游标查询是通过AsyncTaskLoader在后台线程中执行,从而不会阻断线程 |