├── .github └── dependabot.yml ├── .gitignore ├── CNAME ├── LICENSE ├── README.md ├── README_0.x.md ├── README_CN.md ├── README_CN_0.x.md ├── _config.yml ├── build.gradle ├── filepicker ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── rosuh │ │ └── filepicker │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── rosuh │ │ │ └── filepicker │ │ │ ├── FilePickerActivity.kt │ │ │ ├── adapter │ │ │ ├── BaseAdapter.kt │ │ │ ├── FileListAdapter.kt │ │ │ ├── FileNavAdapter.kt │ │ │ └── RecyclerViewListener.kt │ │ │ ├── bean │ │ │ ├── FileBean.kt │ │ │ ├── FileItemBeanImpl.kt │ │ │ └── FileNavBeanImpl.kt │ │ │ ├── config │ │ │ ├── AbstractFileDetector.kt │ │ │ ├── AbstractFileFilter.kt │ │ │ ├── AbstractFileType.kt │ │ │ ├── DefaultFileDetector.kt │ │ │ ├── FileItemOnClickListener.kt │ │ │ ├── FilePickerConfig.kt │ │ │ ├── FilePickerManager.kt │ │ │ ├── ILog.kt │ │ │ ├── ItemClickListener.kt │ │ │ └── SimpleItemClickListener.kt │ │ │ ├── engine │ │ │ ├── GlideEngine.kt │ │ │ ├── ImageEngine.kt │ │ │ ├── ImageLoadController.kt │ │ │ └── PicassoEngine.kt │ │ │ ├── filetype │ │ │ ├── AudioFileType.kt │ │ │ ├── CompressedFileType.kt │ │ │ ├── DataBaseFileType.kt │ │ │ ├── DataFileType.kt │ │ │ ├── ExecutableFileType.kt │ │ │ ├── FileType.kt │ │ │ ├── FontFileType.kt │ │ │ ├── PageLayoutFileType.kt │ │ │ ├── RasterImageFileType.kt │ │ │ ├── TextFileType.kt │ │ │ ├── VideoFileType.kt │ │ │ └── WebFileType.kt │ │ │ ├── utils │ │ │ ├── DefaultLogger.kt │ │ │ ├── FileListAdapterListener.kt │ │ │ ├── FileUtils.kt │ │ │ └── ScreenUtils.kt │ │ │ └── widget │ │ │ ├── PosLinearLayoutManager.kt │ │ │ └── RecyclerViewFilePicker.kt │ └── res │ │ ├── anim │ │ ├── item_slide_down_anim_file_picker.xml │ │ └── layout_item_anim_file_picker.xml │ │ ├── drawable │ │ ├── bg_btn_focus_file_picker_crane.xml │ │ ├── bg_btn_focus_file_picker_rail.xml │ │ ├── bg_btn_focus_file_picker_reply.xml │ │ ├── bg_btn_focus_file_picker_shrine.xml │ │ ├── btn_selector_file_picker_crane.xml │ │ ├── btn_selector_file_picker_rail.xml │ │ ├── btn_selector_file_picker_reply.xml │ │ ├── btn_selector_file_picker_shrine.xml │ │ ├── btn_style_normal_file_picker.xml │ │ ├── ic_arrow_right_file_picker.xml │ │ ├── ic_back_file_picker.xml │ │ ├── ic_bt_file_picker.xml │ │ ├── ic_compressed_file_picker.xml │ │ ├── ic_empty_file_list_file_picker.xml │ │ ├── ic_excel_file_picker.xml │ │ ├── ic_exec_file_picker.xml │ │ ├── ic_folder_file_picker.xml │ │ ├── ic_html_file_picker.xml │ │ ├── ic_image_file_picker.xml │ │ ├── ic_music_file_picker.xml │ │ ├── ic_pdf_file_picker.xml │ │ ├── ic_txt_file_picker.xml │ │ ├── ic_unknown_file_picker.xml │ │ ├── ic_video_file_picker.xml │ │ ├── ic_word_file_picker.xml │ │ └── rec_loading_file_picker.xml │ │ ├── layout │ │ ├── empty_file_list_file_picker.xml │ │ ├── item_list_file_picker.xml │ │ ├── item_nav_file_picker.xml │ │ ├── item_single_choise_list_file_picker.xml │ │ └── main_activity_for_file_picker.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rHK │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values-zh │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ ├── style_theme_attrs.xml │ │ └── styles.xml │ └── test │ └── java │ └── me │ └── rosuh │ └── filepicker │ └── ExampleUnitTest.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── AndroidFilePicker_Banner_Dr_Sugiyama.png ├── Android_FilePicker_Banner.png ├── crane_theme.png ├── default_theme.png ├── multi_folder.jpg ├── onky_dir.jpg ├── reply_theme.png ├── sample_UI.jpg └── shrine_theme.png ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── rosuh │ │ │ └── sample │ │ │ ├── SampleActivity.kt │ │ │ ├── SampleAdapter.kt │ │ │ └── SampleInJavaActivity.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── demo_activity_main.xml │ │ └── fragment_sample.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-zh │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── me │ └── rosuh │ └── sample │ └── ExampleUnitTest.java └── settings.gradle /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: com.squareup.picasso:picasso 11 | versions: 12 | - "> 2.5.2" 13 | - dependency-name: junit:junit 14 | versions: 15 | - "> 4.12" 16 | - dependency-name: org.jetbrains.kotlin:kotlin-stdlib-jdk7 17 | versions: 18 | - 1.4.31 19 | - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin 20 | versions: 21 | - 1.4.31 22 | - dependency-name: com.android.tools.build:gradle 23 | versions: 24 | - 4.1.2 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | *.iml 37 | .idea/* 38 | 39 | # Keystore files 40 | # Uncomment the following line if you do not want to check your keystore files in. 41 | #*.jks 42 | 43 | # External native build folder generated in Android Studio 2.2 and later 44 | .externalNativeBuild 45 | 46 | # Google Services (e.g. APIs or Firebase) 47 | google-services.json 48 | 49 | # Freeline 50 | freeline.py 51 | freeline/ 52 | freeline_project_description.json 53 | 54 | # fastlane 55 | fastlane/report.xml 56 | fastlane/Preview.html 57 | fastlane/screenshots 58 | fastlane/test_output 59 | fastlane/readme.md 60 | .idea/misc.xml 61 | .idea/modules.xml 62 | .idea/codeStyles/Project.xml 63 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | afp.rosuh.me -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Banner](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/AndroidFilePicker_Banner_Dr_Sugiyama.png) 2 | 3 | # Android File Picker🛩️ 4 | 5 | [![](https://jitpack.io/v/me.rosuh/AndroidFilePicker.svg)](https://jitpack.io/#me.rosuh/AndroidFilePicker) 6 | 7 | [中文简体](./README_CN.md) 8 | 9 | If you're using `0.x` version, checkout the [README_0.x](./README_0.x.md) file. 10 | 11 | Well, it doesn't have a name like Rocky, Cosmos or Fish. Android File Picker, like its name, is a local file selector framework. Some of his characteristics are described below: 12 | 13 | - Launcher in Activity or Fragment 14 | - Start with a single line of code 15 | - Browse and select all files in local storage 16 | - Custom Root path to start 17 | - Built-in default file type and file discriminator 18 | - Or you can implement the file type yourself 19 | - Built in Single Choice mode and Multiple Choice mode. 20 | - Custom list filter 21 | - Just want to show pictures(Or videos, audio...)? No problem! 22 | - Of course, you can just display the folder 23 | - Custom item click event: only need to implement the listener 24 | - Apply different themes, including four built-in themes and custom themes 25 | - More to find out yourself 26 | 27 | | Rail | Reply | Crane | Shrine | 28 | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 29 | | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/default_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/reply_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/crane_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/shrine_theme.png) | 30 | 31 | ## Version Compatibility 32 | It depends on your targetAPI. 33 | 34 | - `targetAPI > 33`, may be you are finding [photo picker](https://developer.android.com/about/versions/14/changes/partial-photo-video-access?hl=zh-cn#media-reselection) 35 | - `targetAPI == 33` 36 | - Handle [media permissions](https://developer.android.com/training/data-storage/shared/media#access-other-apps-files) by your onw 37 | - This lib will only show media files which your app has permission to access 38 | - `targetAPI <= 33` 39 | - Set `android:requestLegacyExternalStorage="true"` in your `AndroidManifest.xml` file 40 | - Handler `android.permission.READ_EXTERNAL_STORAGE` permission by your own 41 | - This lib will show all files in your storage 42 | 43 | ## Download 44 | 45 | [Gradle](https://docs.jitpack.io/android/#installing): 46 | 47 | In your project `build.gradle`: 48 | 49 | ```xml 50 | dependencyResolutionManagement { 51 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 52 | repositories { 53 | google() 54 | mavenCentral() 55 | maven { url 'https://jitpack.io' } 56 | } 57 | } 58 | ``` 59 | 60 | In your module `build.gradle`: 61 | 62 | ```xml 63 | dependencies { 64 | implementation 'me.rosuh:AndroidFilePicker:$latest_version' 65 | } 66 | ``` 67 | This lib now support AndroidX, check the version below. 68 | 69 | Check out [releases page](https://github.com/rosuH/AndroidFilePicker/releases) to see more versions. 70 | 71 | ## Usage 📑 72 | 73 | ### Permission 74 | 75 | You should request permission by yourself, this lib will not request permission for you. 76 | See [Version Compatibility](#version-compatibility) for more details. 77 | 78 | ### Launch 🚀 79 | 80 | ```kotlin 81 | FilePickerManager 82 | .from(context) 83 | .forResult(FilePickerManager.REQUEST_CODE) 84 | ``` 85 | 86 | 87 | 88 | ### Receive Result 89 | 90 | In `onActivityResult()` callback of the starting `Activity` or `Fragment`: 91 | 92 | ```kotlin 93 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 94 | when (requestCode) { 95 | FilePickerManager.instance.REQUEST_CODE -> { 96 | if (resultCode == Activity.RESULT_OK) { 97 | val list = FilePickerManager.instance.obtainData() 98 | // do your work 99 | } else { 100 | Toast.makeText(this@SampleActivity, "You didn't choose anything~", Toast.LENGTH_SHORT).show() 101 | } 102 | } 103 | } 104 | } 105 | ``` 106 | 107 | The result is a path list of the selected file (`ArrayList()`). 108 | 109 | 110 | ## Docs 111 | 112 | - [Source Code Explanation](https://github.com/rosuH/AndroidFilePicker/wiki/4.-%E7%A4%BA%E4%BE%8B%E5%8F%8A%E8%A7%A3%E9%87%8A). 113 | 114 | - [Change Log](https://github.com/rosuH/AndroidFilePicker/wiki/Change-Log) 115 | 116 | - [TODO](https://github.com/rosuH/AndroidFilePicker/wiki/TODO) 117 | 118 | 119 | 120 | --- 121 | 122 | ## Special Thanks To: 123 | 124 | - [whichName](https://github.com/whichname) 125 | - [Matisse](https://github.com/zhihu/Matisse) 126 | - [Default Icon Author Shulk](http://iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=11271) 127 | - [Theme Color](https://material.io/design/material-studies/about-our-material-studies.html) 128 | - [Empty icon](https://github.com/rosuH/AndroidFilePicker/blob/master/filepicker/src/main/res/drawable/ic_empty_file_list_file_picker.xml) made by [freepik](https://www.freepik.com/) from www.flaticon.com 129 | -------------------------------------------------------------------------------- /README_0.x.md: -------------------------------------------------------------------------------- 1 | ![Banner](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/AndroidFilePicker_Banner_Dr_Sugiyama.png) 2 | 3 | # Android File Picker🛩️ 4 | 5 | [![](https://jitpack.io/v/me.rosuh/AndroidFilePicker.svg)](https://jitpack.io/#me.rosuh/AndroidFilePicker) 6 | 7 | [中文简体](./README_CN_1.x.md) 8 | 9 | Well, it doesn't have a name like Rocky, Cosmos or Fish. Android File Picker, like its name, is a local file selector framework. Some of his characteristics are described below: 10 | 11 | - Launcher in Activity or Fragment 12 | - Start with a single line of code 13 | - Browse and select all files in local storage 14 | - Custom Root path to start 15 | - Built-in default file type and file discriminator 16 | - Or you can implement the file type yourself 17 | - Built in Single Choice mode and Multiple Choice mode. 18 | - Custom list filter 19 | - Just want to show pictures(Or videos, audio...)? No problem! 20 | - Of course, you can just display the folder 21 | - Custom item click event: only need to implement the listener 22 | - Apply different themes, including four built-in themes and custom themes 23 | - More to find out yourself 24 | 25 | | Rail Style(default) | Reply Style | Crane Style | Shrine Style | 26 | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 27 | | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/default_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/reply_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/crane_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/shrine_theme.png) | 28 | 29 | ## Version Compatibility 30 | It depends on your targetAPI. 31 | 32 | - `targetAPI <= 28`, no problem at all ;) 33 | - `targetAPI == 29`, please enable `requestLegacyExternalStorage` feature for your project : D 34 | - `targetAPI == 29` 35 | - When running on Android 11 and above, only media files (images, audio and video) can be read, but nothing else can be accessed (e.g. PDF documents, apk binary files, etc.) 36 | 37 | Please check out this issue: [All About Scope Storage. ](https://github.com/rosuH/AndroidFilePicker/issues/146) 38 | ## Download 39 | 40 | Gradle: 41 | 42 | In your project `build.gradle`: 43 | 44 | ```xml 45 | allprojects { 46 | repositories { 47 | ... 48 | maven { url 'https://jitpack.io' } 49 | } 50 | } 51 | ``` 52 | 53 | In your module `build.gradle`: 54 | 55 | ```xml 56 | dependencies { 57 | implementation 'me.rosuh:AndroidFilePicker:$latest_version' 58 | } 59 | ``` 60 | This lib now support AndroidX, check the version below. 61 | 62 | Check out [releases page](https://github.com/rosuH/AndroidFilePicker/releases) to see more versions. 63 | 64 | ## Usage 📑 65 | 66 | ### Permission 67 | 68 | The library requires one permissions: 69 | 70 | - `android.permission.READ_EXTERNAL_STORAGE` 71 | 72 | If you do not have permission to apply, this framework will check and apply at startup. 73 | 74 | ### Launch 🚀 75 | 76 | ```kotlin 77 | FilePickerManager 78 | .from(context) 79 | .forResult(FilePickerManager.REQUEST_CODE) 80 | ``` 81 | 82 | 83 | 84 | ### Receive Result 85 | 86 | In `onActivityResult()` callback of the starting `Activity` or `Fragment`: 87 | 88 | ```kotlin 89 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 90 | when (requestCode) { 91 | FilePickerManager.instance.REQUEST_CODE -> { 92 | if (resultCode == Activity.RESULT_OK) { 93 | val list = FilePickerManager.instance.obtainData() 94 | // do your work 95 | } else { 96 | Toast.makeText(this@SampleActivity, "You didn't choose anything~", Toast.LENGTH_SHORT).show() 97 | } 98 | } 99 | } 100 | } 101 | ``` 102 | 103 | The result is a path list of the selected file (`ArrayList()`). 104 | 105 | 106 | ## Docs 107 | 108 | - [Source Code Explanation](https://github.com/rosuH/AndroidFilePicker/wiki/4.-%E7%A4%BA%E4%BE%8B%E5%8F%8A%E8%A7%A3%E9%87%8A). 109 | 110 | - [Change Log](https://github.com/rosuH/AndroidFilePicker/wiki/Change-Log) 111 | 112 | - [TODO](https://github.com/rosuH/AndroidFilePicker/wiki/TODO) 113 | 114 | 115 | 116 | --- 117 | 118 | ## Special Thanks To: 119 | 120 | - [whichName](https://github.com/whichname) 121 | - [Matisse](https://github.com/zhihu/Matisse) 122 | - [Default Icon Author Shulk](http://iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=11271) 123 | - [Theme Color](https://material.io/design/material-studies/about-our-material-studies.html) 124 | - [Empty icon](https://github.com/rosuH/AndroidFilePicker/blob/master/filepicker/src/main/res/drawable/ic_empty_file_list_file_picker.xml) made by [freepik](https://www.freepik.com/) from www.flaticon.com 125 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | ![Banner](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/AndroidFilePicker_Banner_Dr_Sugiyama.png) 2 | 3 | # Android File Picker🛩️ 4 | 5 | [![](https://jitpack.io/v/me.rosuh/AndroidFilePicker.svg)](https://jitpack.io/#me.rosuh/AndroidFilePicker) 6 | 7 | 如果你使用的是 `1.x` 版本,请查看 [README_0.x](./README_CN_0.x.md) 文件。 8 | 9 | 它没有像 Rocky、Cosmos 或 Fish 这样的名字。Android File Picker,正如其名,是一个本地文件选择框架。以下是它的一些特点: 10 | 11 | - 在 Activity 或 Fragment 中启动 12 | - 一行代码启动 13 | - 浏览和选择本地存储中的所有文件 14 | - 自定义根路径开始 15 | - 内置默认文件类型和文件区分器 16 | - 或者你可以自己实现文件类型 17 | - 内置单选模式和多选模式。 18 | - 自定义列表过滤器 19 | - 只想显示图片(或视频、音频...)?没问题! 20 | - 当然,你也可以只显示文件夹 21 | - 自定义条目点击事件:只需实现监听器 22 | - 应用不同的主题,包括四个内置主题和自定义主题 23 | - 更多功能等你发现 24 | 25 | | Rail | Reply | Crane | Shrine | 26 | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 27 | | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/default_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/reply_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/crane_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/shrine_theme.png) | 28 | 29 | ## 版本兼容性 30 | 取决于你的 targetAPI。 31 | 32 | - `targetAPI > 33`,也许你正在寻找 [照片选择器](https://developer.android.com/about/versions/14/changes/partial-photo-video-access?hl=zh-cn#media-reselection) 33 | - `targetAPI == 33` 34 | - 处理[媒体权限](https://developer.android.com/training/data-storage/shared/media#access-other-apps-files)由你自己处理 35 | - 此库将仅显示你的应用有权限访问的媒体文件 36 | - `targetAPI <= 33` 37 | - 在你的 `AndroidManifest.xml` 文件中设置 `android:requestLegacyExternalStorage="true"` 38 | - 由你自己处理 `android.permission.READ_EXTERNAL_STORAGE` 权限 39 | - 此库将显示存储中的所有文件 40 | 41 | ## 下载 42 | 43 | [Gradle](https://docs.jitpack.io/android/#installing): 44 | 45 | 在项目的 `build.gradle` 文件中: 46 | 47 | ```gradle 48 | dependencyResolutionManagement { 49 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 50 | repositories { 51 | google() 52 | mavenCentral() 53 | maven { url 'https://jitpack.io' } 54 | } 55 | } 56 | 在模块的 build.gradle 文件中: 57 | 58 | ```gradle 59 | dependencies { 60 | implementation 'me.rosuh:AndroidFilePicker:$latest_version' 61 | } 62 | ``` -------------------------------------------------------------------------------- /README_CN_0.x.md: -------------------------------------------------------------------------------- 1 | ![Banner](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/AndroidFilePicker_Banner_Dr_Sugiyama.png) 2 | 3 | # AndroidFilePicker 4 | 5 | [![](https://jitpack.io/v/me.rosuh/AndroidFilePicker.svg)](https://jitpack.io/#me.rosuh/AndroidFilePicker) 6 | 7 | 8 | 9 | 它没有像 Rocky,Cosmos 或是 Peppa 这样的名字。 Android File Picker 正如其名,是一个本地文件选择器框架。 他的一些特征如下所述: 10 | 11 | - 在 `Activity` 或 `Fragment` 中启动 12 | - 从一行代码开始 13 | - 浏览本地存储中的所有文件 14 | - 内置默认文件类型和文件鉴别器 15 | - 或者您可以自己实现文件类型 16 | - 内置了单选模式和多选模式 17 | - 自定义列表过滤器 18 | - 只想显示图片(或视频,音频......)? 没问题! 19 | - 当然,您也可只显示文件夹 20 | - 自定义`item`点击事件:只需要实现监听器 21 | - 四个内置主题和自定义主题 22 | - 还有更多待您自己探索的特性(?) 23 | 24 | 25 | 26 | | Rail Style(default) | Reply Style | Crane Style | Shrine Style | 27 | | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | 28 | | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/default_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/reply_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/crane_theme.png) | ![](https://raw.githubusercontent.com/rosuH/AndroidFilePicker/master/images/shrine_theme.png) | 29 | 30 | ## 版本兼容性 31 | 这取决于您的 targetAPI : 32 | 33 | - `targetAPI <= 28`,完全没有问题 ;) 34 | - `targetAPI == 29`,请为您的项目启用 `requestLegacyExternalStorage` 特性:D 35 | - `targetAPI == 29` 36 | - 当运行于 Android 11以及以上的平台时,仅可以读取媒体文件(图片、音视频),除此均无法访问(比如PDF文档、apk 二进制文件等) 37 | 38 | 请参看 issue: [All About Scope Storage. ](https://github.com/rosuH/AndroidFilePicker/issues/146) 39 | ## 下载使用 40 | 41 | 1. 在你的项目中添加依赖 42 | 43 | 现在项目 `build.gradle` 配置文件添加仓库: 44 | 45 | ```xml 46 | allprojects { 47 | repositories { 48 | ... 49 | maven { url 'https://jitpack.io' } 50 | } 51 | } 52 | ``` 53 | 54 | 然后在子模块(`app`)的配置文件添加依赖: 55 | 56 | ```xml 57 | dependencies { 58 | implementation 'me.rosuh:AndroidFilePicker:latest_version' 59 | } 60 | ``` 61 | 62 | `latest_version` 请自行替换成 [最新版本](https://github.com/rosuH/AndroidFilePicker/releases) 63 | 64 | 65 | 66 | ## 使用 67 | 68 | ### 权限 69 | 70 | 你需要自己申请权限,这个库不会为你申请权限。看看[版本兼容性](#版本兼容性)了解更多细节。 71 | 72 | ### 开始使用 73 | 74 | 简单的链式调用示意: 75 | 76 | ```kotlin 77 | FilePickerManager 78 | .from(context) 79 | .forResult(FilePickerManager.REQUEST_CODE) 80 | ``` 81 | 82 | 现在你已经起飞了🛩️...(真的只有两行) 83 | 84 | 85 | ### 获取结果 86 | 87 | *获取结果*:`onActivityResult`接受消息,然后调用`FilePickerManager.obtainData()`获取保存的数据,**结果是所选取文件的路径列表(`ArrayList()`)** 88 | 89 | ```kotlin 90 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 91 | when (requestCode) { 92 | FilePickerManager.instance.REQUEST_CODE -> { 93 | if (resultCode == Activity.RESULT_OK) { 94 | val list = FilePickerManager.instance.obtainData() 95 | // do your work 96 | } else { 97 | Toast.makeText(this@SampleActivity, "没有选择任何东西~", Toast.LENGTH_SHORT).show() 98 | } 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ### 更多示例 105 | 106 | 来翻翻我写的[飞行手册](https://github.com/rosuH/AndroidFilePicker/wiki)吧? 107 | 108 | 或者想看看[主题配色](https://github.com/rosuH/AndroidFilePicker/wiki/3.-%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9#2-%E4%B8%BB%E9%A2%98%E5%B1%95%E7%A4%BA)? 109 | 110 | ## 功能 & 特点 111 | 112 | 1. 链式调用 113 | 2. 默认选中实现 114 | - 点击条目(`item`)无默认实现 115 | - 点击`CheckBox`为选中 116 | - 长按条目为更改选中状态:选中/取消选中 117 | 3. 内置四种主题配色 + 可自定义配色 118 | - 查看主题颜色示意图,然后调用`setTheme()`传入自定义主题 119 | 4. 默认实现多种文件类型 120 | - 实现`IFileType`接口来实现你的文件类型 121 | - 实现`AbstractFileType`抽象类来实现你的文件类型甄别器 122 | 5. 公开文件过滤接口 123 | - 实现`AbstractFileFilter`抽象类来定制你自己的文件过滤器,这样可以控制文件列表的展示内容 124 | 6. 多种可配置选项 125 | 1. 选中时是否忽略文件夹 126 | 2. 是否显示隐藏文件夹(以符号`.`开头的,视为隐藏文件或隐藏文件夹) 127 | 3. 可配置导航栏的文本,默认显示、多选文本、取消选择文本以及根目录默认名称 128 | 7. 公开条目(`item`)选择监听器,可自定义条目被点击的实现 129 | 130 | ## 其他 131 | 132 | - [部分源码说明](https://github.com/rosuH/AndroidFilePicker/wiki/%E9%83%A8%E5%88%86%E6%BA%90%E7%A0%81%E8%AF%B4%E6%98%8E)。 133 | 134 | - [更新日志](https://github.com/rosuH/AndroidFilePicker/wiki/Change-Log) 135 | 136 | - [TODO](https://github.com/rosuH/AndroidFilePicker/wiki/TODO) 137 | 138 | 139 | 140 | --- 141 | 142 | ## Special Thanks To: 143 | 144 | - [*1 @whichName](https://github.com/whichname) 145 | - [BRVAH](https://github.com/CymChad/BaseRecyclerViewAdapterHelper) 146 | - [Matisse](https://github.com/zhihu/Matisse) 147 | - [默认图标作者 Shulk](http://iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=11271) 148 | - [主题配色](https://material.io/design/material-studies/about-our-material-studies.html) 149 | - [Empty icon](https://github.com/rosuH/AndroidFilePicker/blob/master/filepicker/src/main/res/drawable/ic_empty_file_list_file_picker.xml) made by [freepik](https://www.freepik.com/) from www.flaticon.com 150 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version="1.6.0" 5 | repositories { 6 | google() 7 | mavenCentral() 8 | maven { 9 | url "https://plugins.gradle.org/m2/" 10 | } 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:7.0.0' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 16 | classpath "org.jlleitschuh.gradle:ktlint-gradle:10.0.0" 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | apply plugin: "org.jlleitschuh.gradle.ktlint" 22 | 23 | allprojects { 24 | repositories { 25 | google() 26 | mavenCentral() 27 | maven { url 'https://jitpack.io' } 28 | } 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /filepicker/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /filepicker/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'maven-publish' 6 | 7 | group='com.github.rosuH' 8 | 9 | android { 10 | defaultConfig { 11 | minSdkVersion 16 12 | compileSdk 33 13 | targetSdkVersion 33 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | packagingOptions { 27 | exclude 'META-INF/proguard/androidx-annotations.pro' 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | } 35 | 36 | 37 | afterEvaluate { 38 | publishing { 39 | publications { 40 | release(MavenPublication) { 41 | from components.release 42 | groupId = 'me.rosuh' 43 | artifactId = 'AndroidFilePicker' 44 | version = '1.0' 45 | } 46 | } 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation fileTree(dir: 'libs', include: ['*.jar']) 52 | compileOnly 'com.squareup.picasso:picasso:2.5.2' 53 | compileOnly ("com.github.bumptech.glide:glide:4.9.0") { 54 | exclude group: "com.android.support" 55 | } 56 | def kotlin_version = '1.6.0' 57 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 58 | 59 | implementation 'androidx.appcompat:appcompat:1.3.0' 60 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 61 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2' 62 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" 63 | testImplementation 'junit:junit:4.12' 64 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 65 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 66 | } 67 | -------------------------------------------------------------------------------- /filepicker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /filepicker/src/androidTest/java/me/rosuh/filepicker/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker; 2 | 3 | import android.content.Context; 4 | import androidx.test.platform.app.InstrumentationRegistry; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /filepicker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/adapter/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.adapter 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import android.view.View 5 | import me.rosuh.filepicker.bean.FileBean 6 | 7 | abstract class BaseAdapter : RecyclerView.Adapter() { 8 | abstract fun getItem(position: Int): FileBean? 9 | abstract fun getItemView(position: Int): View? 10 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/adapter/FileListAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.adapter 2 | 3 | import android.os.Build 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.* 8 | import androidx.collection.ArraySet 9 | import androidx.recyclerview.widget.RecyclerView 10 | import me.rosuh.filepicker.FilePickerActivity 11 | import me.rosuh.filepicker.R 12 | import me.rosuh.filepicker.bean.FileBean 13 | import me.rosuh.filepicker.bean.FileItemBeanImpl 14 | import me.rosuh.filepicker.config.FilePickerManager.config 15 | import me.rosuh.filepicker.engine.ImageLoadController 16 | import me.rosuh.filepicker.filetype.RasterImageFileType 17 | import me.rosuh.filepicker.filetype.VideoFileType 18 | import me.rosuh.filepicker.utils.FileListAdapterListener 19 | import me.rosuh.filepicker.utils.FileListAdapterListenerBuilder 20 | 21 | /** 22 | * 23 | * @author rosu 24 | * @date 2018/11/21 25 | * 文件列表适配器类 26 | */ 27 | class FileListAdapter( 28 | private val context: FilePickerActivity, 29 | var isSingleChoice: Boolean = config.singleChoice 30 | ) : BaseAdapter() { 31 | val dataList: ArrayList = ArrayList(10) 32 | private var latestChoicePos = -1 33 | private lateinit var recyclerView: RecyclerView 34 | 35 | private var listener: FileListAdapterListener? = null 36 | 37 | fun addListener(block: FileListAdapterListenerBuilder.() -> Unit) { 38 | this.listener = FileListAdapterListenerBuilder().also(block) 39 | } 40 | 41 | private val checkedSet: ArraySet by lazy { 42 | ArraySet(20) 43 | } 44 | 45 | val checkedCount: Int 46 | get() = checkedSet.count() 47 | 48 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 49 | if (parent is RecyclerView) { 50 | recyclerView = parent 51 | } 52 | return FileListItemHolder( 53 | LayoutInflater.from(context).inflate( 54 | R.layout.item_list_file_picker, 55 | parent, 56 | false 57 | ) 58 | ) 59 | } 60 | 61 | override fun getItemView(position: Int): View? { 62 | return recyclerView.findViewHolderForAdapterPosition(position)?.itemView 63 | } 64 | 65 | override fun getItemCount(): Int { 66 | return dataList.size 67 | } 68 | 69 | override fun getItemViewType(position: Int): Int { 70 | return DEFAULT_FILE_TYPE 71 | } 72 | 73 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { 74 | val item = getItem(position) ?: return 75 | (holder as BaseViewHolder).bind(item, position) 76 | } 77 | 78 | override fun onBindViewHolder( 79 | holder: RecyclerView.ViewHolder, 80 | position: Int, 81 | payloads: MutableList 82 | ) { 83 | // Using payload to refresh partly 84 | // 使用 payload 进行局部刷新 85 | if (payloads.isEmpty()) { 86 | onBindViewHolder(holder, position) 87 | return 88 | } 89 | (holder as FileListItemHolder).check(getItem(position)?.isChecked() ?: false) 90 | } 91 | 92 | override fun getItem(position: Int): FileItemBeanImpl? { 93 | if (position >= 0 && 94 | position < dataList.size && 95 | getItemViewType(position) == DEFAULT_FILE_TYPE 96 | ) return dataList[position] 97 | return null 98 | } 99 | 100 | /*--------------------------ViewHolder Begin------------------------------*/ 101 | 102 | abstract inner class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 103 | abstract fun bind(itemImpl: FileItemBeanImpl, position: Int) 104 | } 105 | 106 | 107 | inner class FileListItemHolder(itemView: View) : 108 | BaseViewHolder(itemView) { 109 | 110 | private val isSkipDir: Boolean = config.isSkipDir 111 | private val tvFileName = itemView.findViewById(R.id.tv_list_file_picker)!! 112 | private val checkBox = itemView.findViewById(R.id.cb_list_file_picker)!! 113 | private val ivIcon = itemView.findViewById(R.id.iv_icon_list_file_picker)!! 114 | private val radioButton = itemView.findViewById(R.id.rb_list_file_picker)!! 115 | 116 | init { 117 | val rightId = if (config.singleChoice) { 118 | R.id.rb_list_file_picker 119 | } else { 120 | R.id.cb_list_file_picker 121 | } 122 | val params = tvFileName.layoutParams as RelativeLayout.LayoutParams 123 | params.addRule(RelativeLayout.LEFT_OF, rightId) 124 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 125 | params.addRule(RelativeLayout.START_OF, rightId) 126 | } 127 | tvFileName.layoutParams = params 128 | } 129 | 130 | fun check(isCheck: Boolean) { 131 | if (config.singleChoice) { 132 | radioButton.isChecked = isCheck 133 | } else { 134 | checkBox.isChecked = isCheck 135 | } 136 | } 137 | 138 | private fun onCheck( 139 | itemImpl: FileItemBeanImpl, 140 | buttonView: CompoundButton, 141 | isChecked: Boolean, 142 | position: Int 143 | ) { 144 | if (isChecked) { 145 | checkedSet.add(itemImpl) 146 | } else { 147 | checkedSet.remove(itemImpl) 148 | } 149 | itemImpl.setCheck(isChecked) 150 | listener?.onCheckSizeChanged(checkedCount) 151 | // listener?.onCheck(isChecked, buttonView, position) 152 | } 153 | 154 | override fun bind(itemImpl: FileItemBeanImpl, position: Int) { 155 | tvFileName.text = itemImpl.fileName 156 | checkBox.apply { 157 | tag = itemImpl 158 | visibility = when { 159 | (isSkipDir && itemImpl.isDir) || config.singleChoice -> { 160 | View.GONE 161 | } 162 | else -> { 163 | View.VISIBLE 164 | } 165 | } 166 | setOnCheckedChangeListener { buttonView, isChecked -> 167 | if (tag != itemImpl) return@setOnCheckedChangeListener 168 | onCheck(itemImpl, buttonView, isChecked, position) 169 | } 170 | isChecked = itemImpl.isChecked() 171 | } 172 | 173 | radioButton.apply { 174 | tag = itemImpl 175 | visibility = when { 176 | (isSkipDir && itemImpl.isDir) || !config.singleChoice -> { 177 | View.GONE 178 | } 179 | else -> { 180 | View.VISIBLE 181 | } 182 | } 183 | setOnCheckedChangeListener { buttonView, isChecked -> 184 | if (tag != itemImpl) return@setOnCheckedChangeListener 185 | onCheck(itemImpl, buttonView, isChecked, position) 186 | } 187 | isChecked = itemImpl.isChecked() 188 | } 189 | 190 | when { 191 | itemImpl.isDir -> { 192 | ivIcon.setImageResource(R.drawable.ic_folder_file_picker) 193 | } 194 | else -> { 195 | val resId: Int = 196 | itemImpl.fileType?.fileIconResId ?: R.drawable.ic_unknown_file_picker 197 | when (itemImpl.fileType) { 198 | is RasterImageFileType, is VideoFileType -> { 199 | ImageLoadController.load( 200 | context, 201 | ivIcon, 202 | itemImpl.filePath, 203 | resId 204 | ) 205 | } 206 | else -> { 207 | ivIcon.setImageResource(resId) 208 | } 209 | } 210 | } 211 | } 212 | } 213 | } 214 | 215 | /*--------------------------ViewHolder End------------------------------*/ 216 | 217 | /*--------------------------OutSide call method begin------------------------------*/ 218 | 219 | fun setNewData(list: List?) { 220 | list?.let { 221 | dataList.clear() 222 | dataList.addAll(it) 223 | notifyDataSetChanged() 224 | } 225 | } 226 | 227 | inline fun multipleCheckOrNo( 228 | item: FileItemBeanImpl, 229 | position: Int, 230 | isCanSelect: () -> Boolean, 231 | checkFailedFunc: () -> Unit 232 | ) { 233 | when { 234 | item.isChecked() -> { 235 | // 当前被选中,说明即将取消选中 236 | // had selected, will dis-select 237 | multipleDisCheck(position) 238 | } 239 | isCanSelect() -> { 240 | // 当前未被选中,并且检查合格,则即将新增选中 241 | // current item is not selected, and can be selected, will select 242 | multipleCheck(position) 243 | } 244 | else -> { 245 | // 新增选中项失败的情况 246 | // add new selected item failed 247 | checkFailedFunc() 248 | } 249 | } 250 | } 251 | 252 | fun multipleCheck(position: Int) { 253 | getItem(position)?.let { 254 | it.setCheck(true) 255 | notifyItemChanged(position, true) 256 | } 257 | } 258 | 259 | fun multipleDisCheck(position: Int) { 260 | getItem(position)?.let { 261 | it.setCheck(false) 262 | notifyItemChanged(position, false) 263 | } 264 | } 265 | 266 | fun singleCheck(position: Int) { 267 | when (latestChoicePos) { 268 | -1 -> { 269 | // 从未选中过 270 | getItem(position)?.let { 271 | it.setCheck(true) 272 | notifyItemChanged(position, true) 273 | } 274 | latestChoicePos = position 275 | } 276 | position -> { 277 | // 取消选中 278 | getItem(latestChoicePos)?.let { 279 | it.setCheck(false) 280 | notifyItemChanged(latestChoicePos, false) 281 | } 282 | latestChoicePos = -1 283 | } 284 | else -> { 285 | // disCheck the old one 286 | getItem(latestChoicePos)?.let { 287 | it.setCheck(false) 288 | notifyItemChanged(latestChoicePos, false) 289 | } 290 | // check the new one 291 | latestChoicePos = position 292 | getItem(latestChoicePos)?.let { 293 | it.setCheck(true) 294 | notifyItemChanged(latestChoicePos, true) 295 | } 296 | } 297 | } 298 | } 299 | 300 | fun disCheckAll() { 301 | dataList 302 | .forEachIndexed { index, item -> 303 | if (!(config.isSkipDir && item.isDir) && item.isChecked()) { 304 | item.setCheck(false) 305 | checkedSet.remove(item) 306 | listener?.onCheckSizeChanged(checkedCount) 307 | notifyItemChanged(index, false) 308 | } 309 | } 310 | } 311 | 312 | fun checkAll() { 313 | dataList 314 | .forEachIndexed { index, item -> 315 | if (checkedSet.size >= config.maxSelectable) { 316 | return 317 | } 318 | if (!(config.isSkipDir && item.isDir) && !item.isChecked()) { 319 | item.setCheck(true) 320 | checkedSet.add(item) 321 | listener?.onCheckSizeChanged(checkedCount) 322 | notifyItemChanged(index, true) 323 | } 324 | } 325 | } 326 | 327 | fun resetCheck() { 328 | checkedSet.clear() 329 | } 330 | 331 | 332 | /*--------------------------OutSide call method end------------------------------*/ 333 | companion object { 334 | const val DEFAULT_FILE_TYPE = 10001 335 | } 336 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/adapter/FileNavAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.adapter 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import me.rosuh.filepicker.FilePickerActivity 9 | import me.rosuh.filepicker.R 10 | import me.rosuh.filepicker.bean.FileNavBeanImpl 11 | 12 | /** 13 | * 14 | * @author rosu 15 | * @date 2018/11/21 16 | */ 17 | class FileNavAdapter( 18 | private val activity: FilePickerActivity 19 | ) : BaseAdapter() { 20 | private lateinit var recyclerView: RecyclerView 21 | val dataList: ArrayList = ArrayList(3) 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { 24 | if (parent is RecyclerView) { 25 | recyclerView = parent 26 | } 27 | return NavListHolder(activity.layoutInflater, parent) 28 | } 29 | 30 | override fun getItemView(position: Int): View? { 31 | return recyclerView.findViewHolderForAdapterPosition(position)?.itemView 32 | } 33 | 34 | override fun getItemCount(): Int { 35 | return dataList.size 36 | } 37 | 38 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, postion: Int) { 39 | (holder as NavListHolder).bind(dataList[postion], postion) 40 | } 41 | 42 | override fun getItem(position: Int): FileNavBeanImpl? { 43 | return if (position >= 0 && position < dataList.size) { 44 | dataList[position] 45 | } else { 46 | null 47 | } 48 | } 49 | 50 | fun setNewData(list: List?) { 51 | list?.let { 52 | dataList.clear() 53 | dataList.addAll(it) 54 | notifyDataSetChanged() 55 | } 56 | } 57 | 58 | inner class NavListHolder(inflater: LayoutInflater, val parent: ViewGroup) : 59 | RecyclerView.ViewHolder(inflater.inflate(R.layout.item_nav_file_picker, parent, false)) { 60 | 61 | private var mBtnDir: TextView? = null 62 | 63 | private var pos: Int? = null 64 | 65 | fun bind(item: FileNavBeanImpl?, position: Int) { 66 | pos = position 67 | mBtnDir = itemView.findViewById(R.id.tv_btn_nav_file_picker) 68 | mBtnDir?.text = item!!.dirName 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/adapter/RecyclerViewListener.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.adapter 2 | 3 | import android.content.res.Resources 4 | import androidx.core.view.GestureDetectorCompat 5 | import androidx.recyclerview.widget.RecyclerView 6 | import android.view.GestureDetector 7 | import android.view.MotionEvent 8 | import android.view.View 9 | import me.rosuh.filepicker.R 10 | 11 | /** 12 | * 13 | * @author rosu 14 | * @date 2018/11/29 15 | * 列表点击监听器,监听列表的点击并分辨出为单击、长按和子项被点击 16 | * OnItemTouchListener 无法轻易实现对子控件点击事件的监听 17 | * 18 | */ 19 | class RecyclerViewListener( 20 | val recyclerView: RecyclerView, 21 | val itemClickListener: OnItemClickListener 22 | ) : 23 | RecyclerView.OnItemTouchListener { 24 | 25 | /** 26 | * Custom item click listener, receive item event and redispatch 27 | */ 28 | interface OnItemClickListener { 29 | 30 | /** 31 | * Item click 32 | */ 33 | fun onItemClick( 34 | adapter: RecyclerView.Adapter, 35 | view: View, 36 | position: Int 37 | ) 38 | 39 | /** 40 | * Item long click 41 | */ 42 | fun onItemLongClick( 43 | adapter: RecyclerView.Adapter, 44 | view: View, 45 | position: Int 46 | ) 47 | 48 | /** 49 | * Item child click 50 | */ 51 | fun onItemChildClick( 52 | adapter: RecyclerView.Adapter, 53 | view: View, 54 | position: Int 55 | ) 56 | } 57 | 58 | private var gestureDetectorCompat: GestureDetectorCompat = 59 | GestureDetectorCompat(recyclerView.context, ItemTouchHelperGestureListener()) 60 | 61 | override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) { 62 | gestureDetectorCompat.onTouchEvent(e) 63 | } 64 | 65 | override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { 66 | return gestureDetectorCompat.onTouchEvent(e) 67 | } 68 | 69 | override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {} 70 | 71 | private val screenWidth by lazy { Resources.getSystem().displayMetrics.widthPixels } 72 | private val iconRight = screenWidth * 0.1370 73 | private val checkBoxLeft = screenWidth * (1 - 0.1370) 74 | 75 | inner class ItemTouchHelperGestureListener : GestureDetector.SimpleOnGestureListener() { 76 | override fun onSingleTapUp(e: MotionEvent): Boolean { 77 | val childView = recyclerView.findChildViewUnder(e!!.x, e.y) 78 | childView ?: return false 79 | when (childView.id) { 80 | R.id.item_list_file_picker -> { 81 | // 点击在 icon 上或者点击在 checkbox 上 82 | if (e.x <= iconRight || e.x >= checkBoxLeft) { 83 | itemClickListener.onItemChildClick( 84 | recyclerView.adapter!!, 85 | childView, 86 | recyclerView.getChildLayoutPosition(childView) 87 | ) 88 | return true 89 | } 90 | itemClickListener.onItemClick( 91 | recyclerView.adapter!!, 92 | childView, 93 | recyclerView.getChildLayoutPosition(childView) 94 | ) 95 | } 96 | R.id.item_nav_file_picker -> { 97 | itemClickListener.onItemClick( 98 | recyclerView.adapter!!, 99 | childView, 100 | recyclerView.getChildLayoutPosition(childView) 101 | ) 102 | } 103 | } 104 | return true 105 | } 106 | 107 | override fun onLongPress(e: MotionEvent) { 108 | val childView = recyclerView.findChildViewUnder(e!!.x, e.y) 109 | childView ?: return 110 | when (childView.id) { 111 | R.id.item_list_file_picker -> { 112 | itemClickListener.onItemLongClick( 113 | recyclerView.adapter!!, 114 | childView, 115 | recyclerView.getChildLayoutPosition(childView) 116 | ) 117 | } 118 | } 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/bean/FileBean.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.bean 2 | 3 | /** 4 | * 5 | * @author rosu 6 | * @date 2018/11/21 7 | */ 8 | interface FileBean { 9 | var fileName: String 10 | var filePath: String 11 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/bean/FileItemBeanImpl.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.bean 2 | 3 | import me.rosuh.filepicker.filetype.FileType 4 | import java.io.File 5 | 6 | /** 7 | * 8 | * @author rosu 9 | * @date 2018/11/21 10 | * 11 | * 文件列表项 12 | * @property isChecked 是否被选中 13 | * @property fileType 文件类型 14 | * @property isHide 是否为隐藏文件,以符号 . 开头的视为隐藏文件 15 | * 16 | * FileBean 接口属性 17 | * @property fileName 接口文件名 18 | * @property filePath 接口文件路径 19 | * @constructor 20 | */ 21 | class FileItemBeanImpl( 22 | override var fileName: String, 23 | override var filePath: String, 24 | private var isChecked: Boolean, 25 | var fileType: FileType?, 26 | val isDir: Boolean, 27 | var isHide: Boolean, 28 | ) : FileBean { 29 | 30 | fun isChecked(): Boolean { 31 | return isChecked 32 | } 33 | 34 | fun setCheck(check: Boolean) { 35 | isChecked = check 36 | } 37 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/bean/FileNavBeanImpl.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.bean 2 | 3 | /** 4 | * 5 | * @author rosu 6 | * @date 2018/11/21 7 | */ 8 | class FileNavBeanImpl(val dirName: String, val dirPath: String) : FileBean { 9 | override var fileName: String 10 | get() = dirName 11 | set(value) {} 12 | override var filePath: String 13 | get() = dirPath 14 | set(value) {} 15 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/AbstractFileDetector.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import me.rosuh.filepicker.bean.FileItemBeanImpl 4 | import me.rosuh.filepicker.filetype.FileType 5 | 6 | /** 7 | * 8 | * @author rosu 9 | * @date 2020/06/25 10 | * 这个类用于注册你自己的文件类型检测方法。您需要遵循下列步骤: 11 | * 1. 实现您自己的文件类型[FileType],也就是其中的[FileType.verify]方法 12 | * 2. 构建此类的一个子类,并在[AbstractFileDetector.fillFileType] 中,检测文件类型,并赋值给[FileItemBeanImpl.fileType]属性 13 | * =================================================================================================================== 14 | * This class is used to register your own file type detection methods. You need to follow the following steps: 15 | * 1. implement your own file type [FileType], which is the [FileType.verify] method of the [FileType]. 16 | * 2. Construct a subclass of this class and, in [AbstractFileDetector.fillFileType], detect the file type and assign it to [FileItemBeanImpl.fileType] property. 17 | * 18 | */ 19 | abstract class AbstractFileDetector { 20 | /** 21 | * 自定义文件类型识别方法,传入 @param itemBeanImpl 条目数据对象, 22 | * 由实现者来实现文件类型的甄别,返回填充了 fileType 的方法 23 | */ 24 | abstract fun fillFileType(itemBeanImpl: FileItemBeanImpl): FileItemBeanImpl 25 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/AbstractFileFilter.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import me.rosuh.filepicker.bean.FileItemBeanImpl 4 | 5 | /** 6 | * 7 | * @author rosu 8 | * @date 2018/11/23 9 | */ 10 | abstract class AbstractFileFilter { 11 | /** 12 | * 自定义过滤器接口,此接口在生成列表数据的时候被调用 13 | * 返回一个经过处理的列表数据,进而生成列表视图 14 | * @param listData ArrayList 未处理的文件列表数据 15 | * @return ArrayList 16 | */ 17 | abstract fun doFilter(listData: ArrayList): ArrayList 18 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/AbstractFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import me.rosuh.filepicker.bean.FileItemBeanImpl 4 | 5 | /** 6 | * 7 | * @author rosu 8 | * @date 2018/11/27 9 | */ 10 | @Deprecated( 11 | "Because the class name was confused.", 12 | ReplaceWith("AbstractFileDetector", "me.rosuh.filepicker.config") 13 | ) 14 | abstract class AbstractFileType { 15 | /** 16 | * 自定义文件类型识别方法,传入 @param itemBeanImpl 条目数据对象, 17 | * 由实现者来实现文件类型的甄别,返回填充了 fileType 的方法 18 | */ 19 | abstract fun fillFileType(itemBeanImpl: FileItemBeanImpl): FileItemBeanImpl 20 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/DefaultFileDetector.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import me.rosuh.filepicker.bean.FileItemBeanImpl 4 | import me.rosuh.filepicker.filetype.* 5 | 6 | /** 7 | * 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class DefaultFileDetector : AbstractFileDetector() { 12 | 13 | var enableCustomTypes: Boolean = false 14 | private set 15 | 16 | private val allDefaultFileType: ArrayList by lazy { 17 | ArrayList() 18 | } 19 | 20 | fun registerDefaultTypes() { 21 | with(allDefaultFileType) { 22 | clear() 23 | add(AudioFileType()) 24 | add(RasterImageFileType()) 25 | add(CompressedFileType()) 26 | add(DataBaseFileType()) 27 | add(ExecutableFileType()) 28 | add(FontFileType()) 29 | add(PageLayoutFileType()) 30 | add(TextFileType()) 31 | add(VideoFileType()) 32 | add(WebFileType()) 33 | } 34 | enableCustomTypes = false 35 | } 36 | 37 | /** 38 | * @author rosuh@qq.com 39 | * @date 2020/9/16 40 | * save user's custom file types 41 | */ 42 | fun registerCustomTypes(customFileTypes: ArrayList) { 43 | allDefaultFileType.clear() 44 | allDefaultFileType.addAll(customFileTypes) 45 | enableCustomTypes = true 46 | } 47 | 48 | fun clear(){ 49 | allDefaultFileType.clear() 50 | enableCustomTypes = false 51 | } 52 | 53 | override fun fillFileType(itemBeanImpl: FileItemBeanImpl): FileItemBeanImpl { 54 | for (type in allDefaultFileType) { 55 | if (type.verify(itemBeanImpl.fileName)) { 56 | itemBeanImpl.fileType = type 57 | break 58 | } 59 | } 60 | return itemBeanImpl 61 | } 62 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/FileItemOnClickListener.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import android.view.View 4 | import me.rosuh.filepicker.adapter.FileListAdapter 5 | 6 | /** 7 | * 8 | * @author rosu 9 | * @date 2018/11/26 10 | */ 11 | @Deprecated("It's not flexible enough.", replaceWith = ReplaceWith("me.rosuh.filepicker.config.ItemClickListener")) 12 | interface FileItemOnClickListener { 13 | 14 | fun onItemClick( 15 | itemAdapter: FileListAdapter, 16 | itemView: View, 17 | position: Int 18 | ) 19 | 20 | fun onItemChildClick( 21 | itemAdapter: FileListAdapter, 22 | itemView: View, 23 | position: Int 24 | ) 25 | 26 | fun onItemLongClick( 27 | itemAdapter: FileListAdapter, 28 | itemView: View, 29 | position: Int 30 | ) 31 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/FilePickerConfig.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import android.content.Intent 4 | import android.util.Log 5 | import androidx.annotation.NonNull 6 | import androidx.annotation.StringRes 7 | import me.rosuh.filepicker.FilePickerActivity 8 | import me.rosuh.filepicker.R 9 | import me.rosuh.filepicker.engine.ImageEngine 10 | import me.rosuh.filepicker.filetype.FileType 11 | import me.rosuh.filepicker.utils.DefaultLogger 12 | import java.io.File 13 | import java.util.concurrent.* 14 | 15 | /** 16 | * 17 | * @author rosu 18 | * @date 2018/11/27 19 | */ 20 | class FilePickerConfig(private val pickerManager: FilePickerManager) { 21 | 22 | var isAutoFilter: Boolean = false 23 | 24 | private val customFileTypes: ArrayList by lazy { 25 | ArrayList(2) 26 | } 27 | 28 | private val contextRes = pickerManager.contextRef!!.get()!!.resources 29 | 30 | /** 31 | * 是否显示隐藏文件,默认隐藏 32 | * 以符号 . 开头的文件或文件夹视为隐藏 33 | */ 34 | var isShowHiddenFiles = false 35 | private set 36 | 37 | /** 38 | * 是否显示选中框,默认显示 39 | */ 40 | var isShowingCheckBox = true 41 | private set 42 | 43 | /** 44 | * 在选中时是否忽略文件夹 45 | */ 46 | var isSkipDir = true 47 | private set 48 | 49 | /** 50 | * 是否是单选 51 | * 如果是单选,则隐藏顶部【全选/取消全选按钮】 52 | */ 53 | var singleChoice = false 54 | private set 55 | 56 | /** 57 | * 最大可被选中数量 58 | */ 59 | var maxSelectable = Int.MAX_VALUE 60 | private set 61 | 62 | internal val defaultStorageName = contextRes.getString(R.string.file_picker_tv_sd_card) 63 | 64 | /** 65 | * 存储类型 66 | */ 67 | var mediaStorageName = defaultStorageName 68 | private set 69 | 70 | /** 71 | * 自定义存储类型,根据此返回根目录 72 | */ 73 | @get:StorageMediaType 74 | @set:StorageMediaType 75 | var mediaStorageType: String = STORAGE_EXTERNAL_STORAGE 76 | private set 77 | 78 | /** 79 | * 自定义根目录路径,需要先设置 [mediaStorageType] 为 [STORAGE_CUSTOM_ROOT_PATH] 80 | */ 81 | var customRootPath: String = "" 82 | private set 83 | 84 | internal var customRootPathFile: File? = null 85 | private set 86 | 87 | /** 88 | * 自定义过滤器 89 | */ 90 | var selfFilter: AbstractFileFilter? = null 91 | private set 92 | 93 | /** 94 | * 自定文件类型甄别器和默认类型甄别器 95 | */ 96 | @Deprecated( 97 | "Use 'register' function instead.", 98 | replaceWith = ReplaceWith("me.rosuh.filepicker.config.FilePickerConfig.registerFileType()"), 99 | level = DeprecationLevel.WARNING 100 | ) 101 | var customDetector: AbstractFileDetector? = null 102 | 103 | val defaultFileDetector: DefaultFileDetector by lazy { DefaultFileDetector().also { it.registerDefaultTypes() } } 104 | 105 | /** 106 | * 点击操作接口,采用默认实现 107 | */ 108 | @Deprecated("Check the itemClickListener") 109 | var fileItemOnClickListener: FileItemOnClickListener? = null 110 | private set 111 | 112 | /** 113 | * 点击操作接口 114 | */ 115 | var itemClickListener: ItemClickListener? = null 116 | private set 117 | 118 | /** 119 | * 主题 120 | */ 121 | var themeId: Int = R.style.FilePickerThemeRail 122 | private set 123 | 124 | /** 125 | * 全选文字,取消全选文字,返回文字,已选择文字,确认按钮,选择限制提示语,空列表提示 126 | */ 127 | var selectAllText: String = contextRes.getString(R.string.file_picker_tv_select_all) 128 | private set 129 | var deSelectAllText: String = contextRes.getString(R.string.file_picker_tv_deselect_all) 130 | private set 131 | 132 | @StringRes 133 | var hadSelectedText: Int = R.string.file_picker_selected_count 134 | private set 135 | var confirmText: String = contextRes.getString(R.string.file_picker_tv_select_done) 136 | private set 137 | 138 | @StringRes 139 | var maxSelectCountTips: Int = R.string.max_select_count_tips 140 | private set 141 | 142 | var emptyListTips: String = contextRes.getString(R.string.empty_list_tips_file_picker) 143 | private set 144 | 145 | /** 146 | * 允许使用你项目中的线程池 147 | * Allow the use of thread pools in your project 148 | */ 149 | internal var threadPool: ExecutorService? = null 150 | 151 | /** 152 | * 自定义线程池不会默认关闭,如果你需要在结束文件选择时关闭,请传 true 153 | * The custom thread pool will not be closed by default, 154 | * if you need to close when the file selection is finished, please pass true 155 | */ 156 | internal var threadPoolAutoShutDown: Boolean = false 157 | 158 | /** 159 | * 如果您的 Glide 版本低于 4.9, 请使用自定义的 [ImageEngine] 160 | */ 161 | 162 | /** 163 | * 如果您的 Glide 版本低于 4.9, 请使用自定义的 [ImageEngine] 164 | */ 165 | var customImageEngine: ImageEngine? = null 166 | private set 167 | 168 | var logger: ILog = DefaultLogger 169 | private set 170 | 171 | fun showHiddenFiles(isShow: Boolean): FilePickerConfig { 172 | isShowHiddenFiles = isShow 173 | return this 174 | } 175 | 176 | fun showCheckBox(isShow: Boolean): FilePickerConfig { 177 | isShowingCheckBox = isShow 178 | return this 179 | } 180 | 181 | fun skipDirWhenSelect(isSkip: Boolean): FilePickerConfig { 182 | isSkipDir = isSkip 183 | return this 184 | } 185 | 186 | fun maxSelectable(max: Int): FilePickerConfig { 187 | maxSelectable = if (max < 0) Int.MAX_VALUE else max 188 | return this 189 | } 190 | 191 | @JvmOverloads 192 | fun storageType( 193 | volumeName: String = "", 194 | @StorageMediaType storageMediaType: String 195 | ): FilePickerConfig { 196 | mediaStorageName = volumeName 197 | mediaStorageType = storageMediaType 198 | return this 199 | } 200 | 201 | fun setCustomRootPath(path: String): FilePickerConfig { 202 | customRootPath = path 203 | path.takeIf { 204 | it.isNotBlank() 205 | }?.let { 206 | File(it) 207 | }?.takeIf { 208 | it.exists() 209 | }?.let { 210 | customRootPathFile = it 211 | } 212 | return this 213 | } 214 | 215 | fun filter(fileFilter: AbstractFileFilter): FilePickerConfig { 216 | selfFilter = fileFilter 217 | return this 218 | } 219 | 220 | /** 221 | * @author rosuh@qq.com 222 | * @date 2020/9/15 223 | * custom file type had upgrade to [registerFileType], which can simplify your usage. 224 | * 实现 [AbstractFileDetector] 以自定义您自己的文件类型检测器 225 | * Custom your file detector by implementing [AbstractFileDetector] 226 | */ 227 | @Deprecated( 228 | "Use 'register' function instead.", 229 | ReplaceWith("registerFileType(types)"), 230 | level = DeprecationLevel.WARNING 231 | ) 232 | fun customDetector(detector: AbstractFileDetector): FilePickerConfig { 233 | this.customDetector = detector 234 | return this 235 | } 236 | 237 | /** 238 | * This method would be removed in 0.8.0. 239 | * Try to using [ItemClickListener] which the below one. 240 | * @author hi@rosuh.me 241 | */ 242 | @Deprecated( 243 | "It's not flexible enough.", 244 | replaceWith = ReplaceWith("me.rosuh.filepicker.config.FilePickerConfig.setItemClickListener") 245 | ) 246 | fun setItemClickListener(fileItemOnClickListener: FileItemOnClickListener): FilePickerConfig { 247 | this.fileItemOnClickListener = fileItemOnClickListener 248 | return this 249 | } 250 | 251 | /** 252 | * Setting item click listener which can intercept click event. 253 | * @author hi@rosuh.me 254 | * @since 0.7.2 255 | */ 256 | fun setItemClickListener( 257 | itemClickListener: ItemClickListener 258 | ): FilePickerConfig { 259 | this.itemClickListener = itemClickListener 260 | return this 261 | } 262 | 263 | fun setTheme(themeId: Int): FilePickerConfig { 264 | this.themeId = themeId 265 | return this 266 | } 267 | 268 | /** 269 | * 是否启用单选模式 270 | */ 271 | fun enableSingleChoice(): FilePickerConfig { 272 | this.singleChoice = true 273 | return this 274 | } 275 | 276 | /** 277 | * 设置界面的字符串,包括: 278 | * 选中全部[selectAllString] 279 | * 取消选中[unSelectAllString] 280 | * 已选择[hadSelectedStrRes] 281 | * 确认[confirmText] 282 | * 多选限制提示:“您只能选择 1 个条目”[maxSelectCountTipsStrRes] 283 | * 空试图体视:“空空如也”[emptyListTips] 284 | * 注意: 285 | * [hadSelectedStrRes] 和 [maxSelectCountTipsStrRes] 是 String format 限制的字符串,你需要传入 [R.string.file_picker_selected_count] 类似的 286 | * 中的 id,并且包含一个可传入 Int 类型参数的占位符 287 | *---------------------------------------------------------------------------------------------- 288 | * Set the string of the interface, including: 289 | * Select all [selectAllString] 290 | * Uncheck [unSelectAllString] 291 | * Selected [hadSelectedStrRes] 292 | * Confirm [confirmText] 293 | * Multiple selection limit prompt: "You can only select 1 item" [maxSelectCountTipsStrRes] 294 | * Empty tries to look at the stereo: "empty as well" [emptyListTips] 295 | * Note: 296 | * [hadSelectedStrRes] and [maxSelectCountTipsStrRes] are strings of String format restrictions, you need to pass some string like [R.string.file_picker_selected_count] 297 | * The id in * and contains a placeholder for passing in an Int type parameter 298 | */ 299 | fun setText( 300 | @NonNull selectAllString: String = contextRes.getString(R.string.file_picker_tv_select_all), 301 | @NonNull unSelectAllString: String = contextRes.getString(R.string.file_picker_tv_deselect_all), 302 | @NonNull @StringRes hadSelectedStrRes: Int = R.string.file_picker_selected_count, 303 | @NonNull confirmText: String = contextRes.getString(R.string.file_picker_tv_select_done), 304 | @NonNull @StringRes maxSelectCountTipsStrRes: Int = R.string.max_select_count_tips, 305 | @NonNull emptyListTips: String = contextRes.getString(R.string.empty_list_tips_file_picker) 306 | ): FilePickerConfig { 307 | this.selectAllText = selectAllString 308 | this.deSelectAllText = unSelectAllString 309 | this.hadSelectedText = hadSelectedStrRes 310 | this.confirmText = confirmText 311 | this.maxSelectCountTips = maxSelectCountTipsStrRes 312 | this.emptyListTips = emptyListTips 313 | return this 314 | } 315 | 316 | fun imageEngine(ie: ImageEngine): FilePickerConfig { 317 | this.customImageEngine = ie 318 | return this 319 | } 320 | 321 | /** 322 | * @author rosuh@qq.com 323 | * @date 2020/9/15 324 | * 用于注册你自定义的文件类型。 325 | * 库将自动调用你的自定义类型里的[FileType.verify]来识别文件。如果识别成功,就会自动填充到 [me.rosuh.filepicker.bean.FileItemBeanImpl.fileType] 中 326 | * 如果[autoFilter]为 true,那么库将自动过滤掉不符合你自定义类型的文件。不会在结果中显示出来。 327 | * 如果为 false,那么就只是检测类型。不会对结果列表做修改 328 | * 你不需要再调用[fileType]方法,否则将默认使用[fileType] 329 | * --- 330 | * Pass your custom [FileType] instances list and all done! This lib would auto detect file type 331 | * by using [FileType.verify]. 332 | * If [autoFilter] is true, this lib will filter result by using your custom file types. 333 | * If [autoFilter] is true, the library will automatically filter out files that do not meet your custom type. 334 | * Will not show up in the results. * If it is false, then only the detection type. No changes to the result list 335 | * You don't need to call [fileType] again ! 336 | */ 337 | fun registerFileType(types: List, autoFilter: Boolean = true): FilePickerConfig { 338 | this.customFileTypes.addAll(types) 339 | this.defaultFileDetector.registerCustomTypes(customFileTypes) 340 | this.isAutoFilter = autoFilter 341 | return this 342 | } 343 | 344 | /** 345 | * 允许使用你项目中的线程池, 自定义线程池不会默认关闭,如果你需要在结束文件选择时关闭,请传 true 346 | * Allow the use of thread pools in your project 347 | * The custom thread pool will not be closed by default, 348 | * if you need to close when the file selection is finished, please pass true 349 | */ 350 | fun threadPool(threadPool: ExecutorService, autoShutdown: Boolean): FilePickerConfig { 351 | this.threadPool = threadPool 352 | this.threadPoolAutoShutDown = autoShutdown 353 | return this 354 | } 355 | 356 | /** 357 | * 允许使用你项目中的 Logger 358 | * Allow the use of logger in your project 359 | */ 360 | fun log(logger: ILog): FilePickerConfig { 361 | this.logger = logger 362 | return this 363 | } 364 | 365 | fun forResult(requestCode: Int) { 366 | val activity = pickerManager.contextRef?.get() 367 | val fragment = pickerManager.fragmentRef?.get() 368 | 369 | val intent = Intent(activity, FilePickerActivity::class.java) 370 | if (fragment == null) { 371 | activity?.startActivityForResult(intent, requestCode) 372 | } else { 373 | fragment.startActivityForResult(intent, requestCode) 374 | } 375 | } 376 | 377 | fun resetCustomFile() { 378 | this.customRootPathFile = null 379 | } 380 | 381 | fun clear() { 382 | this.customFileTypes.clear() 383 | this.customImageEngine = null 384 | this.fileItemOnClickListener = null 385 | this.selfFilter = null 386 | this.customDetector = null 387 | this.defaultFileDetector.clear() 388 | resetCustomFile() 389 | } 390 | 391 | companion object { 392 | /** 393 | * 手机内部的外置存储,也就是内置 SD 卡 394 | */ 395 | @get:StorageMediaType 396 | const val STORAGE_EXTERNAL_STORAGE = "STORAGE_EXTERNAL_STORAGE" 397 | 398 | /** 399 | * TODO 可拔插的 SD 卡 400 | */ 401 | @get:StorageMediaType 402 | const val STORAGE_UUID_SD_CARD = "STORAGE_UUID_SD_CARD" 403 | 404 | /** 405 | * TODO 可拔插 U 盘 406 | */ 407 | @get:StorageMediaType 408 | const val STORAGE_UUID_USB_DRIVE = "STORAGE_UUID_USB_DRIVE" 409 | 410 | /** 411 | * 自定义路径 412 | */ 413 | @get:StorageMediaType 414 | const val STORAGE_CUSTOM_ROOT_PATH = "STORAGE_CUSTOM_ROOT_PATH" 415 | 416 | /** 417 | * 存储类型,目前仅支持 [STORAGE_EXTERNAL_STORAGE] 和 [STORAGE_CUSTOM_ROOT_PATH] 418 | */ 419 | @Retention(AnnotationRetention.SOURCE) 420 | annotation class StorageMediaType 421 | } 422 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/FilePickerManager.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import android.app.Activity 4 | import androidx.fragment.app.Fragment 5 | import me.rosuh.filepicker.engine.ImageLoadController 6 | import java.lang.ref.WeakReference 7 | 8 | /** 9 | * 10 | * @author rosu 11 | * @date 2018/11/22 12 | */ 13 | object FilePickerManager { 14 | /** 15 | * 启动 Launcher Activity 所需的 request code 16 | */ 17 | const val REQUEST_CODE = 10401 18 | 19 | internal var contextRef: WeakReference? = null 20 | 21 | internal var fragmentRef: WeakReference? = null 22 | 23 | internal lateinit var config: FilePickerConfig 24 | 25 | private var dataList: MutableList = ArrayList() 26 | 27 | @JvmStatic 28 | fun from(activity: Activity): FilePickerConfig { 29 | reset() 30 | this.contextRef = WeakReference(activity) 31 | config = FilePickerConfig(this) 32 | return config 33 | } 34 | 35 | /** 36 | * 不能使用 fragmentRef.getContext(),因为无法保证外部的代码环境 37 | */ 38 | @JvmStatic 39 | fun from(fragment: Fragment): FilePickerConfig { 40 | reset() 41 | this.fragmentRef = WeakReference(fragment) 42 | this.contextRef = WeakReference(fragment.activity) 43 | config = FilePickerConfig(this) 44 | return config 45 | } 46 | 47 | /** 48 | * 保存数据@param list List到本类中 49 | */ 50 | internal fun saveData(list: MutableList) { 51 | dataList = list 52 | } 53 | 54 | /** 55 | * 供调用者获取结果 56 | * @return List 57 | */ 58 | @JvmOverloads 59 | @JvmStatic 60 | fun obtainData(release: Boolean = false): MutableList { 61 | val result = ArrayList(dataList) 62 | if (release) { 63 | release() 64 | } 65 | return result 66 | } 67 | 68 | private fun reset() { 69 | contextRef?.clear() 70 | fragmentRef?.clear() 71 | dataList.clear() 72 | ImageLoadController.reset() 73 | } 74 | 75 | /** 76 | * 释放资源与重置属性 77 | * Release resources and reset attributes 78 | */ 79 | @JvmStatic 80 | fun release() { 81 | reset() 82 | if (this::config.isInitialized) { 83 | config.clear() 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/ILog.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | interface ILog { 4 | fun d(tag: String, msg: String) 5 | fun e(tag: String, msg: String, tr: Throwable? = null) 6 | fun i(tag: String, msg: String) 7 | fun v(tag: String, msg: String) 8 | fun w(tag: String, msg: String) 9 | fun wtf(tag: String, msg: String) 10 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/ItemClickListener.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import android.view.View 4 | import me.rosuh.filepicker.adapter.FileListAdapter 5 | /** 6 | * If the user return true means the event has been consumed. 7 | * @author rosuh@qq.com 8 | * @date 2021/8/23 9 | */ 10 | interface ItemClickListener { 11 | 12 | fun onItemClick( 13 | itemAdapter: FileListAdapter, 14 | itemView: View, 15 | position: Int 16 | ): Boolean 17 | 18 | fun onItemChildClick( 19 | itemAdapter: FileListAdapter, 20 | itemView: View, 21 | position: Int 22 | ): Boolean 23 | 24 | fun onItemLongClick( 25 | itemAdapter: FileListAdapter, 26 | itemView: View, 27 | position: Int 28 | ): Boolean 29 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/config/SimpleItemClickListener.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.config 2 | 3 | import android.view.View 4 | import me.rosuh.filepicker.adapter.FileListAdapter 5 | 6 | open class SimpleItemClickListener : FileItemOnClickListener { 7 | override fun onItemClick(itemAdapter: FileListAdapter, itemView: View, position: Int) {} 8 | 9 | override fun onItemChildClick(itemAdapter: FileListAdapter, itemView: View, position: Int) {} 10 | 11 | override fun onItemLongClick(itemAdapter: FileListAdapter, itemView: View, position: Int) {} 12 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/engine/GlideEngine.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.engine 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.media.ThumbnailUtils 6 | import android.widget.ImageView 7 | import com.bumptech.glide.Glide 8 | import com.bumptech.glide.load.DataSource 9 | import com.bumptech.glide.load.engine.GlideException 10 | import com.bumptech.glide.request.RequestListener 11 | import com.bumptech.glide.request.target.Target 12 | import com.bumptech.glide.request.transition.DrawableCrossFadeTransition 13 | import me.rosuh.filepicker.R 14 | import me.rosuh.filepicker.utils.dp 15 | 16 | /** 17 | * @author rosu 18 | * @date 2020-04-15 19 | * An [ImageEngine] implementation by using Glide 20 | */ 21 | class GlideEngine : ImageEngine { 22 | override fun loadImage( 23 | context: Context?, 24 | imageView: ImageView?, 25 | url: String?, 26 | placeholder: Int 27 | ) { 28 | if (context == null || imageView == null) { 29 | return 30 | } 31 | Glide.with(context) 32 | .asBitmap() 33 | .load(url) 34 | .override(imageView.width.coerceAtLeast(40.dp)) 35 | .error(R.drawable.ic_unknown_file_picker) 36 | .into(imageView) 37 | } 38 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/engine/ImageEngine.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.engine 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import android.widget.ImageView 6 | 7 | /** 8 | * @author rosu 9 | * @date 2020-04-15 10 | * 描述图片加载器的接口,以便 Glide、Picasso 或其他加载器使用 11 | * Describe the interface of the picture loader for use by Glide, Picasso or other loaders 12 | */ 13 | interface ImageEngine { 14 | /** 15 | * 调用此接口加载图片,一般情况下[url]参数表示图片的本地路径 path,通过[Uri.parse]得到的值。通常是 file:/// 开头 16 | * 如果加载失败,将使用[placeholder] 17 | * Call this interface to load the picture. Generally, the [url] parameter indicates the local 18 | * path of the picture, and the value obtained through [Uri.parse]. 19 | * Usually starts with file:/// 20 | * If loading fails, [placeholder] will be used 21 | */ 22 | fun loadImage( 23 | context: Context?, 24 | imageView: ImageView?, 25 | url: String?, 26 | placeholder: Int 27 | ) 28 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/engine/ImageLoadController.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.engine 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import android.widget.ImageView 6 | import me.rosuh.filepicker.R 7 | import me.rosuh.filepicker.config.FilePickerManager 8 | 9 | /** 10 | * @author rosu 11 | * @date 2020-04-15 12 | * 一个全局的图片加载控制类,包含了判断是否存在以及存在哪种图片加载引擎。 13 | * A global image loading controller, including determining whether there is and what kind of 14 | * image loading engine exists. 15 | */ 16 | object ImageLoadController { 17 | private val enableGlide by lazy { 18 | try { 19 | Class.forName("com.bumptech.glide.Glide") 20 | true 21 | } catch (e: ClassNotFoundException) { 22 | false 23 | } catch (e: ExceptionInInitializerError) { 24 | false 25 | } 26 | } 27 | 28 | private val enablePicasso by lazy { 29 | try { 30 | Class.forName("com.squareup.picasso.Picasso") 31 | true 32 | } catch (e: ClassNotFoundException) { 33 | false 34 | } catch (e: ExceptionInInitializerError) { 35 | false 36 | } 37 | } 38 | 39 | private var engine: ImageEngine? = null 40 | 41 | /** 42 | * 加载图片,如果没有不存在图片加载引擎,那么将使用默认 icon 43 | * Load images, if there is no image loading engine, then the default icon is used 44 | */ 45 | fun load( 46 | context: Context, 47 | iv: ImageView, 48 | url: String, 49 | placeholder: Int? = R.drawable.ic_unknown_file_picker 50 | ) { 51 | if (engine == null && !initEngine()) { 52 | iv.setImageResource(placeholder ?: R.drawable.ic_unknown_file_picker) 53 | return 54 | } 55 | try { 56 | engine?.loadImage(context, iv, url, placeholder ?: R.drawable.ic_unknown_file_picker) 57 | } catch (e: NoSuchMethodError) { 58 | Log.d( 59 | "ImageLoadController", """ 60 | AndroidFilePicker throw NoSuchMethodError which means current Glide version was not supported. 61 | We recommend using 4.9+ or you should implements your own ImageEngine. 62 | Ref:https://github.com/rosuH/AndroidFilePicker/issues/76 63 | """.trimIndent() 64 | ) 65 | iv.setImageResource(placeholder ?: R.drawable.ic_unknown_file_picker) 66 | } 67 | } 68 | 69 | /** 70 | * 每次配置更新的时候,都需要重新初始化图片加载器 71 | * Every time the configuration is updated, we need to re-initialize the image loader 72 | */ 73 | private fun initEngine(): Boolean { 74 | engine = when { 75 | FilePickerManager.config.customImageEngine != null -> { 76 | FilePickerManager.config.customImageEngine 77 | } 78 | enableGlide -> { 79 | GlideEngine() 80 | } 81 | enablePicasso -> { 82 | PicassoEngine() 83 | } 84 | else -> { 85 | null 86 | } 87 | } 88 | return engine != null 89 | } 90 | 91 | fun reset() { 92 | engine = null 93 | } 94 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/engine/PicassoEngine.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.engine 2 | 3 | import android.content.Context 4 | import android.widget.ImageView 5 | import com.squareup.picasso.Picasso 6 | import java.io.File 7 | 8 | /** 9 | * @author rosu 10 | * @date 2020-04-15 11 | * An [ImageEngine] implementation by using Picasso 12 | */ 13 | class PicassoEngine : ImageEngine { 14 | override fun loadImage( 15 | context: Context?, 16 | imageView: ImageView?, 17 | url: String?, 18 | placeholder: Int 19 | ) { 20 | if (url?.startsWith("http") == true) { 21 | Picasso.with(context) 22 | .load(url) 23 | .placeholder(placeholder) 24 | .into(imageView) 25 | } else { 26 | Picasso.with(context) 27 | .load(File(url)) 28 | .placeholder(placeholder) 29 | .into(imageView) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/AudioFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 音频文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class AudioFileType : FileType { 12 | override val fileType: String 13 | get() = "Audio" 14 | override val fileIconResId: Int 15 | get() = R.drawable.ic_music_file_picker 16 | 17 | override fun verify(fileName: String): Boolean { 18 | /** 19 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 20 | * 比如 文件名仅为:example_png 21 | */ 22 | val isHasSuffix = fileName.contains(".") 23 | if (!isHasSuffix) { 24 | // 如果没有 . 符号,即是没有文件后缀 25 | return false 26 | } 27 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 28 | return when (suffix) { 29 | "aif", "iff", "m3u", "m4a", "mid", "mp3", "mpa", "wav", "wma", "ogg", "flac", "ape", "alac" -> { 30 | true 31 | } 32 | else -> { 33 | false 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/CompressedFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 压缩文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class CompressedFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "Compressed" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_compressed_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix) { 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix) { 30 | "zip", "rar", "arj", "tar.gz", "tgz", "gz", "iso", "tbz", "tbz2", "7z" -> { 31 | true 32 | } 33 | else -> { 34 | false 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/DataBaseFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 数据库 文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class DataBaseFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "DataBase" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_unknown_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix) { 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix) { 30 | "accdb", "db", "dbf", "mdb", "pdb", "sql" -> { 31 | true 32 | } 33 | else -> { 34 | false 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/DataFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 数据文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class DataFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "DataFile" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_unknown_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix) { 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix) { 30 | "csv", "dat", "ged", "key", "keychain", "pps", 31 | "ppt", "pptx", "sdf", "tar", "tax2016", "tax2017", 32 | "vcf", "xml" -> { 33 | true 34 | } 35 | else -> { 36 | false 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/ExecutableFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 可执行 文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class ExecutableFileType : FileType { 12 | override val fileType: String 13 | get() = "Executable" 14 | override val fileIconResId: Int 15 | get() = R.drawable.ic_exec_file_picker 16 | 17 | override fun verify(fileName: String): Boolean { 18 | /** 19 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 20 | * 比如 文件名仅为:example_png 21 | */ 22 | val isHasSuffix = fileName.contains(".") 23 | if (!isHasSuffix) { 24 | // 如果没有 . 符号,即是没有文件后缀 25 | return false 26 | } 27 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 28 | return when (suffix) { 29 | "apk", "app", "bat", "cgi", "com", "exe", "gadget", "jar", "wsf" -> { 30 | true 31 | } 32 | else -> { 33 | false 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/FileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | /** 4 | * 5 | * @author rosu 6 | * @date 2018/11/27 7 | */ 8 | interface FileType { 9 | /** 10 | * 文件类型 11 | */ 12 | val fileType: String 13 | 14 | val fileIconResId: Int 15 | 16 | /** 17 | * 传入文件路径,判断是否为该类型 18 | * @param fileName String 19 | * @return Boolean 20 | */ 21 | fun verify(fileName: String): Boolean 22 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/FontFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 字体 文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class FontFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "Font" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_unknown_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix) { 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix) { 30 | "fnt", "fon", "otf", "ttf" -> { 31 | true 32 | } 33 | else -> { 34 | false 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/PageLayoutFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * PageLayout 文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class PageLayoutFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "PageLayout" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_pdf_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix){ 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix){ 30 | "idnn", "pct", "pdf" -> { 31 | true 32 | } 33 | else -> { 34 | false 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/RasterImageFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 图片文件类型 7 | * @author rosu 8 | * @date 2018/11/27 9 | */ 10 | class RasterImageFileType : FileType { 11 | 12 | override val fileType: String 13 | get() = "Image" 14 | override val fileIconResId: Int 15 | get() = R.drawable.ic_image_file_picker 16 | 17 | override fun verify(fileName: String): Boolean { 18 | /** 19 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 20 | * 比如 文件名仅为:example_png 21 | */ 22 | val isHasSuffix = fileName.contains(".") 23 | if (!isHasSuffix) { 24 | // 如果没有 . 符号,即是没有文件后缀 25 | return false 26 | } 27 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 28 | return when (suffix) { 29 | "jpeg", "jpg", "bmp", "dds", "gif", "png", "psd", "pspimage", "tga", "thm", "tif", "tiff", "yuv" -> { 30 | true 31 | } 32 | else -> { 33 | false 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/TextFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 文本文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class TextFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "Text" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_unknown_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix) { 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix) { 30 | "doc", "docx", "log", "txt", "msg", "odt", "pages", "rtf", "tex", "wpd", "wps" -> { 31 | true 32 | } 33 | else -> { 34 | false 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/VideoFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * 视频文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class VideoFileType : FileType { 12 | 13 | override val fileType: String 14 | get() = "Video" 15 | override val fileIconResId: Int 16 | get() = R.drawable.ic_video_file_picker 17 | 18 | override fun verify(fileName: String): Boolean { 19 | /** 20 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 21 | * 比如 文件名仅为:example_png 22 | */ 23 | val isHasSuffix = fileName.contains(".") 24 | if (!isHasSuffix) { 25 | // 如果没有 . 符号,即是没有文件后缀 26 | return false 27 | } 28 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 29 | return when (suffix) { 30 | "mp4", "mkv", "mov", "mpg", "mpeg", "3gp", 31 | "3gpp", "3g2", "3gpp2", "webm", "ts", "avi", 32 | "flv", "swf", "wmv", "vob", "m4v" -> { 33 | true 34 | } 35 | else -> { 36 | false 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/filetype/WebFileType.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.filetype 2 | 3 | import me.rosuh.filepicker.R 4 | 5 | /** 6 | * 7 | * Web 文件类型 8 | * @author rosu 9 | * @date 2018/11/27 10 | */ 11 | class WebFileType : FileType { 12 | override val fileType: String 13 | get() = "Web" 14 | override val fileIconResId: Int 15 | get() = R.drawable.ic_html_file_picker 16 | 17 | override fun verify(fileName: String): Boolean { 18 | /** 19 | * 使用 endWith 是不可靠的,因为文件名有可能是以格式结尾,但是没有 . 符号 20 | * 比如 文件名仅为:example_png 21 | */ 22 | val isHasSuffix = fileName.contains(".") 23 | if (!isHasSuffix) { 24 | // 如果没有 . 符号,即是没有文件后缀 25 | return false 26 | } 27 | val suffix = fileName.substring(fileName.lastIndexOf(".") + 1) 28 | return when (suffix) { 29 | "asp", "aspx", "cer", "cfm", "csr", "css", 30 | "dcr", "html", "htm", "js", "jsp", "php", 31 | "rss", "xhtml" -> { 32 | true 33 | } 34 | else -> { 35 | false 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/utils/DefaultLogger.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.utils 2 | 3 | import android.util.Log 4 | import me.rosuh.filepicker.config.ILog 5 | 6 | internal object DefaultLogger : ILog { 7 | override fun d(tag: String, msg: String) { 8 | Log.d(tag, msg) 9 | } 10 | 11 | override fun e(tag: String, msg: String, tr: Throwable?) { 12 | Log.e(tag, msg, tr) 13 | } 14 | 15 | override fun i(tag: String, msg: String) { 16 | Log.i(tag, msg) 17 | } 18 | 19 | override fun v(tag: String, msg: String) { 20 | Log.v(tag, msg) 21 | } 22 | 23 | override fun w(tag: String, msg: String) { 24 | Log.w(tag, msg) 25 | } 26 | 27 | override fun wtf(tag: String, msg: String) { 28 | Log.wtf(tag, msg) 29 | } 30 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/utils/FileListAdapterListener.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.utils 2 | 3 | import android.view.View 4 | 5 | interface FileListAdapterListener { 6 | fun onCheckSizeChanged(count: Int) 7 | } 8 | 9 | private typealias OnCheck = (isCheck: Boolean, view: View, pos: Int) -> Unit 10 | private typealias OnCheckSizeChanged = (count: Int) -> Unit 11 | 12 | class FileListAdapterListenerBuilder : FileListAdapterListener { 13 | 14 | private var onCheck: OnCheck? = null 15 | private var onCheckSizeChanged: OnCheckSizeChanged? = null 16 | 17 | override fun onCheckSizeChanged(count: Int) { 18 | this.onCheckSizeChanged?.invoke(count) 19 | } 20 | 21 | fun onCheckSizeChanged(onCheckSizeChanged: OnCheckSizeChanged) { 22 | this.onCheckSizeChanged = onCheckSizeChanged 23 | } 24 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.utils 2 | 3 | import android.content.Context 4 | import android.os.Environment 5 | import me.rosuh.filepicker.bean.FileItemBeanImpl 6 | import me.rosuh.filepicker.bean.FileNavBeanImpl 7 | import me.rosuh.filepicker.config.FilePickerConfig.Companion.STORAGE_CUSTOM_ROOT_PATH 8 | import me.rosuh.filepicker.config.FilePickerConfig.Companion.STORAGE_EXTERNAL_STORAGE 9 | import me.rosuh.filepicker.config.FilePickerManager.config 10 | import java.io.File 11 | import java.util.* 12 | import kotlin.collections.ArrayList 13 | 14 | /** 15 | * 16 | * @author rosu 17 | * @date 2018/11/22 18 | */ 19 | class FileUtils { 20 | 21 | companion object { 22 | 23 | /** 24 | * 根据配置参数获取根目录文件 25 | * @return File 26 | */ 27 | fun getRootFile(): File { 28 | return when (config.mediaStorageType) { 29 | STORAGE_EXTERNAL_STORAGE -> { 30 | File(Environment.getExternalStorageDirectory().absoluteFile.toURI()) 31 | } 32 | else -> { 33 | File(Environment.getExternalStorageDirectory().absoluteFile.toURI()) 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * 获取给定文件对象[rootFile]下的所有文件,生成列表项对象 40 | */ 41 | fun produceListDataSource( 42 | rootFile: File 43 | ): ArrayList { 44 | val listData: ArrayList = ArrayList() 45 | var isDetected = false 46 | // 查看是否在根目录上级 47 | val realRoot = getRootFile() 48 | val isInRootParent = rootFile.list() == null 49 | && !config.isSkipDir 50 | && rootFile.path == realRoot.parentFile?.path 51 | if (isInRootParent) { 52 | // 如果是文件夹作为可选项时,需要让根目录也作为 item 被点击 53 | listData.add( 54 | FileItemBeanImpl( 55 | realRoot.name, 56 | realRoot.path, 57 | false, 58 | null, 59 | isDir = true, 60 | isHide = false 61 | ) 62 | ) 63 | return config.selfFilter?.doFilter(listData) ?: listData 64 | } 65 | val listFiles = rootFile.listFiles() 66 | if (listFiles.isNullOrEmpty()) { 67 | return listData 68 | } 69 | for (file in listFiles) { 70 | //以符号 . 开头的视为隐藏文件或隐藏文件夹,后面进行过滤 71 | val isHiddenFile = file.name.startsWith(".") 72 | if (!config.isShowHiddenFiles && isHiddenFile) { 73 | // skip hidden files 74 | continue 75 | } 76 | if (file.isDirectory) { 77 | listData.add( 78 | FileItemBeanImpl( 79 | file.name, 80 | file.path, 81 | false, 82 | null, 83 | true, 84 | isHiddenFile 85 | ) 86 | ) 87 | continue 88 | } 89 | val itemBean = FileItemBeanImpl( 90 | file.name, 91 | file.path, 92 | false, 93 | null, 94 | false, 95 | isHiddenFile 96 | ) 97 | // 如果调用者没有实现文件类型甄别器,则使用的默认甄别器 98 | config.customDetector?.fillFileType(itemBean) 99 | ?: config.defaultFileDetector.fillFileType(itemBean) 100 | isDetected = itemBean.fileType != null 101 | if (config.defaultFileDetector.enableCustomTypes 102 | && config.isAutoFilter 103 | && !isDetected 104 | ) { 105 | // enable auto filter AND using user's custom file type. Filter them. 106 | continue 107 | } 108 | listData.add(itemBean) 109 | } 110 | 111 | // 默认字典排序 112 | // Default sort by alphabet 113 | listData.sortWith( 114 | compareBy( 115 | { !it.isDir }, 116 | { it.fileName.uppercase(Locale.getDefault()) }) 117 | ) 118 | // 将当前列表数据暴露,以供调用者自己处理数据 119 | // expose data list to outside caller 120 | return config.selfFilter?.doFilter(listData) ?: listData 121 | } 122 | 123 | /** 124 | * 为导航栏添加数据,也就是每进入一个文件夹,导航栏的列表就添加一个对象 125 | * 如果是退回到上层文件夹,则删除后续子目录元素 126 | */ 127 | fun produceNavDataSource( 128 | currentDataSource: ArrayList, 129 | nextPath: String, 130 | context: Context 131 | ): ArrayList { 132 | // 优先级:目标设备名称 --> 自定义路径 --> 默认 SD 卡 133 | if (currentDataSource.isEmpty()) { 134 | val dirName = getDirAlias(getRootFile()) 135 | currentDataSource.add( 136 | FileNavBeanImpl( 137 | dirName, 138 | nextPath 139 | ) 140 | ) 141 | return currentDataSource 142 | } 143 | 144 | for (data in currentDataSource) { 145 | // 如果是回到根目录 146 | if (nextPath == currentDataSource.first().dirPath) { 147 | return ArrayList(currentDataSource.subList(0, 1)) 148 | } 149 | // 如果是回到当前目录(不包含根目录情况) 150 | // 直接返回 151 | val isCurrent = nextPath == currentDataSource[currentDataSource.size - 1].dirPath 152 | if (isCurrent) { 153 | return currentDataSource 154 | } 155 | 156 | // 如果是回到上层的某一目录(即,当前列表中有该路径) 157 | // 将列表截取至目标路径元素 158 | val isBackToAbove = nextPath == data.dirPath 159 | if (isBackToAbove) { 160 | return ArrayList( 161 | currentDataSource.subList( 162 | 0, 163 | currentDataSource.indexOf(data) + 1 164 | ) 165 | ) 166 | } 167 | } 168 | // 循环到此,意味着将是进入子目录 169 | currentDataSource.add( 170 | FileNavBeanImpl( 171 | nextPath.substring(nextPath.lastIndexOf("/") + 1), 172 | nextPath 173 | ) 174 | ) 175 | return currentDataSource 176 | } 177 | 178 | fun getDirAlias(file: File): String { 179 | val isCustomRoot = config.mediaStorageType == STORAGE_CUSTOM_ROOT_PATH 180 | && file.absolutePath == config.customRootPath 181 | val isPreSetStorageRoot = config.mediaStorageType == STORAGE_EXTERNAL_STORAGE 182 | && file.absolutePath == getRootFile().absolutePath 183 | val isDefaultRoot = file.absolutePath == getRootFile().absolutePath 184 | return when { 185 | isCustomRoot || isPreSetStorageRoot -> { 186 | config.mediaStorageName 187 | } 188 | isDefaultRoot -> { 189 | config.defaultStorageName 190 | } 191 | else -> { 192 | file.name 193 | } 194 | } 195 | } 196 | } 197 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/utils/ScreenUtils.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.utils 2 | 3 | import android.content.res.Resources 4 | import android.util.TypedValue 5 | 6 | 7 | val Int.dp: Int 8 | get() = TypedValue.applyDimension( 9 | TypedValue.COMPLEX_UNIT_DIP, 10 | this.toFloat(), 11 | Resources.getSystem().displayMetrics 12 | ).toInt() 13 | -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/widget/PosLinearLayoutManager.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.widget 2 | 3 | import android.content.Context 4 | import android.os.Parcelable 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import androidx.recyclerview.widget.RecyclerView 7 | import android.util.AttributeSet 8 | 9 | class PosLinearLayoutManager : LinearLayoutManager { 10 | constructor(context: Context?) : super(context) 11 | constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super( 12 | context, 13 | orientation, 14 | reverseLayout 15 | ) 16 | 17 | constructor( 18 | context: Context?, 19 | attrs: AttributeSet?, 20 | defStyleAttr: Int, 21 | defStyleRes: Int 22 | ) : super( 23 | context, 24 | attrs, 25 | defStyleAttr, 26 | defStyleRes 27 | ) 28 | 29 | private var pendingTargetPos = -1 30 | 31 | private var pendingPosOffset = -1 32 | 33 | override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) { 34 | if (pendingTargetPos != -1 && state?.itemCount ?: 0 > 0) { 35 | scrollToPositionWithOffset(pendingTargetPos, pendingPosOffset) 36 | pendingPosOffset = -1 37 | pendingTargetPos = -1 38 | } 39 | super.onLayoutChildren(recycler, state) 40 | } 41 | 42 | override fun onRestoreInstanceState(state: Parcelable?) { 43 | pendingTargetPos = -1 44 | pendingPosOffset = -1 45 | super.onRestoreInstanceState(state) 46 | } 47 | 48 | fun setTargetPos(pos: Int, offset: Int) { 49 | pendingTargetPos = pos 50 | pendingPosOffset = offset 51 | } 52 | } -------------------------------------------------------------------------------- /filepicker/src/main/java/me/rosuh/filepicker/widget/RecyclerViewFilePicker.kt: -------------------------------------------------------------------------------- 1 | package me.rosuh.filepicker.widget 2 | 3 | import android.content.Context 4 | import androidx.recyclerview.widget.RecyclerView 5 | import android.util.AttributeSet 6 | import android.view.View 7 | import android.view.ViewGroup 8 | 9 | class RecyclerViewFilePicker : RecyclerView { 10 | constructor(context: Context) : super(context) 11 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 12 | constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( 13 | context, 14 | attrs, 15 | defStyle 16 | ) 17 | 18 | var emptyView: View? = null 19 | set(value) { 20 | field = value 21 | (this@RecyclerViewFilePicker.rootView as ViewGroup).addView(value) 22 | field?.visibility = View.GONE 23 | } 24 | 25 | fun hasEmptyView(): Boolean = emptyView != null 26 | 27 | override fun setAdapter(adapter: Adapter<*>?) { 28 | super.setAdapter(adapter) 29 | adapter?.registerAdapterDataObserver(adapterDataObserver) 30 | adapterDataObserver.onChanged() 31 | } 32 | 33 | private val adapterDataObserver by lazy { 34 | object : AdapterDataObserver() { 35 | override fun onChanged() { 36 | if (adapter?.itemCount ?: 0 == 0 && emptyView != null) { 37 | emptyView?.animate() 38 | ?.alpha(1f) 39 | ?.withStartAction { 40 | emptyView?.visibility = View.VISIBLE 41 | } 42 | ?.start() 43 | this@RecyclerViewFilePicker.visibility = View.GONE 44 | } else { 45 | emptyView?.animate() 46 | ?.alpha(0f) 47 | ?.withEndAction { 48 | emptyView?.visibility = View.GONE 49 | } 50 | ?.start() 51 | this@RecyclerViewFilePicker.visibility = View.VISIBLE 52 | } 53 | } 54 | 55 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { 56 | onChanged() 57 | } 58 | 59 | override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { 60 | onChanged() 61 | } 62 | 63 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 64 | onChanged() 65 | } 66 | 67 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { 68 | onChanged() 69 | } 70 | 71 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { 72 | onChanged() 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /filepicker/src/main/res/anim/item_slide_down_anim_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 26 | 27 | -------------------------------------------------------------------------------- /filepicker/src/main/res/anim/layout_item_anim_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/bg_btn_focus_file_picker_crane.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/bg_btn_focus_file_picker_rail.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/bg_btn_focus_file_picker_reply.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/bg_btn_focus_file_picker_shrine.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/btn_selector_file_picker_crane.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/btn_selector_file_picker_rail.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/btn_selector_file_picker_reply.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/btn_selector_file_picker_shrine.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/btn_style_normal_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_arrow_right_file_picker.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_back_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_bt_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_compressed_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_empty_file_list_file_picker.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_excel_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_exec_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_folder_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_html_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_image_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_music_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_pdf_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_txt_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_unknown_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_video_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/ic_word_file_picker.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /filepicker/src/main/res/drawable/rec_loading_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /filepicker/src/main/res/layout/empty_file_list_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 26 | -------------------------------------------------------------------------------- /filepicker/src/main/res/layout/item_list_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 29 | 30 | 39 | 40 | 51 | 52 | 63 | 64 | 72 | -------------------------------------------------------------------------------- /filepicker/src/main/res/layout/item_nav_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /filepicker/src/main/res/layout/item_single_choise_list_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 29 | 30 | 38 | 39 | 50 | 51 | 57 | -------------------------------------------------------------------------------- /filepicker/src/main/res/layout/main_activity_for_file_picker.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 |