前提条件
安装并配置好Android Studio
Android Studio Electric Eel | 2022.1.1 Patch 2
Build #AI-221.6008.13.2211.9619390, built on February 17, 2023
Runtime version: 11.0.15+0-b2043.56-9505619 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o.
Windows 11 10.0
GC: G1 Young Generation, G1 Old Generation
Memory: 1280M
Cores: 6
Registry:
external.system.auto.import.disabled=true
ide.text.editor.with.preview.show.floating.toolbar=false
ide.balloon.shadow.size=0
Non-Bundled Plugins:
com.intuit.intellij.makefile (1.0.15)
com.github.setial (4.0.2)
com.alayouni.ansiHighlight (1.2.4)
GsonOrXmlFormat (2.0)
GLSL (1.19)
com.mistamek.drawablepreview.drawable-preview (1.1.5)
com.layernet.plugin.adbwifi (1.0.5)
com.likfe.ideaplugin.eventbus3 (2020.0.2)gradle-wrapper.properties
#Tue Apr 25 13:34:44 CST 2023
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
build.gradle(:Project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {id 'com.android.application' version '7.3.1' apply falseid 'com.android.library' version '7.3.1' apply falseid 'org.jetbrains.kotlin.android' version '1.7.20' apply false
}
setting.gradle
pluginManagement {repositories {maven { url 'https://maven.aliyun.com/repository/public' }google()mavenCentral()gradlePluginPortal()maven { url 'https://jitpack.io' }}
}
dependencyResolutionManagement {repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)repositories {maven { url 'https://maven.aliyun.com/repository/public' }google()mavenCentral()gradlePluginPortal()maven { url 'https://jitpack.io' }}
}
rootProject.name = "FeChat"
include ':app'
build.gralde(:app)
plugins {id 'com.android.application'id 'org.jetbrains.kotlin.android'id 'kotlin-android'id 'kotlin-kapt'
}android {namespace 'com.example.fechat'compileSdk 33defaultConfig {applicationId "com.example.fechat"minSdk 26targetSdk 33versionCode 1versionName "1.0"}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}compileOptions {sourceCompatibility JavaVersion.VERSION_11targetCompatibility JavaVersion.VERSION_11}kotlinOptions {jvmTarget = '1.8'}
}dependencies {implementation 'androidx.core:core-ktx:1.7.0'implementation 'androidx.appcompat:appcompat:1.6.1'implementation 'com.google.android.material:material:1.8.0'implementation 'androidx.constraintlayout:constraintlayout:2.1.4'implementation 'androidx.cardview:cardview:1.0.0'implementation("androidx.activity:activity-ktx:1.7.1")// 沉浸式状态栏 https://github.com/gyf-dev/ImmersionBarimplementation 'com.gyf.immersionbar:immersionbar:3.0.0'implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0' // fragment快速实现(可选)implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0' // kotlin扩展(可选)implementation 'com.google.code.gson:gson:2.8.9'implementation "androidx.room:room-runtime:2.4.2"implementation "androidx.room:room-ktx:2.4.2"kapt "androidx.room:room-compiler:2.4.2"implementation 'org.apache.commons:commons-csv:1.5'implementation 'com.permissionx.guolindev:permissionx:1.4.0'implementation 'com.blankj:utilcodex:1.30.0' // 无implementation 'com.github.bumptech.glide:glide:4.12.0'kapt 'com.github.bumptech.glide:compiler:4.12.0'implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.3'implementation 'com.github.li-xiaojun:XPopup:latest.release'
}
对Kotlin语言有基本了解
内容在前一篇博客中写了基础配置,如果本篇内容看不懂,可以先去上一篇。
使用 Jetpack activity 协定
为了简化照片选择器的集成,请添加 1.7.0 版或更高版本的 androidx.activity 库。
您可以使用以下 activity 结果协定来启动照片选择器:
- PickVisualMedia,用于选择单张图片或单个视频。
- PickMultipleVisualMedia,用于选择多张图片或多个视频。
如果照片选择器在设备上不可用,该库会自动调用 ACTION_OPEN_DOCUMENT intent 操作。搭载 Android 4.4(API 级别 19)或更高版本的设备支持此 intent。您可以通过调用 isPhotoPickerAvailable() 来验证照片选择器在给定设备上是否可用。
图片选择
照片选择器提供了一个可浏览、可搜索的界面,其中按日期(从最近到最早)顺序向用户呈现其媒体库中的文件。您可以指定用户只能看到照片或只能看到视频,并且默认情况下,允许的媒体选择量上限设置为 1
单图选择器
ActivityResultContracts.PickVisualMedia()
多图选择器
ActivityResultContracts.PickMultipleVisualMedia("自定义选择上限")
平台会限制您可以让用户在照片选择器中选择的文件数量上限。如需访问此限制,请调用 getPickImagesMaxLimit()。 在不支持照片选择器的设备上,系统会忽略此上限。
注意:如果照片选择器不可用,且支持库调用
ACTION_OPEN_DOCUMENT
intent 操作,则系统会忽略指定的可选媒体文件数量上限。
适用的设备
照片选择器适用于符合以下条件的设备:
- 搭载 Android 11(API 级别 30)或更高版本
- 通过 Google 系统更新接收对模块化系统组件的更改
搭载 Android 4.4(API 级别 19)到 Android 10(API 级别 29)的旧款设备,以及搭载 Android 11 或 12 且支持 Google Play 服务的 Android Go 设备,都可以安装向后移植的照片选择器版本。如需通过 Google Play 服务自动安装向后移植的照片选择器模块,请将以下条目添加到应用清单文件的 <application>
标记中:
<!-- Trigger Google Play services to install the backported photo picker module. -->
<service android:name="com.google.android.gms.metadata.ModuleDependencies"android:enabled="false"android:exported="false"><intent-filter><action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" /></intent-filter><meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>
保留媒体文件访问权限
默认情况下,系统会授予应用对媒体文件的访问权限,直到设备重启或应用停止运行。如果您的应用执行长时间运行的工作(例如在后台上传大型文件),您可能需要将此访问权限保留更长时间。为此,请调用 takePersistableUriPermission() 方法:
val flag = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flag)
实现方案
根据官方文档介绍,于是有了以下的初始化方式(此处在context的create生命周期中调用):
private var media: ActivityResultLauncher<PickVisualMediaRequest>? = null
fun initPicker(activity: AppCompatActivity,maxFiles: Int = 1,listener: PickFileResultListener
) {val contract = if (maxFiles == 1)ActivityResultContracts.PickVisualMedia()elseActivityResultContracts.PickMultipleVisualMedia(maxFiles)media =activity.registerForActivityResult(contract) { uri ->if (uri != null) {val uris = uri.toString().substring(1, uri.toString().length - 1).split(",")listener.onPick(uris)Log.e("PhotoPicker", "Selected URI: $uris")} else {Log.e("PhotoPicker", "No media selected")}}
}
选择回调监听器
interface PickFileResultListener {fun onPick(uris: List<String>)
}
调用如下
fun pickMedia(fileType: String) {media?.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.SingleMimeType(fileType)))
}
其中选择类型定义为:
const val PICK_IMAGE_VIDEO = "*/*"
const val PICK_IMAGE = "image/*"
const val PICK_VIDEO = "video/*"
const val PICK_GIF = "image/gif"
转化路径
这样得到的Uri如果想要使用,需要先转化为绝对路径
fun Uri2Path(context: Context, uri: Uri?): String? {if (uri == null) {return null}if (ContentResolver.SCHEME_FILE == uri.scheme) {return uri.path} else if (ContentResolver.SCHEME_CONTENT == uri.scheme) {val authority: String = uri.authority!!if (authority.startsWith("com.android.externalstorage")) {return "${Environment.getExternalStorageDirectory()}/${uri.path?.split(":")?.get(1)}"} else {try {var idStr = ""if (authority == "media") {idStr = uri.toString().substring(uri.toString().lastIndexOf('/') + 1)} else if (authority.startsWith("com.android.providers")) {idStr = DocumentsContract.getDocumentId(uri).split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]}val contentResolver = context.contentResolverval cursor: Cursor? = contentResolver.query(MediaStore.Files.getContentUri("external"),arrayOf(MediaStore.Files.FileColumns.DATA),"_id=?",arrayOf(idStr),null)if (cursor != null) {cursor.moveToFirst()val idx: Int = cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA)val path = cursor.getString(idx)cursor.close()return path}} catch (e: Exception) {e.printStackTrace()}}}return null
}
大图预览
预览框架采用XPopup
需要添加依赖
implementation 'com.github.li-xiaojun:XPopup:latest.release'
private fun showImageView(imageView: ImageView, currentPosition: Int) {XPopup.Builder(context).isDestroyOnDismiss(true).asImageViewer(imageView, currentPosition, uris,{ popupView, position ->/*Toast.makeText(context, "切换到第" + position + "个图片", Toast.LENGTH_SHORT).show()*/},SmartGlideImageLoader(R.mipmap.ic_launcher)).show()
}