Android 媒体文件选择库(图片与视频),支持 target api = 33
。
TODO:
- 内置权限支持;
- 支持视频预览;
- 支持单选图片预览;
- 支持
API 34
Grant partial access to photos and videos 交互。
基于 boxing 封装,由于 boxing 官方不再维护,这里对相关问题进行了修复。包括:
- RotatePhotoView 依赖混乱导致的崩溃。
- Android 10 SQLiteException 崩溃问题,解决方案可以参考 replacement-for-group-by-in-contentresolver-query-in-android-q-android-10-a 和 https://stackoverflow.com/questions/56823336/query-mediastore-on-android-q。
- Android 11 invalid token limit,解决方案可以参考 limiting-number-of-rows-in-a-contentresolver-query-function
- 没有适配 Android 10 的 ScopedStorage。
另外,使用时需要配置好 FileProvider:
- xml/file_path
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path name="app_internal" path="/" />
<cache-path name="app_internal_cache" path="/" />
<external-cache-path name="app_external_cache" path="/" />
<external-files-path name="app_external" path="/" />
<external-path name="app_external" path="/" />
</paths>
- manifest
<provider android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.file.provider" android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
除了 boxing,也可以考虑其他方案:
- Matisse (JetpackCompose)
- EasyPhotos
- PictureSelector
- ImageSelector
SystemMediaSelector 用于调用系统相机或 SAF 获取图片或文件。
需要考虑的问题:
- Android 7.0 之后,需要适配 FileProvider。
- 获取的图片方向问题,需要通过 exif 修正。
- 系统返回的不是 file 路径,而是其他类型的 uri,需要通过相关方法转换。
相关参考:
- 官方文档:拍照
- 官方文档:从共享存储空间访问文档和其他文件
- 官方文档:Android 存储用例和最佳做法
- Android. How to capture image from camera and store it in server?
- 你需要知道的 Android 拍照适配方案
- Android 调用系统相机和相册-填坑篇
- Android 大图裁剪
- get-filename-and-path-from-uri-from-media-store
- how-to-get-the-full-file-path-from-uri
使用系统裁剪时发现不同设备厂商以及不同系统版本之间有行为不一致的问题,于是内置了第三方图片裁剪库 uCrop。
其他可选裁剪库有:
class MainActivity : AppCompatActivity() {
private val systemMediaSelector by lazy {
newSystemMediaSelector(this, object : ResultListener {
override fun onTakeSuccess(result: List<Uri>) {
result.forEach {
Timber.e(it.toString())
}
showResult(result)
}
})
}
private val mediaSelector by lazy {
newMediaSelector(this, object : ResultListener {
override fun onTakeSuccess(result: List<Uri>) {
result.forEach {
Timber.e(it.toString())
}
showResult(result)
}
})
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
systemMediaSelector.onRestoreInstanceState(savedInstanceState)
mediaSelector.onRestoreInstanceState(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
systemMediaSelector.onSaveInstanceState(outState)
mediaSelector.onSaveInstanceState(outState)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
systemMediaSelector.onActivityResult(requestCode, resultCode, data)
mediaSelector.onActivityResult(requestCode, resultCode, data)
}
///////////////////////////////////////////////////////////////////////////
// MediaStore
///////////////////////////////////////////////////////////////////////////
fun selectOnePhotoByMediaStore(view: View) {
mediaSelector.takePicture().start()
}
fun selectOnePhotoWithCameraByMediaStore(view: View) {
mediaSelector.takePicture().needMediaLocation().enableCamera().start()
}
fun selectOnePhotoWithCameraAndCropByMediaStore(view: View) {
mediaSelector.takePicture().needMediaLocation().enableCamera().crop().needGif().start()
}
fun selectMultiPhotoByMediaStore(view: View) {
mediaSelector.takePicture().needMediaLocation().count(9).needGif().start()
}
fun selectOneVideoByMediaStore(view: View) {
mediaSelector.takeVideo().needMediaLocation().start()
}
///////////////////////////////////////////////////////////////////////////
// Photos by Intent or SAF
///////////////////////////////////////////////////////////////////////////
fun captureOnePhoto(view: View) {
systemMediaSelector.takePhotoByCamera().start()
}
fun captureOnePhotoAndCrop(view: View) {
systemMediaSelector.takePhotoByCamera().crop().start()
}
fun selectOnePhoto(view: View) {
systemMediaSelector.takePhotoFromSystem().start()
}
fun selectOnePhotoAndCrop(view: View) {
systemMediaSelector.takePhotoFromSystem().crop().start()
}
fun selectPhotos(view: View) {
systemMediaSelector.takePhotoFromSystem().multiple(true).start()
}
...
}
implementation 'io.github.ztiany:android-media-selector:1.1.1'