jjzjj

Android文件选择器 自动申请存储权限 适配安卓 4.4 ~ 13支持无root权限访问和操作Android/data和Android/obb目录

wx6399f7d59b749 2023-03-28 原文


Android上进行文件选择或操作的第三方库,自动申请存储权限,支持 Android4.4 ~ 13,再也不用为了适配各种版本而苦恼了,快速集成,一句代码搞定,完善的文档,支持无root权限访问和操作Android/data和Android/obb目录(适配Android 13),支持SD卡,高度自定义UI满足你的所有需求,使用非常灵活,支持国际化,对于Android文件选择你只需要关注你的业务代码即可其他的都交给它。

特性

  • 自动申请存储权限
  • 安卓 4.4 ~ 13
  • Android/data和Android/obb目录访问和操作
  • SD卡
  • 高度自定义UI
  • 国际化

前言

在开始之前可以给项目一个Star吗?非常感谢,你的支持是我唯一的动力。欢迎Star和Issues!
项目地址:
​Github地址​
​Gitee地址​

demo演示:

系统版本:Android 13
下载链接:​​体验APP​

一、快速开始

第1步:添加仓库:
如果你的项目 Gradle 配置是在 7.0 以下,需要在 build.gradle 文件中加入
allprojects {
repositories {
...
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
如果你的 Gradle 配置是 7.0 及以上,则需要在 settings.gradle 文件中加入
dependencyResolutionManagement {
repositories {
...
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
第2步:添加远程依赖:
配置完远程仓库后,在项目 app 模块下的 build.gradle 文件中加入远程依赖
最新发布版:
dependencies {
...
// 请将"版本"替换成具体的版本号,如 1.1.2
implementation 'io.github.molihuan:pathselector:版本'
}
第3步:基本用法示范:
//如果没有权限会自动申请权限
PathSelector.build(this, MConstants.BUILD_DIALOG)//Dialog构建方式
.setMorePopupItemListeners(
new CommonItemListener("OK") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {

StringBuilder builder = new StringBuilder();
builder.append("you selected:\n");
for (FileBean fileBean : selectedFiles) {
builder.append(fileBean.getPath() + "\n");
}
Mtools.toast(builder.toString());

return false;
}
}
)
.show();//开始构建

二、基本设置

打开调试模式
//开启调试模式,生产环境请关闭
PathSelectorConfig.setDebug(true);
//或者PathSelector.setDebug(true);
1、Activity构建模式:
//Activity构建方式
PathSelectFragment selector = PathSelector.build(this, MConstants.BUILD_ACTIVITY)
.setRequestCode(635)
.setMorePopupItemListeners(
new CommonItemListener("OK") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {

StringBuilder builder = new StringBuilder();
builder.append("you selected:\n");
for (FileBean fileBean : selectedFiles) {
builder.append(fileBean.getPath() + "\n");
}
Mtools.toast(builder.toString());

return false;
}
}
)
.show();
2、Fragment构建模式:
第1步:在你需要显示的布局文件xml中使用FrameLayout占位
<FrameLayout
android:id="@+id/fragment_select_show_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
第2步:编写代码
//获取PathSelectFragment实例然后在onBackPressed中处理返回按钮点击事件
PathSelectFragment selector = PathSelector.build(this, MConstants.BUILD_FRAGMENT)
.setFrameLayoutId(R.id.fragment_select_show_area)//加载位置,FrameLayout的ID
.setMorePopupItemListeners(
new CommonItemListener("OK") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {

StringBuilder builder = new StringBuilder();
builder.append("you selected:\n");
for (FileBean fileBean : selectedFiles) {
builder.append(fileBean.getPath() + "\n");
}

Mtools.toast(builder.toString());
return false;
}
}
)
.show();//开始构建
第3步:重写onBackPressed()方法让路径选择器优先处理返回按钮点击事件
非常重要!!!
非常重要!!!
非常重要!!!
重要的事情说三遍
@Override
public void onBackPressed() {

//让PathSelectFragment先处理返回按钮点击事件
if (selector != null && selector.onBackPressed()) {
return;
}
......
super.onBackPressed();
}
3、Dialog构建模式 & 常用设置:
//获取PathSelectFragment实例然后在onBackPressed中处理返回按钮点击事件
PathSelectFragment selector = PathSelector.build(this, MConstants.BUILD_DIALOG)
//.setBuildType(MConstants.BUILD_DIALOG)//已经在build中已经设置了
//.setContext(this)//已经在build中已经设置了
.setRootPath("/storage/emulated/0/")//初始路径
.setShowSelectStorageBtn(true)//是否显示内部存储选择按钮
.setShowTitlebarFragment(true)//是否显示标题栏
.setShowTabbarFragment(true)//是否显示面包屑
.setAlwaysShowHandleFragment(true)//是否总是显示长按弹出选项
.setShowFileTypes("", "mp3", "mp4")//只显示(没有后缀)或(后缀为mp3)或(后缀为mp4)的文件
.setSelectFileTypes("", "mp3")//只能选择(没有后缀)或(后缀为mp3)的文件
.setMaxCount(3)//最多可以选择3个文件,默认是-1不限制
.setRadio()//单选
.setSortType(MConstants.SORT_NAME_ASC)//按名称排序
.setTitlebarMainTitle(new FontBean("My Selector"))//设置标题栏主标题,还可以设置字体大小,颜色等
.setTitlebarBG(Color.GREEN)//设置标题栏颜色
.setFileItemListener(//设置文件item点击回调(点击是文件才会回调,如果点击是文件夹则不会)
new FileItemListener() {
@Override
public boolean onClick(View v, FileBean file, String currentPath, BasePathSelectFragment pathSelectFragment) {
Mtools.toast("you clicked path:\n" + file.getPath());
return false;
}
}
)
.setMorePopupItemListeners(//设置右上角选项回调
new CommonItemListener("SelectAll") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {
pathSelectFragment.selectAllFile(true);
return false;
}
},
new CommonItemListener("DeselectAll") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {
pathSelectFragment.selectAllFile(false);
return false;
}
}
)
.setHandleItemListeners(//设置长按弹出选项回调
new CommonItemListener("OK") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {
StringBuilder builder = new StringBuilder();
builder.append("you selected:\n");
for (FileBean fileBean : selectedFiles) {
builder.append(fileBean.getPath() + "\n");
}
Mtools.toast(builder.toString());
return false;
}
},
new CommonItemListener("cancel") {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {
pathSelectFragment.openCloseMultipleMode(false);
return false;
}
}
)
.show();

三、高级设置(自定义UI)

UI布局:

1、自定义选项样式(以HandleItem为例子)
方式1:通过FontBean来设置样式
PathSelectFragment selector = PathSelector.build(this, MConstants.BUILD_DIALOG)
.setHandleItemListeners(//设置长按弹出选项回调
//FontBean可以设置文本、字的大小、字的颜色、字左边的图标
//R.drawable.ic_test_mlh是你自己的图片资源id
new CommonItemListener(new FontBean("OK", 18, Color.RED, R.drawable.ic_test_mlh)) {
@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {
Mtools.toast("You Click");
return false;
}
}
)
.show();
什么?这种方式还不能满足你,那么试试方式2

方式2:重写CommonItemListener的setViewStyle方法来自定义样式
PathSelectFragment selector = PathSelector.build(this, MConstants.BUILD_DIALOG)
.setHandleItemListeners(
//重写CommonItemListener的setViewStyle方法来自定义样式
new CommonItemListener("OK") {
@Override
public boolean setViewStyle(RelativeLayout container, ImageView leftImg, TextView textView) {
textView.setTextSize(18);
textView.setTextColor(Color.RED);
//默认是不显示图标的
leftImg.setVisibility(View.VISIBLE);
leftImg.setImageResource(R.drawable.ic_test_mlh);
leftImg.getLayoutParams().width = 90;
leftImg.getLayoutParams().height = 90;
return true;
}

@Override
public boolean onClick(View v, List<FileBean> selectedFiles, String currentPath, BasePathSelectFragment pathSelectFragment) {
Mtools.toast("You Click");
return false;
}
}
)
.show();
什么?什么?这种方式还不能满足你,那么你来写UI它来帮你添加,试试高度自定义UI

2、高度自定义UI(以Titlebar为例子):
第1步:新建一个布局文件,如:fragment_custom_titlebar.xml
<?xml versinotallow="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:id="@+id/my_btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn1" />

<Button
android:id="@+id/my_btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="selectAll" />

</LinearLayout>
第2步:新建一个类,如:CustomTitlebarFragment.class使其继承AbstractTitlebarFragment并关联第1步中的布局文件
public class CustomTitlebarFragment extends AbstractTitlebarFragment {
private Button btn1;
private Button btn2;

@Override
public int setFragmentViewId() {
return R.layout.fragment_custom_titlebar;
}

@Override
public void getComponents(View view) {
btn1 = view.findViewById(R.id.my_btn1);
btn2 = view.findViewById(R.id.my_btn2);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Mtools.toast("The current path is:\n" + psf.getCurrentPath());
}
});
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
psf.selectAllFile(true);
}
});
}
}
第3步:编写代码
//获取PathSelectFragment实例然后在onBackPressed中处理返回按钮点击事件
PathSelectFragment selector = PathSelector.build(this, MConstants.BUILD_DIALOG)
.setTitlebarFragment(new CustomTitlebarFragment())
.show();

四、接口与方法(尽量看源码,都写了注释,懒得写文档)

IConfigDataBuilder
方法

注释

备注

setFrameLayoutId(int id)

设置加载位置FrameLayoutID

当构建模式为MConstants.BUILD_FRAGMENT时必须设置

setRequestCode(int code)

设置请求码

当构建模式为MConstants.BUILD_ACTIVITY时必须设置

setRootPath(String path)

设置开始默认路径

默认为内部存储根路径

setMaxCount(int maxCount)

设置最大选择数量

不设置默认为-1 即无限

setShowFileTypes(String... fileTypes)

设置显示文件类型

没有后缀请用""

setSelectFileTypes(String... fileTypes)

设置选择文件类型

没有后缀请用""

setSortType(int sortType)

设置排序规则

类型请看MConstants

setRadio()

设置单选

默认多选

setShowSelectStorageBtn(boolean var)

设置是否显示内部存储选择按钮

默认true

setShowTitlebarFragment(boolean var)

是否显示标题栏

默认true

setShowTabbarFragment(boolean var)

是否显示面包屑

默认true

setAlwaysShowHandleFragment(boolean var)

是否总是显示长按弹出选项

默认false

setTitlebarMainTitle(FontBean titlebarMainTitle)

设置标题栏主标题

还可以设置字体大小,颜色等

setTitlebarBG(Integer titlebarBG)

设置标题栏背景颜色


setFileItemListener(FileItemListener fileItemListener)

设置文件item点击回调

点击是文件才会回调,如果点击是文件夹则不会

setMorePopupItemListeners(CommonItemListener... morePopupItemListener)

设置右上角选项回调


setHandleItemListeners(CommonItemListener... handleItemListener)

设置长按弹出选项回调


setTitlebarFragment(AbstractTitlebarFragment titlebarFragment)

设置自定义标题栏UI

自己的Fragment必须继承AbstractTitlebarFragment

setHandleFragment(AbstractHandleFragment handleFragment)

设置长按弹出自定义UI

自己的Fragment必须继承AbstractHandleFragment

start()

开始构建

必须调用

......

......


五、!!!特别注意 !!!

分区存储
该库以及适配了分区存储,不需要额外适配,你只需要写你的业务代码即可,其他的交给它。
  • 注意该库已经在库的AndroidManifest.xml中添加了:
<!-- 外部存储的写权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 安卓11额外权限 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<!-- 已经适配了分区存储特性 -->
<application
android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"
>
  • 可能会报错:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed with multiple errors, see logs
请在你的项目中的​​AndroidManifest.xml​​设置一致

版本升级
  • 新版本往往解决了旧版本的一些问题、增加了性能、可扩展性......建议升级新版本
  • 请注意因为重构了项目导致了旧版本与新版本不兼容。1.0.x升级1.1.x为非兼容升级,请注意学习新的API
体积过大
  • 已经集成了Blankj/AndroidUtilCode
    如果项目对大小有严格要求请自行下载源码并精简AndroidUtilCode模块
代码混淆
  • 一般来说无需配置,会自动导入混淆规则

特别鸣谢

开源项目以及其依赖项目。

​LICENSE ​

有关Android文件选择器 自动申请存储权限 适配安卓 4.4 ~ 13支持无root权限访问和操作Android/data和Android/obb目录的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  3. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  4. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

  5. ruby-on-rails - 更好的替代方法 try( :output). try( :data). try( :name)? - 2

    “输出”是一个序列化的OpenStruct。定义标题try(:output).try(:data).try(:title)结束什么会更好?:) 最佳答案 或者只是这样:deftitleoutput.data.titlerescuenilend 关于ruby-on-rails-更好的替代方法try(:output).try(:data).try(:name)?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

  6. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  7. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  8. ruby-on-rails - 没有这样的文件或目录 - 用 Mini Magick 识别 - 2

    在我让另一个人重做我的前端UI之前,我的Rails应用程序运行平稳。我已经尝试解决此错误3天了。这是错误:Nosuchfileordirectory-identifyExtractedsource(aroundline#59):575859606162@post=Post.find(params[:id])authorize@postif@post.update_attributes(post_params)flash[:notice]="Postwasupdated."redirect_to[@topic,@post]else{"utf8"=>"✓","_method"=>"patc

  9. ruby - rbenv 安装权限被拒绝 - 2

    大家好,我正在尝试设置一个开发环境,并且我一直在关注以下教程:Linktotutorial我做得不是很好,除了最基本的版本控制内容外,我对终端命令没有任何实际经验。我点击了第一个链接并尝试运行source~/.bash_profile我得到了错误;mkdir:/usr/local/rbenv/shims:权限被拒绝mkdir:/usr/local/rbenv/versions:权限被拒绝现在每次我加载终端时都会出现错误。bash_profile的内容;exportPATH=/usr/local/rbenv/bin:$PATHexportRBENV_ROOT=/usr/local/rbe

  10. ruby - 有没有办法从 ruby​​ case 语句中访问表达式? - 2

    我想从then子句中访问c​​ase语句表达式,即food="cheese"casefoodwhen"dip"then"carrotsticks"when"cheese"then"#{expr}crackers"else"mayo"end在这种情况下,expr是食物的当前值(value)。在这种情况下,我知道,我可以简单地访问变量food,但是在某些情况下,该值可能无法再访问(array.shift等)。除了将expr移出到局部变量然后访问它之外,是否有直接访问caseexpr值的方法?罗亚附注我知道这个具体示例很简单,只是一个示例场景。 最佳答案

随机推荐