不会写代码的猴子

不会写代码的猴子

Android类似微信聊天页面教程(Kotlin)五——选择发送图片-LMLPHP

 

前提条件

安装并配置好Android Studio

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 false
    id 'com.android.library' version '7.3.1' apply false
    id '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 33

    defaultConfig {
        applicationId "com.example.fechat"
        minSdk 26
        targetSdk 33
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_11
        targetCompatibility 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/ImmersionBar
    implementation '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 结果协定来启动照片选择器:

如果照片选择器在设备上不可用,该库会自动调用 ACTION_OPEN_DOCUMENT intent 操作。搭载 Android 4.4(API 级别 19)或更高版本的设备支持此 intent。您可以通过调用 isPhotoPickerAvailable() 来验证照片选择器在给定设备上是否可用。

图片选择

照片选择器提供了一个可浏览、可搜索的界面,其中按日期(从最近到最早)顺序向用户呈现其媒体库中的文件。您可以指定用户只能看到照片或只能看到视频,并且默认情况下,允许的媒体选择量上限设置为 1

单图选择器

ActivityResultContracts.PickVisualMedia()

多图选择器

ActivityResultContracts.PickMultipleVisualMedia("自定义选择上限")

平台会限制您可以让用户在照片选择器中选择的文件数量上限。如需访问此限制,请调用 getPickImagesMaxLimit()。 在不支持照片选择器的设备上,系统会忽略此上限。

适用的设备

照片选择器适用于符合以下条件的设备:

搭载 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()
    else
        ActivityResultContracts.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.contentResolver
                val 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()
}
05-06 07:05