一个用 Jetpack Compose 实现的 Android 图片选择框架
特点 & 优势:
- 完全用 Kotlin 实现,拒绝 Java
- UI 层完全用 Jetpack Compose 实现,拒绝原生 View 体系
- 支持精细自定义主题,默认提供了 日间 和 夜间 两种主题
- 支持精准筛选图片类型,只显示想要的图片类型
- 支持在图片列表页开启拍照入口,同时支持 FileProvider 和 MediaStore 两种拍照策略
- 支持详细获取图片信息,一共包含 uri、displayName、mimeType、width、height、orientation、size、path、bucketId、bucketDisplayName 等十个属性值
- 适配到 Android 12
Apk 下载体验:releases
日间主题 | 夜间主题 | 自定义主题 |
---|---|---|
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
dependencies {
implementation "com.github.leavesCZY:Matisse:latestVersion"
}
通过 MatisseContract 来启动 Matisse,在回调函数里获取用户选择的图片或拍摄的照片
class MainActivity : AppCompatActivity() {
private val btnImagePicker by lazy {
findViewById<View>(R.id.btnImagePicker)
}
private val matisseContractLauncher = registerForActivityResult(MatisseContract()) {
if (it.isNotEmpty()) {
val mediaResource = it[0]
val imageUri = mediaResource.uri
val imagePath = mediaResource.path
val imageWidth = mediaResource.width
val imageHeight = mediaResource.height
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnImagePicker.setOnClickListener {
val matisse = Matisse()
matisseContractLauncher.launch(matisse)
}
}
}
通过 Matisse 对象来实现自定义,一共支持以下六个自定义属性
/**
* @param theme 主题。默认是日间主题
* @param supportedMimeTypes 需要显示的图片类型。默认是包含 Gif 在内的所有图片
* @param maxSelectable 可以选择的最大图片数量。默认是 1
* @param spanCount 显示图片列表时的列表。默认是 4
* @param tips 权限被拒绝、图片数量超限时的 Toast 提示
* @param captureStrategy 拍照策略。默认不开启拍照功能
*/
data class Matisse(
val theme: MatisseTheme = LightMatisseTheme,
val supportedMimeTypes: List<MimeType> = ofImage(hasGif = true),
val maxSelectable: Int = 1,
val spanCount: Int = 4,
val tips: MatisseTips = defaultMatisseTips,
val captureStrategy: CaptureStrategy = NothingCaptureStrategy
)
theme 用于设置 Matisse 的主题,包括:背景色、系统状态栏颜色、文本颜色、字体大小、按钮文本、Icon
Matisse 提供了两种默认主题:
- LightMatisseTheme。日间主题
- DarkMatisseTheme。夜间主题
开发者可以在这两个主题的基础上,通过 copy
方法来快速构建想要的效果
LightMatisseTheme.copy(
surfaceColor = Color.White,
topAppBarTheme = TopAppBarTheme(
defaultBucketName = "全部",
backgroundColor = blueColor,
contentColor = Color.White,
fontSize = 18.sp,
),
)
或者是自己来完整实例化 MatisseTheme 对象
val darkColor = Color(color = 0xFF1F1F20)
val blueColor = Color(color = 0xFF03A9F4)
MatisseTheme(
surfaceColor = Color.White,
onPreviewSurfaceColor = darkColor,
imageBackgroundColor = Color.LightGray.copy(alpha = 0.4f),
alphaIfDisable = 0.5f,
captureIconTheme = CaptureIconTheme(
backgroundColor = Color.LightGray.copy(alpha = 0.4f),
icon = Icons.Filled.PhotoCamera,
tint = Color.White,
),
topAppBarTheme = TopAppBarTheme(
defaultBucketName = "全部",
backgroundColor = blueColor,
contentColor = Color.White,
fontSize = 18.sp,
),
bottomNavigationTheme = BottomNavigationTheme(
backgroundColor = Color.White,
),
dropdownMenuTheme = DropdownMenuTheme(
backgroundColor = Color.White,
textStyle = TextStyle(
fontSize = 14.sp,
color = Color.Black,
),
),
checkBoxTheme = CheckBoxTheme(
countable = true,
frameColor = Color.Transparent,
circleColor = Color.White,
circleFillColor = blueColor,
fontSize = 14.sp,
textColor = Color.White,
),
previewButtonTheme = PreviewButtonTheme(
textBuilder = { selectedSize: Int, maxSelectable: Int ->
"点击预览($selectedSize/$maxSelectable)"
},
textStyle = TextStyle(
fontSize = 14.sp,
color = blueColor,
),
),
sureButtonTheme = SureButtonTheme(
textBuilder = { selectedSize: Int, _: Int ->
"使用($selectedSize)"
},
textStyle = TextStyle(
fontSize = 14.sp,
color = Color.White,
),
backgroundColor = blueColor,
),
systemBarsTheme = SystemBarsTheme(
statusBarColor = blueColor,
statusBarDarkIcons = false,
navigationBarColor = Color.White,
navigationBarDarkIcons = true
),
)
supportedMimeTypes 用于设置想要展示的图片格式,默认包含以下所有类型
enum class MimeType(val type: String) {
JPEG("image/jpeg"),
PNG("image/png"),
HEIC("image/heic"),
HEIF("image/heif"),
BMP("image/x-ms-bmp"),
WEBP("image/webp"),
GIF("image/gif");
}
如果希望只展示 Gif 类型的图片,可以这么做:
val matisse = Matisse(supportedMimeTypes = listOf(MimeType.GIF))
maxSelectable 用于设置允许选择的最大图片数量,默认是 1
spanCount 用于设置展示图片时的列数,默认是 4
tips 用于设置通过 Toast 向用户弹出的文案内容,包括:权限被拒绝时的提示、图片选择数量超限时的提示。默认值如下所示
data class MatisseTips(
val onReadExternalStorageDenied: String,
val onWriteExternalStorageDenied: String,
val onCameraDenied: String,
val onSelectLimit: (selectedSize: Int, maxSelectable: Int) -> String,
)
private val defaultMatisseTips = MatisseTips(onReadExternalStorageDenied = "请授予存储访问权限后重试",
onWriteExternalStorageDenied = "请授予存储写入权限后重试",
onCameraDenied = "请授予拍照权限后重试",
onSelectLimit = { _: Int, maxSelectable: Int ->
"最多只能选择${maxSelectable}张图片"
}
)
captureStrategy 用于控制是否开启拍照入口,以及开启拍照功能后要采用的具体策略
Matisse 提供了三种默认实现,默认值是 NothingCaptureStrategy,代表不开启拍照功能。其他两种策略所需要的权限和图片存储的位置都不一样,对用户的感知也不一样
拍照策略 | 所需权限 | 配置项 | 对用户是否可见 |
---|---|---|---|
NothingCaptureStrategy | |||
FileProviderCaptureStrategy | 无 | 外部需要配置 FileProvider | 否,图片存储在应用私有目录内,对用户不可见 |
MediaStoreCaptureStrategy | Android 10 之前需要 WRITE_EXTERNAL_STORAGE 权限,Android 10 开始不需要权限 | 无 | 是,图片存储在系统相册内,对用户可见 |
如果使用的是 FileProviderCaptureStrategy,外部还需要配置 FileProvider,authorities 视自身情况而定,通过 authorities 来实例化 FileProviderCaptureStrategy
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="github.leavesczy.matisse.samples.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
file_paths.xml
中需要配置 external-files-path
路径的 Pictures 文件夹,name 可以随意命名
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="Capture"
path="Pictures" />
</paths>
开发者根据自己的实际情况来决定选择哪一种策略:
- 如果应用本身就需要申请 WRITE_EXTERNAL_STORAGE 权限的话,选 MediaStoreCaptureStrategy,拍照后的图片保存在系统相册中也比较符合用户的认知
- 如果应用本身就不需要申请 WRITE_EXTERNAL_STORAGE 权限的话,选 FileProviderCaptureStrategy,为了相册问题而多申请一个敏感权限得不偿失
Matisse 要求一个必需权限和一个可选权限
用于读取系统相册内的所有图片
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
可选权限是否需要声明取决于开发者采用的拍照策略:
- MediaStoreCaptureStrategy。由于在 Android 10 之前向系统相册写入图片需要存储写入权限,所以需要申请 WRITE_EXTERNAL_STORAGE 权限。而 Android 10 开始之后的版本则不需要,因此可以将该权限的 maxSdkVersion 设为 28
- FileProviderCaptureStrategy。无需申请此权限
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />