Skip to content

About

1000万以内最好的全能(文件、视频、图片)选择库,微信抖音样式但仿华为相册滑动选择丝滑效果。支持文件选择(任何后缀)支持定义规则选择图片或者视频,比如图片9张或者视频1张。

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Repository files navigation

图库选择器

最新版本

  • 使用最新版的 filepicker 库,必须使用最新版的xwidget库。都是 5.8.1 以上确保。

  • 用了很多,都没有丝滑的选择效果的开源库,滑动选择丝滑。闲来没事,随手写一个吧。

  • 图库选择,文件选择

  • 仿华为相册滑动选择手势逻辑,

  • 微信选择库样式风格

  • 支持多选,限制数量,不限制数量。

  • 选择条件可限定:图片 / 视频各自限量(如图片 9 张、视频 1 个),可以要求必须选够, 也能让图片和视频只能选其中一类(互斥)。见「按类型限量」一节。

  • 使用场景,多用于 视频剪辑选择库,相册管理选择库,朋友圈选图或者视频。

好不好用,直接安装。

依赖库,都是常用库,强烈建议 项目使用。

  • 下面的这个三方库,本maven仓库中的aar都过滤掉了。建议自己从下面引用,防止库冲突。

最新版本

  1. **filepicker
    **
  2. **xwidget
    **
  3. **BRV
    **
  4. **BasePopup
    **
  5. androidx.media3 这里建议使用1.11.0
    稳定版
  6. glide
dependencies {
    // 基础组件库 (必须)
    implementation("com.gitee.pichs:filepicker:7.5.0")

    // 基础组件库 (必须)
    implementation("com.gitee.pichs:xwidget:5.8.1")

    // glide 图片加载 (必须)
    implementation("com.github.bumptech.glide:glide:4.16.0")

    //基础库(必须)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation(libs.androidx.activity.ktx)
    implementation(libs.androidx.fragment.ktx)
    implementation(libs.material)
    implementation(libs.androidx.recyclerview)
    implementation(libs.androidx.annotation)
    implementation(libs.kotlinx.coroutines.android)
    implementation(libs.kotlinx.coroutines.core)

    // brv (必须)
    implementation("com.github.liangjingkanji:BRV:1.6.1")
    // 弹窗 (必须)
    implementation("io.github.razerdp:BasePopup:3.2.1")
    // 视频播放库 (必须)采用exoplayer
    implementation("androidx.media3:media3-exoplayer:1.7.1")
    implementation("androidx.media3:media3-ui:1.7.1")
}


使用libs.version.toml用户引入方式
dependencies {
    api(libs.filepicker)
    api(libs.xwidget)
    api(libs.glide)
    api(libs.brv)
    api(libs.basepopup)
    api(libs.media3.exoplayer)
    api(libs.media3.ui)
}

# libs.version.toml中写法
[versions]
xwidget="5.8.1"
filepicker="7.5.0"
brv="1.6.1"
basepopup="3.2.1"
glide="4.16.0"
activityKtx="1.9.0"
fragmentKtx="1.6.2"
recyclerview="1.4.0"
kotlinxCoroutinesAndroid="1.7.3"
kotlinxCoroutinesCore="1.7.3"
media3Exoplayer="1.7.1"

[libraries]
androidx-media3-exoplayer={module="androidx.media3:media3-exoplayer",version.ref="media3Exoplayer"}
androidx-media3-ui={module="androidx.media3:media3-ui",version.ref="media3Exoplayer"}
androidx-activity-ktx={group="androidx.activity",name="activity-ktx",version.ref="activityKtx"}
androidx-fragment-ktx={group="androidx.fragment",name="fragment-ktx",version.ref="fragmentKtx"}
androidx-recyclerview={group="androidx.recyclerview",name="recyclerview",version.ref="recyclerview"}
kotlinx-coroutines-android={group="org.jetbrains.kotlinx",name="kotlinx-coroutines-android",version.ref="kotlinxCoroutinesAndroid"}
kotlinx-coroutines-core={group="org.jetbrains.kotlinx",name="kotlinx-coroutines-core",version.ref="kotlinxCoroutinesCore"}
xwidget={group="com.gitee.pichs",name="xwidget",version.ref="xwidget"}
filepicker={group="com.gitee.pichs",name="filepicker",version.ref="filepicker"}
basepopup={group="io.github.razerdp",name="BasePopup",version.ref="basepopup"}
brv={group="com.github.liangjingkanji",name="BRV",version.ref="brv"}
glide={group="com.github.bumptech.glide",name="glide",version.ref="glide"}

动图效果

filepicker 效果演示

FilePicker 文件选择器 - 使用文档

FilePicker 是一个支持图片、视频、文件选择的轻量级文件选择器,支持自定义配置、选中列表管理、回调返回选中数据。


快速入口

  • 支持 Activity 和 Fragment 调用

  • 支持多选、最大数量控制、文件大小限制

  • 支持返回原图选项

  • 支持 UI 文案与界面定制

  • 支持滑动选择

  • 支持单选立即返回

  • 支持预选列表


基本用法

1. 在 Activity 中启动

FilePicker.with(this) // this: FragmentActivity
    .setSelectType(FilePickerSelectType.ofImage()) // 设置选择类型
    .setMaxSelectNumber(9) // 设置最大选择数量
    .setSlideChooseEnable(true) // 开启滑动选择
    .setOnSelectCallback(object : OnSelectCallback {
        override fun onSelectedCallback(isUseOriginal: Boolean, list: MutableList<MediaEntity>) {
            // 选择完成回调
            println("是否使用原图: $isUseOriginal")
            list.forEach {
                println("文件路径: ${it.path}")
            }
        }
        override fun onCancel() {
            // 取消选择
        }
    })
    .setUiConfig(
        FilePickerUIConfig(
            isHideSelectTab = false,
            allAlbumName = "全部",
            confirmBtnText = "发送",
            isShowOriginal = false,
            isPreviewPageIndexMode = true,
            isShowSelectedListDeleteIcon = true,
            folderNickNameMap = hashMapOf(
                "DCIM" to "相册"
            )
        )
    ).
    .start()

2. 在 Fragment 中启动

FilePicker.with(this) // this: Fragment
    .setSelectType(FilePickerSelectType.ofImage())
    .setMaxSelectNumber(5)
    .setMaxFileSize(50 * 1024 * 1024) // 50MB
    .setMinFileSize(1 * 1024) // 1KB
    .setOnSelectCallback { isUseOriginal, list ->
        // 选择完成回调
    }
    .setUiConfig(
        FilePickerUIConfig(
            isHideSelectTab = true,
            allAlbumName = "全部",
            confirmBtnText = "下一步",
            isShowOriginal = false,
            isPreviewPageIndexMode = true,
            isShowSelectedListDeleteIcon = true,
            folderNickNameMap = hashMapOf(
                "DICM" to "相机",
                "Download" to "下载"
            )
        )
    ).
    .start()

3. 选择文件(音频 / 文档 / 压缩包)

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofAudio()) // 文件类型:ofAudio/ofDocument/ofPdf/ofApk/ofZipAll...
    .setMaxSelectNumber(1)
    .setSingleClickEnable(true) // 单击直接返回,文件选择场景常用
    .setOnSelectCallback { isUseOriginal, list ->
        // 返回 MediaEntity(含 path/name/mimeType/size)
    }
    .start()

4. 自定义后缀过滤

任意后缀组合,不需要为每种后缀单独加类型:

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofExtensions("xz", "tar", "bak"))
    .start()

有专属类型入口的后缀(图片、音频、文档、核心压缩包等)优先用 ofXxx(),
两者在 UI 模式与预览上的差异见下方 类型入口怎么选。

详见下方 自定义后缀过滤。

5. 拍照 / 录像入口(默认关闭)

在网格第一格放一个"相机"格子,点击调起系统相机拍照或录像,拍完自动插到列表第一个。

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofImage())  // 或 ofVideo()
    .setShowCameraEntry(true)
    .start()

默认 false:不开启时这条链路完全不参与 —— 不占列表格子、不注册 viewType、不请求权限,
对宿主等同于不存在。

生效条件:setShowCameraEntry(true),且当前类型下相机能产出点东西(图片或视频)。 GIF / 音频 / 文档不显示;有 tab 的类型里入口跟着当前 tab 走。

格子的画法按"能产出几种"决定:

当前生效类型 格子
只有图片(ofImage(),或 ofAll() 的「图片」页) 整格 = 拍照
只有视频(ofVideo(),或 ofAll() 的「视频」页) 整格 = 录像
图片 + 视频(ofAll() / ofAllWithGif() 的「全部」页) 从中间劈开:左半边录像、右半边拍照
只有 GIF(「GIF」页) 不显示入口

行为说明:

项 说明
位置 网格左上角第一格,随列表滚动,尺寸与相邻格子一致
点击 调起系统相机(拍照 ACTION_IMAGE_CAPTURE / 录像 ACTION_VIDEO_CAPTURE)
拍完 自动插到列表第一个位置,并默认勾选上("这张就是要用的")
已到上限 仍然插入,但不勾选(不会超过 maxSelectNumber),并 toast 提示
单选立即返回 maxSelectNumber == 1 且开了 setSingleClickEnable(true) 时,拍完直接回调返回,\
           拍到的这张会**替换**掉已有选择(宿主通过 `setSelectedList` 预置的那张也一样)              |

| 大小超限 | 产物不符合 minFileSize / maxFileSize 时不插入、不勾选,toast 提示(文案见
cameraEntrySizeLimitToastContent) | | 产物落地 | 写进系统相册(DCIM/Camera),和系统相机拍的照片放在一起 | | 取消拍摄 | 会清理掉本次产生的空记录/空文件,不会在相册里留下打不开的空条目 | | 图标与文案 | 图标默认用库内资源,可换 cameraEntryIconResId(拍照)/ cameraEntryVideoIconResId(录像);
文字标签用 cameraEntryPhotoText / cameraEntryVideoText |

权限与 manifest:

  • 库不声明也不占用 CAMERA 权限 —— 拍摄由相机 App 自己完成,调用方不需要该权限。
    但如果宿主 App 自己声明了 android.permission.CAMERA 却还没授权,ACTION_IMAGE_CAPTURE /
    ACTION_VIDEO_CAPTURE 会直接抛 SecurityException(系统规定)。这种情况下库会先申请权限,
    授权后自动继续拍摄。真机实测原话(华为 BRE-AL80 / Android 12,宿主声明了但未授权):
    Permission Denial: starting Intent { act=android.media.action.VIDEO_CAPTURE ... } with revoked permission android.permission.CAMERA
  • 库不往宿主 manifest 里加任何东西(没有 <provider>、没有 <queries>)。
  • 产物写入系统相册:Android 10+ 不需要任何存储权限;Android 7.0–9 需要 WRITE_EXTERNAL_STORAGE
    (与本库查询相册本身的前提一致)。

为什么不用 resolveActivity() 判断有没有相机 App(官方文档推荐这么写):
宿主 targetSdk ≥ 30 时,没声明 <queries> 的应用会被包可见性过滤,resolveActivity() / queryIntentActivities() 对这几个相机 action 一律返回 null / 0,但 startActivity 依然能成功。 同一台华为设备上实测:四个相机 action 的 resolveActivity 全是 null,而实际能起 com.huawei.camera/com.android.camera.VideoCamera。用 resolveActivity() != null 当守卫 会让相机入口永远点不动。本库因此改成 startActivity 包 try/catch。


6. 按类型限量(图片 N 张 / 视频 M 个 / 互斥)

setMaxSelectNumber 只能管一个总数。7.2.0 起新增按类型分别限量:图片、视频各自设上限, 可以要求"必须选够",也可以要求"图片和视频只能选其中一类"。

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofAll())
    .setSelectRules(
        SelectRule.ofImage(0, 9),   // 图片最多 9 张(min = 0 表示不强制)
        SelectRule.ofVideo(0, 1),   // 视频最多 1 个
    )
    .start()

SelectRule.ofImage(min, max) / ofVideo(min, max) / ofGif(min, max):

  • max:该类型的硬上限,到顶后勾不上,提示"图片最多选择2个";
  • min > 0:必须选。提交时数量必须落在 min..max 内;不满足时确定按钮不高亮, 点击提示"图片必须选3个",且不会返回;
  • 没配规则的类型不受限制。
需求 写法
图片 ≤ 9、视频 ≤ 1,可混选 setSelectRules(SelectRule.ofImage(0, 9), SelectRule.ofVideo(0, 1))
图片必须正好 9 张 setSelectRules(SelectRule.ofImage(9, 9))
视频必须 1 个、图片最多 9 张 setSelectRules(SelectRule.ofVideo(1, 1), SelectRule.ofImage(0, 9))
图片和视频只能选其中一类 上面的规则 + .setExclusive(true)
合计最多 5 个(不分类) .setMaxSelectNumber(5)
GIF 不占图片额度 .setGifCountsAsImage(false)

互斥(setExclusive(true))的锁定规则:

  • 只有一类配了 min > 0 → 直接锁定那一类,另一类点了会被拦下(等于"只能选图片 / 只能选视频");
  • 两类都配了 min > 0 → 先点哪类就锁定哪类,另一类的"必须选"本次作废;
  • 已选类型全部取消后自动解锁,可以改选另一类。

启用条件(重要):setExclusive(...) 和 setGifCountsAsImage(...) 只有在配置了 setSelectRules(...) 之后才生效。一个规则都不配时,它们会被忽略(打 warn 日志), 数量限制仍由 setMaxSelectNumber(n) 决定 —— 这样"只开互斥、没配规则"不会把你设的最大数量弄丢。

和 setMaxSelectNumber 的关系:用了规则,setMaxSelectNumber 就不生效了。 数量限制始终只有一个来源:

  • 不用规则 → setMaxSelectNumber(n) 是唯一口径(老行为);
  • 用了规则 → 合计上限由各类型 max 自动叠加得出(互斥时取单类型最大的 max), 调用方不需要再写第二个数字,也就不存在"合计上限和规则冲突"这种配置。

所以规则模式下没有任何"谁优先"的抉择 —— 从设计上就没有第二个数字。

三个提示文案都可以在 FilePickerUIConfig 里改:selectTypeMaxToastContent("图片最多选择2个")、 selectExclusiveConflictToastContent("当前只能选择视频")、selectRuleNotSatisfiedToastContent ("图片必须选3个")。

预选列表(setSelectedList)超出规则时会按规则截断并打 warn 日志,不会静默吞掉。


API 文档

FilePicker

方法名 描述
with(activity) 使用 FragmentActivity 初始化
with(fragment) 使用 Fragment 初始化
convertToPathList(list) 将 MediaEntity 列表转换为路径列表
convertToEntityList(list) 将路径列表转换为 MediaEntity 列表

7.0.0 起类型入口方法统一收敛到 FilePickerSelectType(语义更贴合)。
FilePicker.ofImage() 等老方法已标注 @Deprecated,仍可正常使用(内部转发),
IDE 会提示迁移,建议尽快替换为 FilePickerSelectType.ofXxx()。完整对照见
已废弃 API。

已废弃 API(@Deprecated)

以下方法自 7.0.0 起标注 @Deprecated(源码级兼容,调用不报错、行为不变,内部直接转发到新入口)。
新代码请一律使用右列写法:

废弃(不要再用) 最新写法
FilePicker.ofAll() FilePickerSelectType.ofAll()
FilePicker.ofAllWithGif() FilePickerSelectType.ofAllWithGif()
FilePicker.ofImage() FilePickerSelectType.ofImage()
FilePicker.ofVideo() FilePickerSelectType.ofVideo()
FilePicker.ofGif() FilePickerSelectType.ofGif()
FilePicker.ofAudio() FilePickerSelectType.ofAudio()
FilePicker.ofDocument() FilePickerSelectType.ofDocument()
FilePicker.ofPdf() FilePickerSelectType.ofPdf()
FilePicker.ofDoc() FilePickerSelectType.ofDoc()
FilePicker.ofPpt() FilePickerSelectType.ofPpt()
FilePicker.ofExcel() FilePickerSelectType.ofExcel()
FilePicker.ofTxt() FilePickerSelectType.ofTxt()
FilePicker.ofApk() FilePickerSelectType.ofApk()
FilePicker.ofZipAll() FilePickerSelectType.ofZipAll()
FilePicker.ofZip() FilePickerSelectType.ofZip()
FilePicker.ofRar() FilePickerSelectType.ofRar()
FilePicker.of7Z() FilePickerSelectType.of7Z()

6.x 已删除的 ofTar() / ofGz() / ofBz2() / ofIso() / ofBr() / ofLz4() / ofZstd() / ofXz()
不在废弃之列(已移除),替代写法见下方 7.0.0 旧 API 迁移对照表。

FilePickerSelectType 类型入口

方法名 描述
ofAll() 选择图片和视频
ofAllWithGif() 选择图片、视频和GIF
ofImage() 只选择图片
ofVideo() 只选择视频
ofGif() 只选择GIF
ofAudio() 只选择音频
ofDocument() 选择所有文档类型
ofPdf() 只选择PDF
ofDoc() 只选择DOC和DOCX
ofPpt() 只选择PPT和PPTX
ofExcel() 只选择XLS和XLSX
ofTxt() 只选择TXT
ofMarkdown() 只选择 Markdown(.md / .markdown)
ofXmind() 只选择 XMind 思维导图(.xmind)
ofFreemind() 只选择 FreeMind 思维导图(.mm)
ofApk() 只选择APK
ofZipAll() 选择所有压缩包类型
ofZip() 只选择ZIP
ofRar() 只选择RAR
of7Z() 只选择7Z
ofExtensions(...) 按任意后缀组合自定义过滤(见下节)

类型入口怎么选

有专属类型入口的后缀,优先用类型入口;没有的才用 ofExtensions()。
两者能力上都能查到文件,但语义与体验有差别:

场景 推荐入口 原因
选图片/视频 ofImage() / ofVideo() / ofAll() 相册网格模式、大图预览、原图选项齐全
选音频/文档/APK ofAudio() / ofDocument() / ofApk() 专属查询条件与图标
选 Markdown/思维导图 ofMarkdown() / ofXmind() / ofFreemind() 按后缀查询,专属图标(见下)
选压缩包 ofZipAll() / ofZip() / ofRar() / of7Z() mime + 后缀双轨查询,图标专属
选其他任意后缀 ofExtensions("xz", "bak", ...) 无对应类型时的通用方案

ofExtensions() 与类型入口的差异(按后缀捞文件的语义带来的):

  • UI 呈现为文件列表模式(按时间排序 + 文件夹分组),不是相册网格模式

  • 无大图预览手势(isCanPreview() 不放行 ext: 类型)

  • 查询按文件名后缀匹配,会包含隐藏目录(. 开头路径)下的文件——若需排除,
    设置 setMaxFileSize()(走 filePathNotContains("/.") 过滤)或自行过滤结果

Markdown / 思维导图(按后缀识别)

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofMarkdown())   // .md / .markdown
    // .setSelectType(FilePickerSelectType.ofXmind())    // .xmind
    // .setSelectType(FilePickerSelectType.ofFreemind()) // .mm
    .start()

这三个类型只按文件名后缀查询与识别,不走 mime 轨,因为 MediaStore 给不出可靠的 mime:

后缀 MediaStore 常见 mime 后果(若按 mime 判)
.xmind application/zip(xmind 本质是 zip 容器) 查询会捞出所有 zip;图标显示成 zip
.md text/plain 或 application/octet-stream 图标显示成 txt
.mm application/octet-stream 无专属图标

因此它们不属于压缩包:不参与 ZIP_ALL、isArchive() 的任何推导,与.xmind 是 zip 容器这件事无关。
图标映射里这三个判断排在 isTxt() / isZip() 之前,后缀命中即显示专属图标。

归属上它们是文档类型,但 ofDocument() 的查询条件不含这三种(ofDocument() 行为保持不变),
需要单独用 ofMarkdown() / ofXmind() / ofFreemind()。

后缀表统一登记在 FilePickerSelectType.DOC_EXTS,新增别名(如 mdown / mkd)只需加一行,查询与图标自动跟上。

自定义后缀过滤

tar/gz/bz2/iso/br/lz4/zstd/xz 等其他压缩格式不再单独提供 API,统一用
FilePickerSelectType.ofExtensions() 按任意后缀组合:

// 按任意后缀过滤,直接作为 selectType 传入
FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofExtensions("xz", "tar", "bak"))
    .start()
  • 后缀自动去掉前导点、转小写、去重,并过滤 SQL 通配符(% _)

  • 任意后缀都能查(真机已验证:编造后缀 .qqqxx7、大写 .ABCxyz、双重点 .tar.gz 均命中),
    前提是文件位于 MediaStore 索引的公共存储;应用私有目录、.nomedia 目录内的文件查不到

  • 图标识别不受影响:内置的 tar/gz/bz2/iso/br/lz4/zstd/xz 类型常量与图标映射仍然生效,
    老代码直接传 FilePickerSelectType.TAR 等字符串常量依旧可用

  • 查询、识别由内部「压缩包登记处」统一驱动:ZIP_ALL 由登记处派生,
    单个类型(如 ZIP)只取登记处中自己那一行,互不影响

7.0.0 旧 API 迁移对照表:

6.x(已删除) 7.0.0 等价写法
ofTar() FilePickerSelectType.ofExtensions("tar")
ofGz() FilePickerSelectType.ofExtensions("gz", "tgz")
ofBz2() FilePickerSelectType.ofExtensions("bz2")
ofIso() FilePickerSelectType.ofExtensions("iso")
ofBr() FilePickerSelectType.ofExtensions("br")
ofLz4() FilePickerSelectType.ofExtensions("lz4")
ofZstd() FilePickerSelectType.ofExtensions("zstd")
ofXz() FilePickerSelectType.ofExtensions("xz")
ofTar() + ofIso() 组合 FilePickerSelectType.ofExtensions("tar", "iso")

也可以不做任何改动:把 selectType 直接传 FilePickerSelectType.TAR 等字符串常量,查询、过滤、图标展示行为与旧版一致。

Builder

方法名 描述
setSelectType(type) 设置选择的文件类型
setMaxSelectNumber(num) 设置最大选择数量(0/Int.MAX_VALUE = 不限)。只在不用 setSelectRules 时生效;规则模式下合计上限由规则自动叠加,本方法被忽略
setSelectRules(vararg rules) 按类型分别限量,见「按类型限量」
setExclusive(exclusive) 互斥:图片与视频只能选其中一类。需先配 setSelectRules,否则不生效
setGifCountsAsImage(counts) GIF 是否占用图片额度,默认 true。需先配 setSelectRules,否则不生效
setMaxFileSize(size) 设置最大文件大小(字节)
setMinFileSize(size) 设置最小文件大小(字节)
setSlideChooseEnable(enable) 是否启用滑动选择,默认 true
setSingleClickEnable(enable) 是否启用单选立即返回,默认 false。仅在 maxSelectNumber 为1时生效
setShowCameraEntry(enable) 是否显示网格第一格的拍照/录像入口,默认 false。仅对单一的图片或视频类型生效,详见下文
setSelectedList(list) 设置已选中的 MediaEntity 列表
setSelectedPathList(list) 设置已选中的文件路径列表
setUiConfig(config) 设置自定义UI配置
setOnSelectCallback(callback) 设置选择结果回调
start() 启动选择器

UI 自定义配置 (FilePickerUIConfig)

通过 FilePicker.with(this).setUiConfig(uiConfig) 进行设置。

val uiConfig = FilePickerUIConfig().apply {
    isHideSelectTab = false
    confirmBtnText = "完成"
    allAlbumName = "所有文件"
    // ... 更多配置
}
属性 类型 描述 默认值
isHideSelectTab Boolean 是否隐藏顶部分类Tab false
confirmBtnText String 确定按钮的文本 "确定"
isPreviewPageIndexMode Boolean 预览页面是否显示页码 true
allAlbumName String “全部”文件夹的显示名称 "全部"
previewText String 预览页面的标题 "预览"
isShowBottomPreviewText Boolean 是否显示底部预览按钮 true
previewSelectText String 预览页面底部“选择”按钮文本 "选择"
originalText String “原图”选项的文本 "原图"
isShowOriginal Boolean 是否显示“原图”选项 true
isOriginalChecked Boolean “原图”选项是否默认选中 false
isShowHomePageSelectedBottomListWidget Boolean 是否显示主页底部已选列表 true
isShowSelectedListDeleteIcon Boolean 是否显示已选列表项的删除按钮 false
selectedListDeleteIconResId Int 已选列表项删除按钮的图标资源ID 0
selectedListDeleteIconBackgroundColor Int 已选列表项删除按钮的背景色 Color.TRANSPARENT
atLeastSelectOneToastContent String 未选择任何文件时的提示 "至少选择一个"
selectMaxNumberOverToastContent String 超出最大选择数量时的提示 "已达到最大选择数量"
selectTypeMaxToastContent String 命中分类型上限时的提示(%1$s=类型名,%2$d=上限) "%1$s最多选择%2$d个"
selectExclusiveConflictToastContent String 互斥下点了另一类时的提示(%1$s=当前只能选的类型) "当前只能选择%1$s"
selectRuleNotSatisfiedToastContent String 必选未满足时的提示(%1$s=类型名,%2$d=要求数量) "%1$s必须选%2$d个"
cameraEntryIconResId Int 拍照入口的图标(0 = 库内默认) 0
cameraEntryVideoIconResId Int 录像入口的图标(0 = 库内默认) 0
cameraEntryPhotoText String 「拍照」半边下面的文字 "拍照"
cameraEntryVideoText String 「录像」半边下面的文字 "录像"
cameraEntryFailedToastContent String 相机入口启动失败时的提示 "无法启动相机"
cameraEntrySizeLimitToastContent String 拍摄产物不符合大小限制时的提示 "拍摄的文件不符合大小限制,未加入列表"
folderNickNameMap HashMap<String, String> 文件夹名称映射,用于自定义显示名称 emptyHashMap()


## !!!温馨提示: 如果UI效果与你的需求差距较大,强烈建议下载源码,自己修改一下。fork代码,自己改。!!!

结束语

FilePicker 旨在提供简洁、灵活、可定制的文件选择功能,广泛适用于相册、文件管理、视频选择等场景。

如需深入定制或遇到问题,欢迎补充需求,我可以帮您生成对应的开发指导。

特别鸣谢

本项目在开发过程中参考和使用了以下优秀的开源项目,特此致谢:

  • xwidget - 提供了超级方便的基础组件。

  • BRV —— 便捷的 RecyclerView 适配器库

  • BasePopup —— 强大的弹窗库

  • Glide —— 高效的图片加载库

  • androidx.media3 —— 官方视频播放组件

感谢你们的无私奉献,让开发变得更加高效和有趣!

图片编辑(7.5.0+)

在预览页直接编辑图片:画笔(涂鸦)/ 文字 / 裁剪 / 马赛克。 编辑结果不修改原图;点「完成」后把最终合并图保存到系统相册 Pictures/filepicker/, 返回给调用方的就是编辑后的真实图片地址。

开启编辑(开关)

编辑入口默认关闭,需要显式打开:

FilePicker.with(this)
    .setEditInPreviewEnable(true)   // ← 开关:预览页是否出现「编辑」入口
    .start()

开启后:

  • 单图预览页右上角出现「编辑」
  • 多选预览页(首页选完 N 张 → 预览)同样出现「编辑」,可逐张编辑

完整示例

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofImage())
    .setMaxSelectNumber(9)
    // 1) 预览页可编辑(默认 false,必须显式打开)
    .setEditInPreviewEnable(true)
    // 2) 裁剪参数(可选)
    .setCropAspectRatio(0f)      // 0 = 自由裁剪;>0 = 固定比例(1f = 头像 1:1)
    .setCropOutputMaxSize(2048)  // 裁剪导出最长边,默认 2048
    .setCropQuality(90)          // 导出质量 60~100,默认 90
    .setCropResetText("还原")     // 裁剪页「还原」文案(多语言由接入方传入)
    // 3) 编辑临时缓存目录(可选,默认 Android/data/<包名>/cache/filepicker/)
    .setEditCacheDir(cacheDir.absolutePath)
    .setOnSelectCallback { isUseOriginal, list ->
        list.forEach { entity ->
            // 编辑过的图:entity.editedPath 是编辑产物(会话内预览/角标用)
            // 点确定返回时:path / uri 已经是编辑后的真实地址(Pictures/filepicker/xxx.jpg)
            Log.d("picker", "原始: ${entity.path}  编辑产物: ${entity.editedPath}")
        }
    }
    .start()

编辑结果说明

字段 / API 含义
MediaEntity.path / uri 原图地址;从选择器返回结果时,编辑过的条目已换成编辑后的真实地址
MediaEntity.editedPath 会话内编辑产物地址(预览、已编辑角标使用),返回结果时清空
FilePicker.clearEditCache(context) 手动清理编辑临时缓存(返回调用方时库已自动异步清理)
FilePicker.editCacheSize(context) 当前编辑缓存大小(字节)
FilePicker.editCacheDir(context) 编辑缓存目录
  • 编辑产物发布位置:Pictures/filepicker/filepicker_edit_yyyyMMdd_HHmmss.jpg (系统相册可见,下次选择可直接再次选中)
  • 头像裁剪:setCropAspectRatio(1f),单选模式下点图片直接进入 1:1 裁剪页

注意事项

  • 编辑是会话级覆盖:不改原图文件、不覆盖相册原图;「已编辑」角标显示在缩略图左下角(16dp)。
  • 再次进入选择器并传入 setSelectedList(list) 时,编辑过的条目以编辑后的图展示。
  • Android 10+ 发布到相册走 MediaStore(无需存储权限);9 及以下写公共目录并触发媒体扫描。
  • 文字/画笔比例相对原图短边:裁剪只裁图片,已有文字/画笔仍可编辑,越界部分自然裁掉。

头像裁剪(独立裁剪页)

头像场景:单选、点一张图直接进入 1:1 裁剪页(独立 Activity),裁剪确认后返回成品图。 不经过编辑弹窗,也不修改相册原图。

怎么调

FilePicker.with(this)
    .setSelectType(FilePickerSelectType.ofImage())
    .setMaxSelectNumber(1)          // 头像 = 单选(容量 1 时自动启用"单击即返回"逻辑)
    .setCropAspectRatio(1f)         // ← 关键:大于 0 时,单选模式点图直接进固定比例裁剪页
    .setCropOutputMaxSize(1024)     // 裁剪导出最长边(头像一般 512~1024 就够)
    .setCropQuality(95)             // 导出质量 60~100
    .setOnSelectCallback { isUseOriginal, list ->
        val avatar = list.firstOrNull()
        // 返回时 path / uri 已经是裁剪后的成品地址(Pictures/filepicker/xxx.jpg)
        Log.d("avatar", "裁剪结果: ${avatar?.path}")
    }
    .start()

也可以保留勾选流程:

FilePicker.with(this)
    .setMaxSelectNumber(1)
    .setSingleClickEnable(true)     // 允许"点一下就直接选中/裁剪"
    .setCropAspectRatio(1f)
    .start()

生命周期与结果

环节 说明
入口 单选模式(容量=1)下点图片 → FilePickerCropActivity(1:1 固定裁剪框)
裁剪页 UI 取消 / 旋转 / 镜像 / 还原 / 确认裁剪;固定比例不可拖成其它比例
确认裁剪 生成成品图 → 发布到系统相册 Pictures/filepicker/ → 登记媒体库
返回结果 MediaEntity.path / uri = 裁剪后的成品地址;editedPath 仅会话内使用
取消 关闭裁剪页,不产生任何文件

注意

  • setCropAspectRatio(1f) 只影响单选单击(头像)流程;多选场景的裁剪仍走 「预览 → 编辑 → 裁剪」(自由裁剪,受 setEditInPreviewEnable(true) 控制)。
  • 想用别的头像比例(如 4:3、16:9)直接传对应小数:setCropAspectRatio(4f/3f)。
  • 需要"先展示裁剪页再确认"的 UI,也可以按 FilePickerCropActivity.start(...) 直接启动。

升级日志

7.5.0

新增图片编辑器:画笔 / 文字 / 裁剪 / 马赛克,以及编辑结果的全链路管理。

新增

  • 编辑入口:单图预览页「编辑」、多选预览页「编辑」(setEditInPreviewEnable(true) 开启)。 编辑结果是会话级覆盖:原图文件不动,MediaEntity.editedPath 指向编辑产物。
  • 画笔(涂鸦):像素级涂抹,8 色可选、橡皮擦;面板增加锥形粗细滑条(自定义 View,左细右粗)。
  • 文字:默认「字体色」模式(无背景)+ 「圆角背景」模式两种样式;8 色; 文字可拖动、点选后双指缩放;字号按图片短边比例存储,随图片缩放/裁剪保持一致。
  • 裁剪:专注裁剪模式(隐藏主工具条,只留 旋转 / 镜像 / 还原 + ✕ / ✓); 旋转、镜像以裁剪框中心为轴;裁剪框限制在安全区域内(左右各 22dp、顶部状态栏下方、 底部裁剪菜单上方);裁剪只裁图片,已有文字/画笔仍可编辑、越界部分自然裁掉; 停手 2 秒自动放大裁剪框。
  • 马赛克:像素马赛克 / 雾化两种效果,共用可调的笔刷粗细(比画笔更粗)。
  • 已编辑角标:16dp,显示在图库网格、底部已选缩略图条、预览底条及 demo 结果格左下角(图片区域内)。
  • 编辑产物发布:点「完成」合并出图后,复制到系统相册 Pictures/filepicker/ 并在媒体库登记 (Android 10+ 走 MediaStore,无需额外权限;9 及以下写公共目录 + MediaScannerConnection 扫描), 返回给调用方的就是编辑后的真实地址,下次选择可直接再次选中。
  • 缓存清理:返回调用方时异步清理编辑临时缓存(clearCacheExcept 保留结果文件), Pictures/filepicker/ 成品永久保留。
  • 新增 API:setCropAspectRatio / setCropOutputMaxSize / setCropQuality / setEditInPreviewEnable / setEditCacheDir / setCropResetText; FilePicker.clearEditCache / editCacheSize / editCacheDir。

修复

  • 文字/画笔比例统一相对原图短边:裁剪不再改变字号和笔刷粗细,画布与导出一致。
  • 返回键:裁剪模式下先退出裁剪模式,再按才关闭编辑弹窗;文字输入先收键盘。
  • 添加文字后立即显示(不点图片也可见);文字层级高于画笔与马赛克。
  • 再次选择时带入的已编辑图片,缩略图/预览/再次编辑都用编辑后的图。
  • demo:结果列表点击预览显示编辑后的图片。

7.4.1

修复预览页的一批体验问题:底部缩略图绿框复用残留、翻页手感、大图预览卡顿。

修复

  • 底部缩略图绿框残留:以前是"记住上一项、只清它一个",而翻页只改可见项, ViewHolder 被复用后会把绿框带到别的格子上。现在按 adapter position 整条重绘, 任何时候只有当前页那一格亮。
  • 底条滚动:预览页 / 最终预览页 / 相册页统一改用 RecyclerView.smoothScrollToPosition(), 删掉手算居中坐标的 CenterSmoothScroller(findViewByPosition 取不到视图时会算错落点,表现为"乱跳")。
  • 底条间距:三处底部条都按"一屏露出 5 个半格子"压缩间距(只动间距,图片尺寸、 右上角删除按钮的位置和大小都不变);格子已完全可见时不再起无意义的平滑滚动动画。
  • 大图预览卡顿:预览大图改为按屏幕 2 倍采样解码(原先是整张原图全尺寸解码, 4000×3000 的照片单张约 48MB),并先用网格缓存缩略图顶帧、预加载左右相邻页。 左右滑动明显跟手,屏幕内观感与原图一致。

说明

  • 无 API 变更,从 7.4.0 升级无需改代码。

7.4.0

明确新模型的启用条件:只有配置了 setSelectRules(...),setExclusive / setGifCountsAsImage 才生效;一个规则都不配时,一律回退到 setMaxSelectNumber。

变更

  • 新模型启用条件收敛为"规则表非空":

    • 配了规则 → 分类型限量 + 互斥 + GIF 归并生效;合计上限由各类型 max 自动叠加, setMaxSelectNumber 被忽略(打 warn 日志);
    • 没配规则 → setExclusive / setGifCountsAsImage 被忽略(打 warn 日志), 数量限制仍由 setMaxSelectNumber 决定。

    修掉了一个隐患:以前"只开互斥、不配规则"也会切到新模型,导致用户设的 maxSelectNumber 被静默丢掉。

  • 新增 SelectRulesEngine.createOrNull(...) 作为启用判定的唯一入口(纯函数,可单测)。

  • demo:规则模式选了但没填图片/视频数量时,「最大选择数量」重新显示并生效,同时 Toast 提示。

  • 单测补 2 条(启用条件 / 规则推导容量),共 30 条。

7.3.0 / 7.3.1 中间语义已被本版取代:7.3.0 是"合计上限与规则同时生效", 7.3.1 是"规则模式忽略 maxSelectNumber"。请以 7.4.0 为准。

7.3.1

数量限制只留一个来源:规则模式下 setMaxSelectNumber 不再生效,合计上限由各类型 max 自动叠加得出。这样不存在"合计上限与规则要求冲突"的配置,也就不需要任何运行时抉择。

变更

  • 规则模式下 setMaxSelectNumber 被忽略(打一条 warn 日志):合计上限 = 各类型 max 之和; 互斥模式下 = 单类型最大的 max。调用方不需要也不应该再写第二个数字。
  • 不调用 setSelectRules / setExclusive / setGifCountsAsImage 时,setMaxSelectNumber 依然是唯一口径,行为与老版本完全一致。
  • 由于合计上限恒 ≥ 必选数量,"配置矛盾"在库内已经不可能发生(引擎里的 checkSatisfiable 仅作为直接构造 SelectRulesEngine 时的兜底)。
  • demo:规则模式为「无」时显示「最大选择数量」并隐藏分类型输入;选共存 / 互斥时反过来。

7.3.0

合并重复 API:setTotalMaxCount 已移除,合计上限统一用 setMaxSelectNumber。 7.2.0 只发布了 1 天、该 API 没有实际使用;如果你的项目已经用了,换成 setMaxSelectNumber(n) 即可。

变更

  • 数量限制收敛为两个维度、两个 API:

    • 合计上限 → setMaxSelectNumber(n)(一直有效,两个模型都认);
    • 分类型 min/max → setSelectRules(...)。

    两者同时生效、取交集,不再有"启用规则后 setMaxSelectNumber 被忽略"这条规则 —— 那套"单一数据源" 的约定,实际效果是给同一个概念(合计上限)造了第二个名字,对使用者不友好。

  • 移除 setTotalMaxCount:它和 setMaxSelectNumber 含义完全相同。需要合计上限直接用 setMaxSelectNumber(n);不设置时,合计容量仍由各类型 max 之和自动推导。

  • 示例同步更新:setSelectRules(Image(0, 9), Video(0, 1)).setMaxSelectNumber(9) 表示 "图片 ≤ 9、视频 ≤ 1,且合计 ≤ 9"。

7.2.0

没有破坏性 API 变更:未删除方法、未修改签名。不调用新增的 setSelectRules / setExclusive / setTotalMaxCount / setGifCountsAsImage 时,行为与 7.1.0 完全一致。

新增

  • 按类型分别限量:setSelectRules(SelectRule...),图片 / 视频 / GIF 各自设置 min / max。 max 是硬上限,min > 0 表示"必须选"。命中上限时提示的是具体类型的额度 ("图片最多选择2个"),不再笼统地报"已达到最大选择数量" —— 此时视频可能还有额度, 笼统文案会让用户误以为整体已满。
  • 新类型 SelectRule:ofImage(min, max) / ofVideo(min, max) / ofGif(min, max); 带 encode / decode(intent 传递用),配置非法(如 min > max)在构造时就报错。
  • 互斥选择:setExclusive(true),图片和视频只能选其中一类。只有一类配了 min > 0 时直接锁它 (等于只能选图片 / 只能选视频);两类都配了 min > 0 时先点先锁、另一类的必选本次作废; 选中的类型全部取消后自动解锁。
  • 合计上限:setTotalMaxCount(count),属于新模型的一部分(不传则由各类型 max 之和推导)。
  • GIF 额度归属:默认 GIF 占用"图片"额度;单独配了 SelectRule.ofGif 时用自己的规则; 可用 setGifCountsAsImage(false) 关掉归并。
  • FilePickerUIConfig 新增 3 个提示文案:selectTypeMaxToastContent、 selectExclusiveConflictToastContent、selectRuleNotSatisfiedToastContent,可自定义 / 多语言。
  • 必选不满足时确定按钮不高亮:按钮置灰(alpha 0.4)但仍可点击,点击吐司"图片必须选3个", 不会返回 —— 既不让用户误以为能提交,也不会出现"按钮点不动又不知道为什么"。
  • 滑动连选按类型配额筛选:一次划过很多格时按顺序贪心填充,不会先超再回退。
  • 配置矛盾在开发期就报错:合计容量装不下必选数量(如合计 1 却要求图片必须 9 个)时, start() 直接抛 IllegalArgumentException,不留到运行期让用户卡在"确定永远点不了"。
  • FilePickerLog.w():新增 warn 级别日志,排查配置冲突用。
  • demo 新增"规则模式"入口:首页可选 无 / 共存 / 互斥,再填图片数量、视频数量并勾选"强制", demo 会按这三个参数自动拼出对应的 SelectRule。

行为变更(仅在新模型下生效)

  • 同时调用 setMaxSelectNumber 和新模型方法时,setMaxSelectNumber 被忽略并打 warn 日志: 数量限制只保留一个数据源,避免两个数字互相打架。
  • 预选列表(setSelectedList)超出规则时按规则截断并打 warn 日志。
  • setSingleClickEnable 的生效条件从"maxSelectNumber == 1"改为"总容量 == 1" (总容量 = setTotalMaxCount,未设置时由各类型 max 之和推导), 因此 SelectRule.ofVideo(1, 1) 也能触发"点一下就返回"。不启用新模型时二者等价,行为不变。

测试

  • 26 条 JVM 单测:SelectRulesEngineTest(18 条:共存 / 互斥 / 必选 / 上限 / GIF 归并 / 批量筛选 / 矛盾配置 / 序列化)+ SelectMessagesTest(8 条:提示文案映射)。
  • 真机验证(华为 BRE-AL80 / Android 12):互斥锁定、分类型上限、必选拦截、文案、老 API 行为不变、 两套参数并存时让位。

7.1.0

本版没有破坏性 API 变更:未删除任何方法、未修改任何签名,老代码升级后可直接编译。 只有两处 equals / hashCode 的语义变了,见末尾「行为变更」。

新增

  • 拍照 / 录像入口(默认关闭):setShowCameraEntry(true) 在网格第一格放一个相机格子,
    点击调起系统相机拍照或录像,产物写进系统相册(DCIM/Camera)并自动插到列表第一个。
    默认 false,不开启时对宿主零影响 —— 不挂 header、不注册 viewType、不碰权限、
    不给宿主 manifest 加任何声明(无 <provider>、无 <queries>)。

    • 画法随"当前类型下相机能产出什么"变:只有图片 / 只有视频 → 整格一个按钮(图标 + 文字);
      图片与视频都有(ofAll() / ofAllWithGif() 的「全部」页)→ 从中间劈开,左半边录像、
      右半边拍照
      ,中间一条分隔线
    • 有 tab 的类型里入口跟着当前 tab 走:在「视频」页只给录像、「图片」页只给拍照、「GIF」页不给
    • 拍完回来默认勾上("这张就是要用的");已到 maxSelectNumber 上限时只插入不勾选并给提示;
      单选 + setSingleClickEnable(true) 时拍完直接回调返回
    • 产物大小不符合 minFileSize / maxFileSize 时不插入、不勾选,并给提示
    • 宿主声明了 CAMERA 却未授权时,库先申请权限再起相机
    • 探相机用 startActivity 包 try/catch,不用 resolveActivity() —— targetSdk ≥ 30 且宿主
      没声明 <queries> 时它对相机 action 一律返回 null(实测),拿它当守卫会让入口永远点不动
    • 详见「拍照 / 录像入口」一节
  • 文档类型入口:ofMarkdown()(.md / .markdown)、ofXmind()(.xmind)、ofFreemind()(.mm),
    各有专属图标。三者只按后缀查询与识别(.xmind 的 mime 是 application/zip、.md 可能是
    text/plain,mime 轨不可靠),归在文档类型下、不属于压缩包:不参与 ZIP_ALL / isArchive() 推导。
    后缀表见 FilePickerSelectType.DOC_EXTS。ofDocument() 的查询条件不含这三种,行为不变。

  • FilePickerUIConfig 新增相机入口相关配置:cameraEntryIconResId、cameraEntryVideoIconResId、
    cameraEntryPhotoText、cameraEntryVideoText、cameraEntrySizeLimitToastContent、
    cameraEntryFailedToastContent —— 图标与文案全部可自定义,方便多语言适配。

修复

  • 缩略图与原图不同源("缩略图是这张、点开是另一张"):缩略图的权威来源改为真实文件,
    系统预生成的缩略图只当首帧占位;缩略图与原图统一带 MediaStoreSignature(mime + 修改时间 + 方向),
    文件被覆盖/编辑后旧缓存立即失效、不会继续显示旧图。MediaEntity 为此新增 dateModified,
    查询补上 DATE_MODIFIED 列。

  • 预览页显示的不是点的那张:ViewPager2.setCurrentItem 内部会把负下标静默夹到第 0 页,
    而列表在 fast→full 两次扫描之间会被整体替换,indexOf 可能返回 -1。现在所有交给 setCurrentItem
    的下标都先过一道 PreviewIndexResolver,规整到 [0, size-1]。

  • ofAllWithGif() 图片全丢:拼查询条件时,请求里同时含图片和 GIF、又不是"只有 GIF"时
    (正是 ofAllWithGif),图片那一支一个条件都没加上,最终只剩 mime_type LIKE 'video/%',
    图片被整片排除。已补上该分支。

  • 视频预览里封面缩在中间:两个原因叠加 —— ① 只有视频面被按宽高比撑开,封面一直是
    wrap_content + fitCenter,高度取决于缩略图自己的固有尺寸;② 宽高比没算旋转角:
    竖着录的视频存储宽高是横的(如 3840×2160),真实方向在 orientation / unappliedRotationDegrees 里,
    而封面缩略图是系统已经旋转好的竖图,两者对不上就被按高度压小。现在两者共用同一个盒子,
    并且都按显示方向算比例。

  • 拍摄产物被当成"没拍到"丢掉:原先用 MediaStore 的 SIZE <= 0 判断有没有内容,
    但个别机型 SIZE 回填滞后,会把刚拍的照片丢掉。现在 SIZE 为 0 时再真读一个字节确认;
    另外"查询失败"不再等同于"没有内容" —— 那条路径不删任何东西。

  • ImagePreviewActivity 的 url 判空写反了:正常地址会被当成非法地址直接 toast 并结束。

行为变更(等于修 bug,接入方留意)

这两处原来都是错的,修完之后语义变了 —— 如果宿主曾绕过它们,升级后行为会跟着变:

  • MediaEntity.equals / hashCode:原来只比 path,两个都拿不到 path 时会被判成同一个实体,
    于是 indexOf / contains 会命中错的元素(典型表现就是预览错位)。现在 path 无效时退回比较 uri,
    两者都没有有效标识则一律不相等。

  • MediaFolder.equals:原来三个字段的判断写反了(相等时返回 false),除了同一个引用以外
    永远不相等,mediaFolders.remove(folder) / contains 之类的操作全部失效。现在按
    name + folderPath + mediaEntityList 正常比较。

7.0.0

  • 类型入口 API 收敛到 FilePickerSelectType:ofAll() / ofImage() / ofVideo() / ofZipAll() 等
    全部类型方法现在挂在 FilePickerSelectType 上(语义更贴合)。FilePicker.ofXxx() 系列标注
    @Deprecated 但保留且可用(内部转发,行为不变),IDE 会提示迁移。

  • API 精简(破坏性变更):压缩包相关入口只保留核心四个 ofZipAll() / ofZip() / ofRar() / of7Z(),
    删除 ofTar() / ofGz() / ofBz2() / ofIso() / ofBr() / ofLz4() / ofZstd() / ofXz()。

  • 新增自定义后缀过滤:任意后缀组合不再需要为每种类型加 API,一行搞定:

    FilePicker.with(this)
        .setSelectType(FilePickerSelectType.ofExtensions("xz", "tar", "bak"))
        .start()
    • 后缀自动归一化:去前导点、转小写、去重、过滤 SQL 通配符(% _)

    • 过滤按文件名后缀匹配(不区分大小写),mime 轨不参与(认不出的后缀在 MediaStore 里多为 application/octet-stream)

    • SelectTypeUtil.isValidType() 已放行 ext: 前缀的自定义类型,UI 自动按"文件列表"模式呈现

  • 兼容性说明:FilePickerSelectType.TAR / GZ / BZ2 / ISO / BR / LZ4 / ZSTD / XZ 等类型常量
    全部保留,老代码直接传字符串常量依旧可用,行为不变;图标识别逻辑不受本次精简影响。

  • 压缩包逻辑收敛到唯一登记处:类型 → 后缀(ARCHIVE_EXTS)与类型 → mime(ARCHIVE_MIMES)
    两张登记表是唯一数据源,ZIP_ALL、MediaStore 查询条件、MediaEntity 类型识别(isArchive()/isZip() 等)
    全部由其派生。新增压缩格式只需在两张表里各加一行,查询/识别/图标自动跟上,
    不会再出现某处手抄漏掉新格式的问题。

  • 新增接入建议:有专属类型入口的后缀(图片/音频/文档/APK/核心压缩包)优先用 ofXxx() 入口,
    ofExtensions() 用于无对应类型的任意后缀。两者差异见「类型入口怎么选」一节。

  • 修复:RAR 文件识别不到图标/查询不到。
    不同设备的 MimeTypeMap 会把 .rar 映射成三种 mime 之一(application/x-rar-compressed、
    application/vnd.rar、application/rar),现在三种全部兼容;查询与图标识别同时增加文件名后缀兜底。

  • 修复:MediaStore 通用类型(application/octet-stream)文件识别不到。
    mime 为空或为 octet-stream 时,按文件名后缀兜底识别(rar/7z/tar/gz/bz2/iso/br/lz4/zstd/xz、
    图片/视频/音频/文档等全部受益),正确标注了 mime 的文件行为不变。

  • 修复:pptx / xlsx / docx 图标映射错误(原来落到错误图标),rar 使用专属图标,
    新增 zip/tar/gz/iso/rar/7z 等压缩格式独立图标;isArchive() 补齐 bz2 覆盖。

  • 去除硬编码:媒体类型判断中的 MIME 字符串全部收敛到 FilePickerMimeType 常量。

6.0.0

  • 性能大幅优化:图片数量很多的场景下,打开选择器的加载速度显著提升。

    • 媒体库扫描去掉逐行 File.stat() 与逐行日志,列索引一次性提取。

    • 选择状态由「遍历查找」改为「路径 → 下标」哈希表,拖拽/滑动多选在千张级别也保持 O(1)。

    • 首屏聚合、已选列表比对等重计算移出主线程,避免掉帧。

    • 再次进入选择器时,数据已加载则不再全量重扫。

  • 修复:设置最大选择数量后点击开始选择时偶发崩溃(ConcurrentModificationException)。

  • 修复:在部分华为等机型上启动报 ClassNotFoundException: FilePickerUIConfig 的问题(UI 配置改为纯基础类型 Bundle 传递,旧方式保持兼容)。

  • 修复:本地 GIF 不再写入磁盘缓存的问题,滑动加载更省流量/解码开销。

  • 拖拽排序、滑动多选、单选/取消、预览等交互行为与 5.x 完全一致。

5.9.0

  • 解决选择图片过多,intent超过1M的问题。随便选无上限

  • 优化index数字过大展示不全的问题。目前支持到 99999 张

  • 增加文件夹别名设置setUiConfig中folderNickNameMap里自行添加,默认不添加。也可做翻译文件夹用。仅文件夹哦。

  • 其他的UI上的文字都在FilePickerUIConfig中有对应属性,请仔细查看文档。

5.8.0 stable版本

  • 修复最后一行选中图片被遮挡的问题

  • 优化放大图标,更好看了。

5.7.0

  • 优化过多图片首屏展示慢的问题。

5.6.3

  • 修复音频播放放到后台还在继续的问题。

5.6.2

  • 适配xwidget的工具类,适配底部导航栏。xwidget库务必使用最新 5.6.2 版本。

  • 为了让版本 跟xwidget关联,方便使用和记忆,版本号跟xwidget统一了。

4.6.1

  • 剔除日志影响

4.6.0

  • 新版UI 点击更丝滑

4.5.1

  • 适配底部小横导航栏

  • 优化UI选择样式

4.3.0 (预计)

  • 新增:支持更多文件类型选择,如文档(audio, pdf, doc, ppt, excel, txt)、APK、各类压缩包等。

  • 新增:setSingleClickEnable(boolean) API,支持单选模式下单击立即返回。

  • 新增:setSlideChooseEnable(boolean) API,可禁用滑动选择手势。

  • 新增:FilePickerUIConfig 中增加 isHideSelectTab、allAlbumName、isShowBottomPreviewText 等UI配置项。

4.2.0

  • 修复maxFileSize默认值:目前改为Long.MAX_VALUE .

4.1.0

  • 适配手机底部导航栏,为屏幕内虚拟三键的情况。

About

1000万以内最好的全能(文件、视频、图片)选择库,微信抖音样式但仿华为相册滑动选择丝滑效果。支持文件选择(任何后缀)支持定义规则选择图片或者视频,比如图片9张或者视频1张。

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages