Skip to content

Commit

Permalink
♻️ 🎨 🚀 📝
Browse files Browse the repository at this point in the history
[Add]
- 链式调用
- 添加文件类型抽象类
    - 公开文件类型接口
- 添加文件过滤抽象类
    - 公开文件过滤器接口
- 公开条目点击接口,可以自实现条目点击效果
- 添加界面字符串自定义功能
- 返回键返回上层目录功能
- 添加 FilePickerConfig 类保存配置
- 新增四种主题配色
[Update]
- 文件类型可由调用者自己实现,也可以使用默认实现
- FileItemBean 添加图标资源变量,支持自定义类型图标
- 调用 FilePickerManager.obtainData() 获取数据,Intent 仅作消息发送功能
- 更新部分文件类型默认 icon
- README
  • Loading branch information
rosuH committed Nov 27, 2018
1 parent b88b596 commit a932213
Show file tree
Hide file tree
Showing 30 changed files with 848 additions and 273 deletions.
136 changes: 103 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
# AndroidFilePicker
[![](https://jitpack.io/v/me.rosuh/AndroidFilePicker.svg)](https://jitpack.io/#me.rosuh/AndroidFilePicker)

:bookmark: FilePicker 是一个小巧快速的文件选择器框架,以快速集成、高自定义化和可配置化为目标不断前进~

# I 简介

## 功能
1. 独立的 Activity 浏览视图
- 通过`Intent`启动`Activity`,通过`onActivityResult()`获取返回结果
2. 文件筛选显示
- 通过自定义文件类型来配置的文件筛选/过滤,如果你只想显示压缩文件,一行代码搞定
- 自定义过滤器:暴露接口让你自定义过滤器
- 可选择是否显示隐藏的文件/文件夹
- 可选择是否选中文件夹
3. 文件多选/全选
4. 文件夹导航栏
🔖 FilePicker 是一个小巧快速的文件选择器框架,以快速集成、高自定义化和可配置化为目标不断前进~🚩

# II 使用
## 展示

*`sample`模块是使用示例*
展示图正在努力绘制中...不如 clone 后 build 出来看看?😝

# II 使用

1. 在你的项目中添加依赖

Expand All @@ -41,23 +32,28 @@ dependencies {

2. 开始使用

*启动*:在你的`Activity`中启动文件选择器
简单的链式调用示意:

```java
val intent = Intent(this, FilePickerActivity::class.java)
startActivityForResult(intent, FilePickerManager.REQUEST_CODE)
FilePickerManager.instance
.from(this@SampleActivity)
// 主题设置
.setTheme(R.style.FilePickerThemeReply)
// 自定义过滤器(可选)
.filter(fileFilter)
.forResult(FilePickerManager.instance.REQUEST_CODE)
```

*获取结果*`onActivityResult`获取返回的结果,结果是所选取文件的路径列表(`ArrayList<String>()`)
*获取结果*`onActivityResult`接受消息,然后调用`FilePickerManager.obtainData()`获取保存的数据,结果是所选取文件的路径列表(`ArrayList<String>()`)

```java
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
FilePickerManager.REQUEST_CODE -> {
FilePickerManager.instance.REQUEST_CODE -> {
if (resultCode == Activity.RESULT_OK) {
val bundle = data!!.extras
val list = bundle!!.getStringArrayList(FilePickerManager.RESULT_KEY)
// do your work...
val list = FilePickerManager.instance.obtainData()
rv!!.adapter = SampleAdapter(R.layout.demo_item, ArrayList(list))
rv!!.layoutManager = LinearLayoutManager(this@SampleActivity)
} else {
Toast.makeText(this@SampleActivity, "没有选择图片", Toast.LENGTH_SHORT).show()
}
Expand All @@ -68,20 +64,94 @@ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)



### 细节
## 功能 & 特点

1. 链式调用
2. 内置四种主题配色 + 可自定义配色
- 查看主题颜色示意图,然后调用`setTheme()`传入自定义主题
3. 默认实现多种文件类型
- 实现`IFileType`接口来实现你的文件类型
- 实现`AbstractFileType`抽象类来实现你的文件类型甄别器
4. 公开文件过滤接口
- 实现`AbstractFileFilter`抽象类来定制你自己的文件过滤器,这样可以控制文件列表的展示内容
5. 多种可配置选项
1. 选中时是否忽略文件夹
2. 是否显示隐藏文件夹(以符号`.`开头的,视为隐藏文件或隐藏文件夹)
3. 可配置导航栏的文本,默认显示、多选文本、取消选择文本以及根目录默认名称
6. 公开条目(`item`)选择监听器,可自定义条目被点击的实现

### 部分源码说明

1. `FilePickerManager`类是配置类,你可以配置『是否显示隐藏文件』、『是否忽略选中文件夹』等选项
2. `FileFilter.selfFilter()`接口是用以让您自定义过滤器的
- 传入的是文件列表的数据集
- 数据集经过您的处理之后,再生出`Adapter`,绑定视图之后展示出来
1. 包和文件夹

# TODO
- `adapter`包:两个列表(导航栏和文件列表)的数据适配器类

- `bean`包:所有用到的`Model`

- `IFileBean`是文件对象所需要实现的接口

- `config`:管理类、配置类所在

- `AbstractFileFilter`:文件过滤器抽象类,用于给调用者自实现文件过滤器
- `AbstractFileType`:文件类型抽象类,用于给调用者自实现自己的文件类型
- 其中的抽象函数`fillFileType`为文件甄别器,如果你实现了自己的文件类型,那么最好也要实现自己的文件甄别器
- `DefaultFileType`:默认文件类型,文件类型类的默认实现,里面实现了默认的文件甄别器

- `filetype`:一些默认实现的文件类型

- 实现接口`IFileType`以实现自己的文件类型

- `utils`:一些工具类

- `FileUtils`类包含了文件相关的大部分所需的函数
- `PercentLayoutUtils``PercentTextView``TextView`的相对布局实现(*1)


# Log

## 2018-11-27

:recycle: :art: :rocket: :memo:

### Add

- 解耦视图和控制逻辑,为后续自定义布局铺路
- 列表项可打开,可配置打开方式
- 记住父文件夹浏览位置
- 更优雅的方式获取返回结果,`onActivityResult()`只发送通知消息,从另一容器拿到结果
- 链式调用
- 默认视图美观度提升
- 添加文件类型抽象类
- 公开文件类型接口
- 添加文件过滤抽象类
- 公开文件过滤器接口
- 公开条目点击接口,可以自实现条目点击效果
- 添加界面字符串自定义功能
- 返回键返回上层目录功能
- 添加 FilePickerConfig 类保存配置
- 新增四种主题配色

### Update

- 文件类型可由调用者自己实现,也可以使用默认实现
- FileItemBean 添加图标资源变量,支持自定义类型图标
- 调用 FilePickerManager.obtainData() 获取数据,Intent 仅作消息发送功能
- 更新部分文件类型默认 icon
- README


# TODO

- [x] 列表项可打开,可配置打开方式
- [x] 更优雅的方式获取返回结果,`onActivityResult()`只发送通知消息,从另一容器拿到结果
- [x] 默认视图美观度提升
- [x] 链式调用
- [ ] 记住父文件夹浏览位置
- [ ] 解耦视图和控制逻辑,为后续自定义布局铺路



---

# Special Thanks To:

- [*1 @whichName](https://github.com/whichname)

- [BRVAH](https://github.com/CymChad/BaseRecyclerViewAdapterHelper)
- [Matisse](https://github.com/zhihu/Matisse)

97 changes: 42 additions & 55 deletions filepicker/src/main/java/me/rosuh/filepicker/FilePickerActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package me.rosuh.filepicker

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
Expand All @@ -18,24 +17,20 @@ import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CheckBox
import android.widget.Toast
import com.chad.library.adapter.base.BaseQuickAdapter
import com.chad.library.adapter.base.BaseViewHolder
import me.rosuh.filepicker.bean.FileTypeEnum.COMPRESSED
import me.rosuh.filepicker.bean.FileTypeEnum.DIR
import me.rosuh.filepicker.bean.FileTypeEnum.IMAGE
import me.rosuh.filepicker.bean.FileTypeEnum.OCTET_STREAM
import me.rosuh.filepicker.bean.FileTypeEnum.UNKNOWN
import me.rosuh.filepicker.bean.FileTypeEnum.VIDEO
import me.rosuh.filepicker.adapter.FileListAdapter
import me.rosuh.filepicker.adapter.FileNavAdapter
import me.rosuh.filepicker.bean.FileItemBean
import me.rosuh.filepicker.bean.FileNavBean
import me.rosuh.filepicker.bean.IFileBean
import me.rosuh.filepicker.config.FilePickerConfig
import me.rosuh.filepicker.config.FilePickerManager
import me.rosuh.filepicker.config.FilePickerManager.RESULT_KEY
import me.rosuh.filepicker.utils.FileUtils
import me.rosuh.filepicker.utils.PercentTextView
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean

Expand Down Expand Up @@ -71,11 +66,11 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
private var mBtnConfirm: AppCompatButton? = null
private var mBtnGoBack: AppCompatImageButton? = null
private val mFilesIsChecked: AtomicBoolean? = AtomicBoolean(false)

private val FILE_PICKER_PERMISSION_REQUEST_CODE = 10201
private var mTvSelected: PercentTextView? = null
private val pickerConfig by lazy { FilePickerConfig.getInstance(FilePickerManager.instance) }

override fun onCreate(savedInstanceState: Bundle?) {
setTheme(FilePickerManager.themeId)
setTheme(pickerConfig.themeId)

super.onCreate(savedInstanceState)
setContentView(R.layout.activity_file_picker)
Expand All @@ -101,7 +96,7 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
ActivityCompat.requestPermissions(
this@FilePickerActivity,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
FILE_PICKER_PERMISSION_REQUEST_CODE
Companion.FILE_PICKER_PERMISSION_REQUEST_CODE
)
}

Expand All @@ -111,7 +106,7 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
grantResults: IntArray
) {
when (requestCode) {
FILE_PICKER_PERMISSION_REQUEST_CODE -> {
Companion.FILE_PICKER_PERMISSION_REQUEST_CODE -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this@FilePickerActivity, "未授予存储权限", Toast.LENGTH_SHORT).show()
} else {
Expand All @@ -126,7 +121,7 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
*/
private fun prepareLauncher() {
if (Environment.getExternalStorageState() != MEDIA_MOUNTED) {
throw Throwable(IllegalStateException("外部存储不可用"))
throw Throwable(cause = IllegalStateException("外部存储不可用"))
}

// 根目录文件对象
Expand All @@ -149,6 +144,7 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
mBtnSelectedAll = findViewById(R.id.btn_selected_all_file_picker)
mBtnConfirm = findViewById(R.id.btn_confirm_file_picker)
mBtnGoBack = findViewById(R.id.btn_go_back_file_picker)
mTvSelected = findViewById(R.id.tv_toolbar_title_file_picker)
mBtnGoBack!!.setOnClickListener(this)
mBtnSelectedAll!!.setOnClickListener(this)
mBtnConfirm!!.setOnClickListener(this)
Expand Down Expand Up @@ -198,7 +194,7 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
}

/**
* 根据被点击项的类型,触发不同的操作
* 传递条目点击事件给调用者
* @param adapter BaseQuickAdapter<*, *>?
* @param view View?
* @param position Int
Expand All @@ -207,40 +203,13 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
// 如果不是点击列表,则返回
if (view!!.id != R.id.item_list_file_picker) return
val item = adapter!!.getItem(position) as FileItemBean
val file = File(item.filePath)

val intent = Intent()
intent.action = Intent.ACTION_SEND
try {
when (item.fileType) {
DIR -> {
// 文件夹则进入
enterDirAndUpdateUI(item)
}
IMAGE -> {
intent.type = "image/*"
intent.data = Uri.parse(item.mFilePath)
startActivity(intent)
}
VIDEO -> {
intent.type = "video/*"
intent.data = Uri.parse(item.filePath)
startActivity(intent)
}
COMPRESSED -> {
val sub = item.mFileName.substring(item.mFileName.lastIndexOf("."))
intent.type = "application/$sub"
intent.data = Uri.parse(item.filePath)
startActivity(intent)
}
UNKNOWN -> {
Toast.makeText(this@FilePickerActivity, "我们不知道如何打开该文件", Toast.LENGTH_SHORT).show()
}
OCTET_STREAM -> {
Toast.makeText(this@FilePickerActivity, "我们不知道如何打开该文件", Toast.LENGTH_SHORT).show()
}
}
} catch (ae: ActivityNotFoundException) {
Toast.makeText(this@FilePickerActivity, "没有应用可以打开该文件", Toast.LENGTH_SHORT).show()
if (file.exists() && file.isDirectory) {
// 如果是文件夹,则进入
enterDirAndUpdateUI(item)
} else {
FilePickerConfig.getInstance(FilePickerManager.instance).fileIFileItemOnClickListener?.onItemClick(item, position)
}
}

Expand All @@ -263,6 +232,8 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList

/**
* 从导航栏中调用本方法,需要传入 pos,以便生产新的 nav adapter
* @param iFileBean IFileBean
* @param position Int 用来定位导航栏的当前 item,如果是后退按钮,则传入倒数第二个 position
*/
private fun enterDirAndUpdateUI(iFileBean: IFileBean, position: Int) {
// 获取文件夹文件
Expand Down Expand Up @@ -301,7 +272,13 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
}

override fun onBackPressed() {
super.onBackPressed()
if (mNavDataSource.size <= 1) {
super.onBackPressed()
} else {
// 即将进入的 item 的索引
val willEnterItemPos = mNavDataSource.size - 2
enterDirAndUpdateUI(mNavDataSource[willEnterItemPos], willEnterItemPos)
}
}

override fun onClick(v: View?) {
Expand All @@ -310,20 +287,26 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
R.id.btn_selected_all_file_picker -> {
if (mFilesIsChecked!!.get()) {
for (data in mListAdapter!!.data) {
if (FilePickerManager.isSkipDir && data.fileType == DIR) {
val file = File(data.filePath)
if (pickerConfig.isSkipDir && file.exists() && file.isDirectory) {
continue
}
data.isChecked = false
}
mBtnSelectedAll!!.text = "图片全选"
mBtnSelectedAll!!.text = pickerConfig.selectAllText
mTvSelected!!.text = pickerConfig.goBackText
} else {
var checkedCount = 0
for (data in mListAdapter!!.data) {
if (FilePickerManager.isSkipDir && data.fileType == DIR) {
val file = File(data.filePath)
if (pickerConfig.isSkipDir && file.exists() && file.isDirectory) {
continue
}
data.isChecked = true
checkedCount++
}
mBtnSelectedAll!!.text = "取消选中"
mBtnSelectedAll!!.text = pickerConfig.unSelectAllText
mTvSelected!!.text = pickerConfig.hadSelectedText + checkedCount
}

mListAdapter!!.notifyDataSetChanged()
Expand All @@ -344,7 +327,7 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
finish()
}

intent.putExtra(RESULT_KEY, list)
FilePickerManager.instance.saveData(list)
this@FilePickerActivity.setResult(Activity.RESULT_OK, intent)
finish()
}
Expand All @@ -353,4 +336,8 @@ class FilePickerActivity : AppCompatActivity(), BaseQuickAdapter.OnItemClickList
}
}
}

companion object {
const val FILE_PICKER_PERMISSION_REQUEST_CODE = 10201
}
}
Loading

0 comments on commit a932213

Please sign in to comment.