优化代码逻辑嵌套

适配分区存储特性
加入 ShapeView 框架
加入通配符屏幕适配方案
加入服务器动态化配置脚本
升级第三方框架和 SDK 版本
调整 module 的存放位置
调整项目 build 的输出位置
优化和统一 maven 远程仓库配置
优化 Git 忽略规则配置
优化友盟监听器回调逻辑
优化请求成功及 token 失效写法
删除 IntentKey 类
新增 TabAdapter 类
新增 NavigationAdapter 类
新增 BrowserFragment 类
新增 ImageCropActivity 类
新增 PlayButton 自定义控件
新增 SimpleRatingBar 自定义控件
新增 DrawableTextView 自定义控件
UmengClient 新增 getDeviceOaid 方法
AppAdapter 新增 containsItem 方法
ActivityManager 新增销毁指定的 Activity 的方法
ActivityManager 新增前后台切换回调监听
ActivityManager 新增 getResumedActivity 方法
BaseFragment 新增 getApplication 方法
修复 RecyclerPagerAdapter 逻辑死循环的问题
修复 DateDialog 在 2021 年 4 月出现 31 天问题
修复 StatusLayout  无法嵌套滚动的问题
修复 BasePopupWindow 生命周期不同步的问题
修复 WrapRecyclerView 刷新位置计算不正确的问题
修复 SettingBar 类设置分割线属性导致崩溃的问题
修复 CacheDataManager 缓存计算的 Bug
扩展 ShareDialog 的分享类型
优化 CrashActivity 代码高亮逻辑
优化 BrowserView 上传图片和视频的逻辑
优化 UpdateDialog 更新内容滚动的 Bug
优化 VideoPlayActivity 没有根据视频宽高进行横竖屏调整的问题
优化 BottomSheetDialog 没有状态栏和底部导航栏没有沉浸的问题
This commit is contained in:
Android 轮子哥 2021-09-22 00:56:29 +08:00
parent 2308f895d7
commit d5c9fdaf39
483 changed files with 24227 additions and 4966 deletions

22
.gitignore vendored
View File

@ -1,13 +1,11 @@
/.gradle
/.idea
/build
*/build
/captures
/.cxx
*/.cxx
/.externalNativeBuild
._*
*.iml
.DS_Store
.gradle
.idea
.cxx
.externalNativeBuild
build
captures
._*
*.iml
.DS_Store
local.properties

Binary file not shown.

View File

@ -44,13 +44,13 @@
* [为什么没有关于列表多 type 的封装](#为什么没有关于列表多-type-的封装)
* [为什么没有用 Dagger 框架](#为什么没有用-dagger-框架)
* [这不就是一个模板工程换成我也能写一个](#这不就是一个模板工程换成我也能写一个)
* [轮子哥你怎么看待层出不穷的新技术](#轮子哥你怎么看待层出不穷的新技术)
* [假设 AndroidProject 更新了该怎么升级它](#假设-androidproject-更新了该怎么升级它)
* [为什么没有集成界面侧滑功能](#为什么没有集成界面侧滑功能)
* [为什么不用谷歌 ActivityResultContracts](#为什么不用谷歌-activityresultcontracts)
* [轮子哥你怎么看待层出不穷的新技术](#轮子哥你怎么看待层出不穷的新技术)
#### 为什么没有用 MVP
@ -68,7 +68,7 @@
* ButterKnife 最大的缺点是还会自动生成 ViewBinding 类,就算在类中只使用了一个 BindView它也会生成这个类其实这样是不太好的。
* 另外一个点,将 Android Studio 升级到 4.1 之后,会出现以下提示,这个是因为 Gradle 插件在 5.0 之后的版本View ID 将不会以常量的形式存在,所以不能将其定义在 `BindView` 注解或者在 `switch case` 块中。
* 另外一个点,将 Android Studio 升级到 4.1 之后,会出现以下提示,这个是因为 Gradle 在 5.0 之后的版本View ID 将不会以常量的形式存在,所以不能将其定义在 `BindView` 注解或者在 `switch case` 块中。
```text
Resource IDs will be non-final in Android Gradle Plugin version 5.0, avoid using them as annotation
@ -126,29 +126,31 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 关于屏幕适配方案,其实不能说头条的方案就是最好的,其实谷歌已经针对屏幕适配做了处理,就是 dp 和 sp ,而 dp 的计算转换是由屏幕的像素决定,系统只认 px 单位, dp 需要进行转换,比如 1dp 等于几个 px ,这个时候就需要基数进行转换,比如 1dp = 2px这个基数就是 2。
* ldpi1dp=0.75px
* mdpi1dp=1px
* hdpi1dp=1.5px
* xhdpi1dp=2px
* xxhdpi1dp=3px
* xxxhdpi1dp=4px
```text
ldpi1dp=0.75px
mdpi1dp=1px
hdpi1dp=1.5px
xhdpi1dp=2px
xxhdpi1dp=3px
xxxhdpi1dp=4px
```
* 这个是谷歌对屏幕适配的一种默认方式,厂商也可以根据需要去修改默认的基数,从而达到最优的显示效果。
* 谷歌的屏幕适配方案也不是百分之一百完美的,其实会存在一些需求不能满足的问题。谷歌的设计理念是屏幕越大显示的东西越多,这种想法并没有错,但有些 APP 可能对这块会有要求,希望根据屏幕大小对控件进行百分比压缩。这个时候谷歌那套适配方案的设计已经和需求完全不一致了。
* 谷歌的屏幕适配方案也不是百分之一百完美的,其实会存在一些需求不能满足的问题。谷歌的设计理念是屏幕越大显示的东西越多,这种想法并没有错,但有些 App 可能对这块会有要求,希望根据屏幕大小对控件进行百分比压缩。这个时候谷歌那套适配方案的设计已经和需求完全不一致了。
* 那什么样的 App 才会有那样的需求呢?现在手机的屏幕大多在 5 - 6寸而平板大多在 8 - 10 寸,也就是说我们只适配手机的话,只需要针对 5 - 6 寸的,并且它们的分辨率都差不多,其实用谷歌那种方案是最优的,如果我们需要适配平板的话,一般都会要求对控件进行百分比压缩,这个时候谷歌那套方案会把原先在手机显示的控件在平板上面变大一点,这样就会导致屏幕剩余的空间过大,导致控件显示出来的效果比较小,而如果采用百分比对控件压缩的方式,能比较好地控制 APP 在不同屏幕下显示的效果。
* 那什么样的 App 才会有那样的需求呢?现在手机的屏幕大多在 5 - 6寸而平板大多在 8 - 10 寸,也就是说我们只适配手机的话,只需要针对 5 - 6 寸的,并且它们的分辨率都差不多,其实用谷歌那种方案是最优的,如果我们需要适配平板的话,一般都会要求对控件进行百分比压缩,这个时候谷歌那套方案会把原先在手机显示的控件在平板上面变大一点,这样就会导致屏幕剩余的空间过大,导致控件显示出来的效果比较小,而如果采用百分比对控件压缩的方式,能比较好地控制 App 在不同屏幕下显示的效果。
* 另外谈谈我的经历,我自己之前的公司主要是做平板上面的应用,所以也用过 [AutoSize 框架](https://github.com/JessYanCoding/AndroidAutoSize),一年多的使用体验下来,发现这个框架 Bug 还算是比较多的,例如框架会偶尔出现机型适配失效,重写了 **getResources** 方法情况之后出现的情况少了一些,但是仍然还有一些奇奇怪怪的问题,这里就不一一举例了,最后总结下来就是框架还不够成熟,但是框架的思想还是很不错的。我后面换了一家公司,也是做平板应用,项目用的是用[通配符的适配方案](https://github.com/wildma/ScreenAdaptation),跟 AutoSize 相对比,没有了那些奇奇怪怪的问题,但是对项目的侵入性高。这两种方案各有优缺点,大家看着选择。
* 另外谈谈我的经历,我自己之前的公司主要是做平板上面的应用,所以也用过 [AutoSize 框架](https://github.com/JessYanCoding/AndroidAutoSize),一年多的使用体验下来,发现这个框架 Bug 还算是比较多的,例如框架会偶尔出现机型适配失效,重写了 **getResources** 方法情况之后出现的情况少了一些,但是仍然还有一些奇奇怪怪的问题,这里就不一一举例了,最后总结下来就是框架还不够成熟,但是框架的思想还是很不错的。我后面换了一家公司,也是做平板应用,项目用的是用[通配符的适配方案](https://github.com/wildma/ScreenAdaptation),跟 AutoSize 相对比,没有了那些奇奇怪怪的问题,但是代码的侵入性比较高。这两种方案各有优缺点,大家看着选择。
* 最后我对屏幕适配方案进行了总结,如果我们不适配平板的情况下,使用谷歌原生那套方案的效果是最优的;如果需要适配平板,并且在要求手机和平板显示的效果一致的时候,可以换成百分比那种适配方案。
![](picture/help/vote2.jpg)
* 这个是我认为比较好的方式,手机和平板的应用我也都做过,尽管这样我说得也不一定是最好的,大家如果有更好的想法也欢迎和我交流
* 在这块我也发起过群投票,相比谷歌的适配方案,大多数人更认同那种百分比适配方案,秉承着少数服从多数的理念,我在 AndroidProject [v13.0 版本](https://github.com/getActivity/AndroidProject/releases/tag/13.0) 加入了通配符的适配方案。虽然有一部分人不认同但是我想跟这些人说的是我的每一个决定都是十分谨慎的因为这其中涉及到许多人的利益AndroidProject 虽然是我创造的,但是它早就不是我一个人的了,而是大家的,每个重要的决定我都会考虑再三才会去做,在做决定的时候我会把大众的利益放在第一位,把自己的利益放在最后一位,所以大家唯一能做的是,相信我的选择。或许你可能觉得这样不太对,也随时欢迎你提出不同的意见给到我,我不认为自己做的决定一定都是对的,但是我会一直朝着对的方向前进
#### 字体大小为什么不用 dp 而用 sp
@ -160,14 +162,14 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
#### 为什么没有用 DialogFragment 来防止内存泄漏
* DialogFragment 的出现就是为了解决 Dialog 和 Activity 生命周期不同步导致的内存泄漏问题,在 AndroidProject 曾经引入过,也经过了很多个版本的更新迭代,不过在 [10.0](https://github.com/getActivity/AndroidProject/releases/tag/10.0) 版本后就被移除了,原因是 Dialog 虽然有坑,但是 DialogFragment 也有坑,可以说解决了一个问题又引发了各种问题。先来细数 我在 DialogFragment 上踩过的各种坑:
* DialogFragment 的出现就是为了解决 Dialog 和 Activity 生命周期不同步导致的内存泄漏问题,在 AndroidProject 曾经引入过,也经过了很多个版本的更新迭代,不过在 [10.0](https://github.com/getActivity/AndroidProject/releases/tag/10.0) 版本后就被移除了,原因是 Dialog 虽然有坑,但是 DialogFragment 也有坑,可以说解决了一个问题又引发了各种问题。先来细数我在 DialogFragment 上踩过的各种坑:
1. DialogFragment 会占用 Dialog 的 Cancel 和 Dismiss 监听,为了就是在 Dialog 消失之后将自己Fragment从 Activity 上移除,这样的操作看起很合理,但是会引发一个问题,那么就是会导致我们原先给 Dialog 设置的 Cancel 和 Dismiss 监听被覆盖掉,间接导致我们无法使用这个监听,因为 Dialog 的监听器只能有一个观察者,而 AndroidProject 前期解决这个问题的方式是:将 Dialog 的监听器使用的观察者模式,从一对一改造成一对多,也就是一个被观察者可以有很多个观察者,由此来解决这个问题。
2. DialogFragment 的显示和隐藏操作都不能在后台中进行,否则会出现一个报错 `java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState`,这个是因为 DialogFragment 的 show 和 dismiss 方法使用了 FragmentTransaction.commit 方法,这个 commit 方法会触发对 Activity 状态的检查,如果 Activity 的状态已经保存了(即已经调用了 onSaveInstanceState 方法),这个时候把 Fragment commit 到 Activity 上会抛出异常,这种场景在执行异步操作(例如请求网络)未结束前,用户手动将 App 返回到桌面,然后异步操作执行完毕,下一步就是回调异步监听器,这个时候我们的 App 已经处于后台状态,那么我们如果在监听回调中 show 或 dismiss DialogFragment那么就会触发这个异常。AndroidProject 前期对于这个问题的解决方案是重写 DialogFragment.show 方法,加一个对 Activity 的状态判断,如果 Activity 处于后台状态,那么不去调用 super.show(),但是这样会导致一个问题,虽然解决了崩溃的问题,但是又会导致 Dialog 没显示出来,而重写 DialogFragment.dismiss 方法,直接调用 dismissAllowingStateLoss 方法,因为这个方法不会去检查 Activity 的状态。虽然这种解决方式不够完美,但却是我那个时候能想到的最好方法。
3. 最后一个问题是关于 DialogFragment 屏幕旋转的问题,首先 DialogFragment 是通过自身 onCreateDialog 方法来获取 Dialog 对象的,但是如果我们直接通过外层给 DialogFragment 传入 Dialog 的对象时这样的代码逻辑貌似没有问题但是在用户进行屏幕旋转而刚好我们的应用没有固定屏幕方向时DialogFragment 对象会跟随 Activity 销毁重建,因为它本身就是一个 Fragment但是会导致之前的外层传入 Dialog 对象被回收并置空,然后再调用到 onCreateDialog 方法时,返回的是一个空对象的 Dialog那么就会直接 DialogFragment 内部引发空指针异常,而 AndroidProject 前期解决这个问题的方案是,重写 onActivityCreated赶在 onCreateDialog 方法调用之前,先判断 DialogFragment 对象内部持有的 Dialog 是否为空,如果是一个空对象,那么就将自己 dismissAllowingStateLoss 掉。
* 看过这些问题,你是不是和我一样,感觉这 DialogFragment 不是一般的坑,不过最终我放弃了使用 DialogFragment并不是因为 DialogFragment 又出现了新问题,而是我想到了更好的方案来代替 DialogFragment方案就是 Application.registerActivityLifecycleCallbacks想必大家现在已经猜到我想干啥和 DialogFragment 的作用一样,通过监听 Activity 的方式来管控 Dialog 的生命周期,但唯一不同的是,它不会出现刚刚说过 DialogFragment 的那些问题,这种方式在 AndroidProject 上迭代了几个版本过后,这期间没有发现新的问题,也没有收到别人反馈过这块的问题,证明这种方式是可行的。
#### 为什么没有用腾讯 X5 WebView
@ -185,11 +187,11 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 这个问题在前几年是一个比较火热的话题,我表示很能理解,因为新鲜的事物总是能勾起人的好奇,让人忍不住试一试,但是我先问大家一个问题,单 Activity 多 Fragment 和写多个 Activity 有什么优点?大家第一个反应应该是每写一个页面都不需要在清单文件中注册了,但是这个真的是优点吗?我可以很明确地告诉大家,我已经写了那么多句代码,不差那句在清单文件注册的代码。那么究竟什么才是对我们有价值的?我觉得就两点,一是减少前期开发的工作量,二是降低后续维护的难度。所以省那一两句有前途吗?我们是差那一两句代码的人吗?如果这种模式能够帮助我们写好代码,这个当然是有价值的,非常值得一试的,否则就是纯属瞎扯淡。不仅如此,我个人觉得这种模式有很大的弊端,会引发很多问题,例如:
1. 有的页面是全屏有的页面是非全屏,有的页面是固定竖屏有的页面是横屏,进入时怎么切换?返回时怎么切换回来?然后又该怎么去做统一的封装?
2. 不同 Fragment 之间应该怎样通讯Activity 有 onActivityResult 方法可以用,但是 Fragment 有什么方法可以用?还是全用 EventBus 来处理?如果是这样做会不会太低效了?每次都要写一个 Event 类,并且在代码中找起来是不是也不太好找?
3. 如何保证这个 Activity 被系统回收之后,然后引发重建操作,又该如何保证这个 Activity 中的多个 Fragment 之间的回退栈是否正常?假设这个 Activity 里面有 10 个Fragment一下子引发 10 个 Fragment 创建是否会对内存和性能造成影响呢?
* 如果单 Activity 多 Fragment 不能为我们创造太大的价值时,这种模式根本就不值得我们去做,因为我们最终得到的,永远抵不上付出的。
#### 为什么没有用 ConstraintLayout 来写布局
@ -197,7 +199,7 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 大家如果有仔细观察的话,会发现 AndroidProject 其实没有用到 ConstraintLayout 布局在这里谈谈我对这个布局的看法约束布局有一个优点没有布局嵌套所以能减少测量次数从而提升布局绘制的速度但是优点也是它的缺点正是因为没有布局嵌套View 也就没有层级概念,所以它需要定义很多 ViewID 来约束相邻的 View 的位置,就算这个 View 我们在 Java 代码中没有用到,但是在约束布局中还是要定义。这样带来的弊端有几个:
1. 我们每次都要想好这个 ViewID 的名称叫什么,这个就有点烧脑筋了,既要符合代码规范,也要明确和突出其作用。
2. 要考虑好每个 View 上下左右之间的约束关系,否则就会容易出现越界的情况,例如一个 TextView 设计图上有 5 个字,但是后台返回了 10 个字,这个时候 TextView 的控件宽度会被拉长,如果没有设置好右边的约束,极有可能出现遮盖右边 View 的情况。
3. View 之间的关系会变得复杂起来,具体表现为布局一旦发生变更,例如删除或增加某一个 View都会影响整个 ConstraintLayout 布局,因为很多约束关系会因此发生改变,并且在布局预览中就会变得错乱起来,简单通俗点来讲就是,你拆了一块瓦,很可能会导致房倒屋塌。
@ -213,9 +215,9 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* AndroidProject 其实一直有这样做,把很多组件都拆成了独立的框架,例如:权限请求框架 [XXPermissions](https://github.com/getActivity/XXPermissions),网络请求框架 [EasyHttp](https://github.com/getActivity/EasyHttp)、吐司框架 [ToastUtils](https://github.com/getActivity/ToastUtils) 等等,我都是将它抽离在 AndroidProject 之外,作为一个单独的开源项目进行开发和维护,至于说为什么还有一些代码没有抽取出来,主要原因有几点:
1. 和业务的耦合性高,例如 Dialog 组件引用了很多项目的基类,例如 **BaseDialog**、**BaseAdapter** 等
2. 业务有定制化需求,因为 Dialog 的 UI 风格要跟随项目的设计走,所以代码如果在项目中,修改起来会非常方便,如果抽取到框架中,要怎么修改和统一 UI 风格呢?我个人认为框架不适合做 UI 定制化,因为每个产品的设计风格都不一样,就算开放再多的 API 给外部调用的人设置 UI 风格,也无法满足所有人的需求。
* 基于以上几点,我并不认为所有的东西都适合抽取成框架给大家用,有些东西还是跟随 **AndroidProject** 一起更新比较好。当然像权限请求这种东西,我个人觉得抽成框架是比较合适的,因为它和业务的关联性不大,更重要的是,如果某一天你觉得 **XXPermissions** 做得不够好,你随时可以在 **AndroidProject** 替换掉它,并且整个过程不需要太大的改动。
#### 为什么最低兼容到 Android 5
@ -237,7 +239,7 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 我想问大家一个问题,这两个框架搭配起来好用吗?可能大家的回答都不一致,但是我个人觉得不好用,接下来让我们分析一下 Retrofit 有什么问题:
1. 功能太少:如果你用过 Retrofit一说到功能这个词我相信你的脑海中第一个想到能不能用 OkHttp 的拦截器来实现,没错常用的功能 Retrofit 都没有封装,例如最常用的功能有:添加通用请求头和参数、请求和响应的日志打印、动态 Host 及多域名配置Retrofit 统统都没有,需要自己去实现。有时候我在想,如果 Retrofit 没有 **Square 公司背书**,现在应该估计不会有多少人用吧。
2. 不够灵活Retrofit 其实是支持上传的,但是有一个缺点,不能获取进度监听,只能获取到成功和失败。当然网上也有一些解决方案,例如通过设置拦截器,来对 RequestBody 进行二次包装来获取上传进度,但是整个实现的过程十分地麻烦,在 Retrofit 上也没有给出一个好的方案,明明可以由 Retrofit 来做的事,为什么要分发到每个开发者上面。
3. 学习成本高Retrofit 主要的学习成本来源于它的注解,我现在把它里面所有注解罗列出来:@Url、@Body、@Field、@FieldMap、@FormUrlEncoded、@Header、@HeaderMap、@Headers、@HTTP、@Multipart、@Part、@PartMap、@Path、@Query、@QueryMap、@QueryName、@Streaming。我们了解过多少个注解的作用这个时候大多数人肯定会说我都是按照别人的写法复制一遍具体有什么作用我还真的不知道。其实这个是学习成本高带来的弊端人们往往只会记住最常用的那几个。
@ -260,7 +262,7 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 常用的图片加载框架无非就两种,最常用的是 Glide其次是 Fresco。我曾做过一个技术调研
![](picture/help/image.jpg)
![](picture/help/vote1.jpg)
* 无疑 Glide 已成大家最喜爱的图片加载框架,当然也有人使用 Fresco但是占比极少。
@ -280,10 +282,6 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 原生的 RecyclerView.Adapter 本身就支持多 type只需要重写适配器的 getItemType 方法即可,具体用法不做过多介绍。
#### 为什么没有用 Dagger 框架
* 框架的学习和使用成本极高,但总体收益不高,不适用于大部分人,所以不会考虑加入。
#### 这不就是一个模板工程换成我也能写一个
* 想把 AndroidProject 做出来并不难,我当时只花了一两个星期,而做好它需要无限的时间和精力,我花了两年多的时间仍然还在半路之上,尽管有很多人认为它很好用,没有任何 Bug但是在我看来还不够因为每个人衡量标准的程度不同我的标准是随着时间的推移和技术的提升而不断提高。具体付出了多少努力[可以先让我们看一组数据](https://github.com/getActivity/AndroidProject/graphs/contributors)
@ -292,9 +290,108 @@ ActivityXxxxBinding binding = DataBindingUtil.setContentView(this, R.layout.acti
* 与其说 AndroidProject 做的是模板工程,但实际我在架构设计上花费的时间和精力会更多,其实这两者我都有在做,因为我的目的只有一个,能够帮助大家更好地开发和维护项目。具体 AndroidProject 在代码设计上有什么亮点,这里我建议你看一下里面的代码,我相信你看完后会有收获的,后面我可能也会出一篇文章具体讲述 AndroidProject 的亮点。
#### 假设 AndroidProject 更新了该怎么升级它
* 原因和解释首先纠正一点AndroidProject 严格意义上来说,不是框架一种,而属于架构一种,架构升级本身就是一件大事,并且存在很多未知的风险点,我不推荐已使用 AndroidProject 开发的项目去做升级,因为开发和测试的成本极其高,间接能为业务带来价值其实很低,很多时候我知道大家很喜欢 AndroidProject 的代码,想用到公司项目中去,但是我仍然不推荐你那么做,假设这是你的个人项目可以那么做,但是公司项目最好不要,因为公司和你都是要靠这个项目赚钱,谁也不希望项目出现问题,如果是公司要开发人员重构公司项目,也可以考虑那么做,毕竟这个时候的风险公司已经承担了大部分了,接下来的话只需要服从公司安排即可。
* 更新的方式:由于 AndroidProject 不是一个单独的框架那么简单,无法通过更新远程依赖的方式进行升级,所以只能通过替换代码的形式进行更新,需要注意的是,代码覆盖完需要经过严格的自测及测试,测试是做这件事情的关键流程,需要重视起来,对每一处功能进行详细测试,一定要详细,特别涉及到主流程的功能。
#### 为什么不用谷歌 ActivityResultContracts
* ActivityResultContract是 Activity 1.2.0-alpha02 和 Fragment 1.3.0-alpha02 中新追加的新 API但是在此之前 AndroidProject 早已经对 onActivityResult 回调进行了封装,详情请见 BaseActivity
```java
public abstract class BaseActivity extends AppCompatActivity {
/** Activity 回调集合 */
private SparseArray<OnActivityCallback> mActivityCallbacks;
/**
* startActivityForResult 方法优化
*/
public void startActivityForResult(Class<? extends Activity> clazz, OnActivityCallback callback) {
startActivityForResult(new Intent(this, clazz), null, callback);
}
public void startActivityForResult(Intent intent, OnActivityCallback callback) {
startActivityForResult(intent, null, callback);
}
public void startActivityForResult(Intent intent, @Nullable Bundle options, OnActivityCallback callback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new SparseArray<>(1);
}
// 请求码必须在 2 的 16 次方以内
int requestCode = new Random().nextInt((int) Math.pow(2, 16));
mActivityCallbacks.put(requestCode, callback);
startActivityForResult(intent, requestCode, options);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
OnActivityCallback callback;
if (mActivityCallbacks != null && (callback = mActivityCallbacks.get(requestCode)) != null) {
callback.onActivityResult(resultCode, data);
mActivityCallbacks.remove(requestCode);
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
public interface OnActivityCallback {
/**
* 结果回调
*
* @param resultCode 结果码
* @param data 数据
*/
void onActivityResult(int resultCode, @Nullable Intent data);
}
}
```
* 至于要不要换成谷歌出的那种呢?我们先来对比这两种的方式的用法
```java
// Google 的用法
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
Intent data = result.getData();
int resultCode = result.getResultCode();
}
}).launch(new Intent(this, HomeActivity.class));
```
---
```java
// AndroidProject 的用法
startActivityForResult(HomeActivity.class, new OnActivityCallback() {
@Override
public void onActivityResult(int resultCode, @Nullable Intent data) {
}
});
```
* 对这两种经过对比,得出结论如下:
1. 谷歌原生的没有 AndroidProject 封装得那么人性化,谷歌那种方式调用稍微麻烦一点
2. 谷歌那种方式直接集成进 AndroidX 包的,要比直接在 BaseActivity 中封装要好
3. AndroidProject 封装 onActivityResult 回调至少要比谷歌要早一两年,并非谷歌之后的产物
4. 之前使用 AndroidProject 的人群已经习惯和记忆了那种方式,所以 API 不能删也不能改
* 所以并不是我不想用,而是谷歌封装得还不够好,至少在我看来还不够好,抛去 AndroidProject 封装的时间早不说,谷歌封装出来的效果也是强差人意,我感觉谷歌工程师的封装得越来越敷衍了,看起来像是在完成任务,而不是在做好一件事。
#### 轮子哥你怎么看待层出不穷的新技术
* 新东西的出现总能引起别人的好奇和尝试,但是我建议有兴趣的人可以学一下,但是如果要应用到项目中,我个人建议还是慎重,因为纵观历史,我们不难发现,技术创新虽然很受欢迎,但是大多数都经不住时间的考验,最终一个个气尽倒下,这是因为很多新技术,表面看起来很美好,但实际上一入坑深似海。当然也有一些优秀的技术创新活了下来,但是毕竟占的是少数。
* 新东西的出现总能引起别人的好奇和尝试,但是我建议有兴趣的人可以学一下,但是如果要应用到项目中,我个人建议还是慎重,因为纵观历史,我们不难发现,技术创新虽然很受欢迎,但是大多数都经不住时间的考验,最终一个个气尽倒下,这是因为很多新技术,表面看起来很美好,但实际上一入坑深似海。当然也有一些优秀的技术创新活了下来,但是毕竟占的是少数。
* 谈谈我对新技术的看法,首先我会思考这种新技术能解决什么痛点,这点非常重要,再好的技术创新,也必须得创造价值,否则就是在扯淡。有人肯定会问,什么样的技术才算有价值?对于我们 Android 程序员来讲,无非就围绕两点,开发和维护。要么在前期开发上,能发挥很大的作用,要么在后续维护上面,能体现它的优势。

View File

@ -1,6 +1,6 @@
Apache License
Version 2.0, January 2004
Version 2.0, October 2018
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

276
README.md
View File

@ -1,141 +1,149 @@
# 安卓技术中台
* 码云地址:[Gitee](https://gitee.com/getActivity/AndroidProject)
* Kotlin 版本:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)
* 博客地址:[但愿人长久,搬砖不再有](https://www.jianshu.com/p/77dd326f21dc)
* 当我们日复一日年复一年的搬砖的时候,你是否曾想过提升一下开发效率,如果一个通用的架构摆在你的面前,你还会选择自己搭架构么,但是搭建出一个好的架构并非易事,有多少人愿意选择去做,还有多少人选择努力去做好,可能寥寥无几,但是你今天看到的,正是你所想要的,一个真正能解决你开发新项目时最大痛点的架构工程,你不需要再麻木 Copy 原有旧项目的代码,只需改动少量代码就能得到想要的效果,你会发现开发新项目其实是一件很快乐的事。
* AndroidProject 已维护两年多的时间,几乎耗尽我所有的业余时间,里面的代码改了再改,改了又改,不断 Review、不断创新、不断改进、不断测试、不断优化每天都在重复这些枯燥的步骤但是只有这样才能把这件事做好因为我相信把同样一件事重复做迟早有一天可以做好。
* 已经正式投入到多个公司项目实践中,暂时没有发现任何问题或者 Bug[点击下载 Apk 体验](AndroidProject.apk),又或者扫码下载
![](picture/demo_code.png)
#### 常用界面
![](picture/activity/1.jpg) ![](picture/activity/2.jpg) ![](picture/activity/3.jpg)
![](picture/activity/4.jpg) ![](picture/activity/5.jpg) ![](picture/activity/6.jpg)
![](picture/activity/7.jpg) ![](picture/activity/8.jpg) ![](picture/activity/9.jpg)
![](picture/activity/10.jpg) ![](picture/activity/11.jpg) ![](picture/activity/12.jpg)
![](picture/activity/13.jpg) ![](picture/activity/14.jpg) ![](picture/activity/15.jpg)
![](picture/activity/16.jpg) ![](picture/activity/17.jpg) ![](picture/activity/18.jpg)
![](picture/activity/19.jpg) ![](picture/activity/20.jpg) ![](picture/activity/21.jpg)
![](picture/activity/22.jpg) ![](picture/activity/23.jpg) ![](picture/activity/24.jpg)
![](picture/activity/25.jpg) ![](picture/activity/26.jpg) ![](picture/activity/27.jpg)
![](picture/activity/28.jpg) ![](picture/activity/29.jpg)
------
![](picture/activity/30.jpg)
![](picture/activity/31.jpg)
![](picture/activity/32.jpg)
![](picture/activity/33.jpg)
![](picture/activity/34.jpg)
![](picture/activity/35.jpg)
![](picture/activity/36.jpg)
![](picture/activity/37.jpg)
#### 常用对话框
![](picture/dialog/1.jpg) ![](picture/dialog/2.jpg) ![](picture/dialog/3.jpg)
![](picture/dialog/4.jpg) ![](picture/dialog/5.jpg) ![](picture/dialog/6.jpg)
![](picture/dialog/7.jpg) ![](picture/dialog/8.jpg) ![](picture/dialog/9.jpg)
![](picture/dialog/10.jpg) ![](picture/dialog/11.jpg) ![](picture/dialog/12.jpg)
![](picture/dialog/13.jpg) ![](picture/dialog/14.jpg) ![](picture/dialog/15.jpg)
![](picture/dialog/16.jpg) ![](picture/dialog/17.jpg) ![](picture/dialog/18.jpg)
#### 动图欣赏
![](picture/gif/1.gif) ![](picture/gif/2.gif) ![](picture/gif/3.gif)
![](picture/gif/4.gif) ![](picture/gif/5.gif) ![](picture/gif/6.gif)
#### 项目亮点
* App 优化:已经进行了全面的内存优化、布局优化、代码优化、瘦身优化,并且对结果进行了严格的长久测试。
* 代码规范:参照 Android SDK 、Support 源码和参考阿里巴巴的代码规范文档对代码进行命名,并对难点代码进行了注释,对重点代码进行了说明。
* 代码统一:对项目中常见的代码进行了封装,或是封装到基类中、或是封装到工具类中、或者封装到框架中,不追求过度封装,根据实际场景和代码维护性考虑,尽量保证同一个功能的代码在项目中不重复。
* 敏捷开发:一个 App 大概率会出现的功能已经写好对项目的敏捷开发起到了至关重要的作用可用于新项目开发或者旧项目重构可将开发周期缩短近一半的时间并且后续不会因为前期的快速开发而留下成堆的技术遗留问题万丈高楼平地起AndroidProject 属于基建工程,而在软件行业我们称之为技术中台。
* 无任何瑕疵:对小屏手机、全面屏手机、带虚拟按键手机进行了适配和优化,确保每一个界面细节都能处理到位、每一个功能细节都能符合大众的需求、乃至每一行代码都能贴合 Android 程序员的审美观。
# 安卓技术中台
* 项目地址:[Github](https://github.com/getActivity/AndroidProject)、[码云](https://gitee.com/getActivity/AndroidProject)
* Kotlin 版本:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)
* 博客地址:[但愿人长久,搬砖不再有](https://www.jianshu.com/p/77dd326f21dc)
* 当我们日复一日年复一年的搬砖的时候,你是否曾想过提升一下开发效率,如果一个通用的架构摆在你的面前,你还会选择自己搭架构么,但是搭建出一个好的架构并非易事,有多少人愿意选择去做,还有多少人选择努力去做好,可能寥寥无几,但是你今天看到的,正是你所想要的,一个真正能解决你开发新项目时最大痛点的架构工程,你不需要再麻木 Copy 原有旧项目的代码,只需改动少量代码就能得到想要的效果,你会发现开发新项目其实是一件很快乐的事。
* AndroidProject 已维护三年多的时间,几乎耗尽我所有的业余时间,里面的代码改了再改,改了又改,不断 Review、不断创新、不断改进、不断测试、不断优化每天都在重复这些枯燥的步骤但是只有这样才能把这件事做好因为我相信把同样一件事重复做迟早有一天可以做好。
* 已经正式投入到多个公司项目实践中,暂时没有发现任何问题或者 Bug[点击下载 Apk 体验](AndroidProject.apk),又或者扫码下载
![](picture/demo_code.png)
#### 常用界面
![](picture/activity/1.jpg) ![](picture/activity/2.jpg) ![](picture/activity/3.jpg)
![](picture/activity/4.jpg) ![](picture/activity/5.jpg) ![](picture/activity/6.jpg)
![](picture/activity/7.jpg) ![](picture/activity/8.jpg) ![](picture/activity/9.jpg)
![](picture/activity/10.jpg) ![](picture/activity/11.jpg) ![](picture/activity/12.jpg)
![](picture/activity/13.jpg) ![](picture/activity/14.jpg) ![](picture/activity/15.jpg)
![](picture/activity/16.jpg) ![](picture/activity/17.jpg) ![](picture/activity/18.jpg)
![](picture/activity/19.jpg) ![](picture/activity/20.jpg) ![](picture/activity/21.jpg)
![](picture/activity/22.jpg) ![](picture/activity/23.jpg) ![](picture/activity/24.jpg)
![](picture/activity/25.jpg) ![](picture/activity/26.jpg) ![](picture/activity/27.jpg)
------
![](picture/activity/28.jpg)
![](picture/activity/29.jpg)
![](picture/activity/30.jpg)
![](picture/activity/31.jpg)
![](picture/activity/32.jpg)
![](picture/activity/33.jpg)
![](picture/activity/34.jpg)
![](picture/activity/35.jpg)
![](picture/activity/36.jpg)
#### 常用对话框
![](picture/dialog/1.jpg) ![](picture/dialog/2.jpg) ![](picture/dialog/3.jpg)
![](picture/dialog/4.jpg) ![](picture/dialog/5.jpg) ![](picture/dialog/6.jpg)
![](picture/dialog/7.jpg) ![](picture/dialog/8.jpg) ![](picture/dialog/9.jpg)
![](picture/dialog/10.jpg) ![](picture/dialog/11.jpg) ![](picture/dialog/12.jpg)
![](picture/dialog/13.jpg) ![](picture/dialog/14.jpg) ![](picture/dialog/15.jpg)
![](picture/dialog/16.jpg) ![](picture/dialog/17.jpg) ![](picture/dialog/18.jpg)
#### 动图欣赏
![](picture/gif/1.gif) ![](picture/gif/2.gif) ![](picture/gif/3.gif)
![](picture/gif/4.gif) ![](picture/gif/5.gif) ![](picture/gif/6.gif)
![](picture/gif/7.gif) ![](picture/gif/8.gif) ![](picture/gif/9.gif)
![](picture/gif/10.gif) ![](picture/gif/11.gif) ![](picture/gif/12.gif)
#### 项目亮点
* App 优化:已经进行了全面的内存优化、布局优化、代码优化、瘦身优化,并且对结果进行了严格的长久测试。
* 代码规范:参照 Android SDK 、Support 源码和参考阿里巴巴的代码规范文档对代码进行命名,并对难点代码进行了注释,对重点代码进行了说明。
* 代码统一:对项目中常见的代码进行了封装,或是封装到基类中、或是封装到工具类中、或者封装到框架中,不追求过度封装,根据实际场景和代码维护性考虑,尽量保证同一个功能的代码在项目中不重复。
* 敏捷开发:一个 App 大概率会出现的功能已经写好对项目的敏捷开发起到了至关重要的作用可用于新项目开发或者旧项目重构可将开发周期缩短近一半的时间并且后续不会因为前期的快速开发而留下成堆的技术遗留问题万丈高楼平地起AndroidProject 属于基建工程,而在软件行业我们称之为技术中台。
* 无任何瑕疵:对小屏手机、全面屏手机、带虚拟按键手机进行了适配和优化,确保每一个界面细节都能处理到位、每一个功能细节都能符合大众的需求、乃至每一行代码都能贴合 Android 程序员的审美观。
* 兼容性优良:在此感谢开源道路上给予我支持和帮助的小伙伴,一个人一台机在兼容性面前无能为力,而在几百人几百台机面前却不是问题。如果没有这些的测试,有些问题我一个人可能这辈子都发现不了,纵使代码写得再好,逻辑再严谨,没有经过大众的验证,无异于纸上谈兵。
#### [代码规范文档请点击这里查看](https://github.com/getActivity/AndroidCodeStandard)
#### [常见问题解答请点击这里查看](HelpDoc.md)
#### 作者的其他开源项目
* 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp) (已集成)
* 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions) (已集成)
* 吐司框架:[ToastUtils](https://github.com/getActivity/ToastUtils) (已集成)
* 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar) (已集成)
* Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory) (已集成)
* 悬浮窗框架:[XToast](https://github.com/getActivity/XToast) (未集成)
* 国际化框架:[MultiLanguages](https://github.com/getActivity/MultiLanguages) (未集成)
* 日志查看框架:[Logcat](https://github.com/getActivity/Logcat) (未集成)
* 优秀的代码设计AndroidProject 对 startActivityForResult 的设计进行了改良,使得可以直接在方法上传入监听对象,这样我们就不需要重写 onActivityResult 方法来拿到回调,另外原生的 startActivityForResult 还需要传 requestCode 参数,而 AndroidProject 会自动帮你生成这个 requestCode 码,并在 onActivityResult 进行判断,如果满足条件,那么就会回调外层传入的监听对象。然而这只是冰山一角,更多优秀的代码设计还需要你通过阅读 AndroidProject 源码的形式来发掘,在这里不再细说。
#### [代码规范文档请点击这里查看](https://github.com/getActivity/AndroidCodeStandard)
#### [常见问题解答请点击这里查看](HelpDoc.md)
#### 作者的其他开源项目
* 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp) (已集成)
* 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions) (已集成)
* 吐司框架:[ToastUtils](https://github.com/getActivity/ToastUtils) (已集成)
* 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar) (已集成)
* Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory) (已集成)
* Shape 框架:[ShapeView](https://github.com/getActivity/ShapeView) (已集成)
* 悬浮窗框架:[XToast](https://github.com/getActivity/XToast) (未集成)
* 国际化框架:[MultiLanguages](https://github.com/getActivity/MultiLanguages) (未集成)
* 日志查看框架:[Logcat](https://github.com/getActivity/Logcat) (未集成)
#### 微信公众号Android轮子哥
![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/official_ccount.png)
#### Android 技术分享 QQ 群78797078
#### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:
![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png) ![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_wechat.png)
#### [点击查看捐赠列表](https://github.com/getActivity/Donate)
## License
```text
Copyright 2018 Huang JinQun
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#### Android 技术分享 QQ 群78797078
#### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:
![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png) ![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_wechat.png)
#### [点击查看捐赠列表](https://github.com/getActivity/Donate)
## License
```text
Copyright 2018 Huang JinQun
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

View File

@ -1,211 +1,216 @@
apply plugin : 'com.android.application'
apply plugin : 'android-aspectjx'
apply from : '../config.gradle'
// Android https://github.com/getActivity/AndroidCodeStandard
android {
// https://developer.android.google.cn/guide/topics/resources/providing-resources
defaultConfig {
// https://www.jianshu.com/p/17327e191d2e
applicationId 'com.hjq.demo'
//
resConfigs 'zh'
// xxhdpi 1920 * 1080
resConfig 'xxhdpi'
//
proguardFiles 'proguard-sdk.pro', 'proguard-app.pro'
}
// Apk https://www.jianshu.com/p/a1f8e5896aa2
signingConfigs {
config {
storeFile file(StoreFile)
storePassword StorePassword
keyAlias KeyAlias
keyPassword KeyPassword
}
}
buildTypes {
debug {
//
applicationIdSuffix '.debug'
//
debuggable true
jniDebuggable true
//
zipAlignEnabled false
//
shrinkResources false
//
minifyEnabled false
//
signingConfig signingConfigs.config
//
buildConfigField('boolean', 'LOG_ENABLE', 'true')
// BuglyId
buildConfigField('String', 'BUGLY_ID', '"请自行替换 Bugly 上面的 AppID"')
//
buildConfigField('String', 'HOST_URL', '"https://www.test.baidu.com/"')
//
manifestPlaceholders = ['app_name' : '安卓技术中台 Debug 版']
// so
ndk {
abiFilters 'armeabi-v7a'
}
}
preview.initWith(debug)
preview {
applicationIdSuffix ''
//
manifestPlaceholders = ['app_name' : '安卓技术中台 Preview 版']
}
release {
//
debuggable false
jniDebuggable false
//
zipAlignEnabled true
//
shrinkResources true
//
minifyEnabled true
//
signingConfig signingConfigs.config
//
buildConfigField('boolean', 'LOG_ENABLE', 'false')
// BuglyId
buildConfigField('String', 'BUGLY_ID', '"请自行替换 Bugly 上面的 AppID"')
//
buildConfigField('String', 'HOST_URL', '"https://www.baidu.com/"')
//
manifestPlaceholders = ['app_name' : '@string/app_name']
// so Bugly
ndk {
// armeabi0%
// armeabi-v7a10%
// arm64-v8a95%
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
packagingOptions {
//
exclude 'META-INF/*******'
}
// AOP exclude include
aspectjx {
// Gson LeakCanary AOP
// exclude 'androidx', 'com.google', 'com.squareup', 'org.apache', 'com.alipay', 'com.taobao', 'versions.9'
// AOP
include 'com.hjq.demo'
//
// java.util.zip.ZipExceptionCause: zip file is empty
// ClassNotFoundException: Didn't find class on path: DexPathList
}
applicationVariants.all { variant ->
// Apk
variant.outputs.all { output ->
outputFileName = rootProject.getName() + '_v' + variant.versionName + '_' + variant.buildType.name
if (variant.buildType.name == buildTypes.release.getName()) {
outputFileName += '_' + new Date().format('MMdd')
}
outputFileName += '.apk'
}
}
}
// api implementation https://www.jianshu.com/p/8962d6ba936e
dependencies {
//
implementation project(':base')
//
implementation project(':widget')
//
implementation project(':umeng')
// https://github.com/getActivity/XXPermissions
implementation 'com.hjq:xxpermissions:9.8'
// https://github.com/getActivity/TitleBar
implementation 'com.hjq:titlebar:8.2'
// https://github.com/getActivity/ToastUtils
implementation 'com.hjq:toast:8.8'
// https://github.com/getActivity/EasyHttp
implementation 'com.hjq:http:9.0'
// OkHttp https://github.com/square/okhttp
// noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
// Json https://github.com/google/gson
implementation 'com.google.code.gson:gson:2.8.6'
// Gson https://github.com/getActivity/GsonFactory
implementation 'com.hjq.gson:factory:3.0'
// AOP https://mvnrepository.com/artifact/org.aspectj/aspectjrt
implementation 'org.aspectj:aspectjrt:1.9.6'
// https://github.com/bumptech/glide
// 使https://muyangmin.github.io/glide-docs-cn/
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
// https://github.com/gyf-dev/ImmersionBar
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
// ImageViewhttps://github.com/chrisbanes/PhotoView
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
// Bugly https://bugly.qq.com/docs/user-guide/instruction-manual-android/?v=20190418140644
implementation 'com.tencent.bugly:crashreport:3.3.3'
implementation 'com.tencent.bugly:nativecrashreport:3.7.700'
// https://github.com/airbnb/lottie-android
// https://lottiefiles.comhttps://icons8.com/animated-icons
implementation 'com.airbnb.android:lottie:3.6.1'
// https://github.com/scwang90/SmartRefreshLayout
implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3'
implementation 'com.scwang.smart:refresh-header-material:2.0.3'
// https://github.com/JakeWharton/timber
implementation 'com.jakewharton.timber:timber:4.7.1'
// https://github.com/ongakuer/CircleIndicator
implementation 'me.relex:circleindicator:2.1.4'
// https://github.com/square/leakcanary
// noinspection GradleDependency
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
// noinspection GradleDependency
previewImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
// https://github.com/getActivity/MultiLanguages
// https://github.com/getActivity/XToast
// https://github.com/getActivity/Logcat
// https://github.com/Blankj/AndroidUtilCode
// https://github.com/bingoogolapple/BGABanner-Android
// https://github.com/bingoogolapple/BGAQRCode-Android
// https://github.com/sunfusheng/MarqueeView
// https://www.jianshu.com/p/f1f888e4a35f
// https://github.com/JessYanCoding/AndroidAutoSize
// https://github.com/Curzibn/Luban
// https://github.com/leavesC/DoKV
// https://github.com/Cuieney/RxPay
// https://github.com/Meituan-Dianping/walle
// http://msa-alliance.cn/col.jsp?id=120
apply plugin : 'com.android.application'
apply plugin : 'android-aspectjx'
apply from : '../common.gradle'
// Android https://github.com/getActivity/AndroidCodeStandard
android {
// https://developer.android.google.cn/guide/topics/resources/providing-resources
defaultConfig {
// https://www.jianshu.com/p/17327e191d2e
applicationId 'com.hjq.demo'
//
resConfigs 'zh'
// xxhdpi 1920 * 1080
resConfigs 'xxhdpi'
//
proguardFiles 'proguard-sdk.pro', 'proguard-app.pro'
//
buildConfigField('boolean', 'LOG_ENABLE', '' + LOG_ENABLE + '')
// BuglyId
buildConfigField('String', 'BUGLY_ID', '"' + BUGLY_ID + '"')
//
buildConfigField('String', 'HOST_URL', '"' + HOST_URL + '"')
}
// Apk https://www.jianshu.com/p/a1f8e5896aa2
signingConfigs {
config {
storeFile file(StoreFile)
storePassword StorePassword
keyAlias KeyAlias
keyPassword KeyPassword
}
}
// https://developer.android.google.cn/studio/build/build-variants
buildTypes {
debug {
//
applicationIdSuffix '.debug'
//
debuggable true
jniDebuggable true
//
zipAlignEnabled false
//
shrinkResources false
//
minifyEnabled false
//
signingConfig signingConfigs.config
//
addManifestPlaceholders([
'app_name' : '安卓技术中台 Debug 版'
])
// so
ndk {
abiFilters 'armeabi-v7a'
}
}
preview.initWith(debug)
preview {
applicationIdSuffix ''
//
addManifestPlaceholders([
'app_name' : '安卓技术中台 Preview 版'
])
}
release {
//
debuggable false
jniDebuggable false
//
zipAlignEnabled true
//
shrinkResources true
//
minifyEnabled true
//
signingConfig signingConfigs.config
//
addManifestPlaceholders([
'app_name' : '@string/app_name'
])
// so Bugly
ndk {
// armeabi0%
// armeabi-v7a10%
// arm64-v8a95%
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
packagingOptions {
//
exclude 'META-INF/*******'
}
// AOP exclude include
//
// java.util.zip.ZipExceptionCause: zip file is empty
// ClassNotFoundException: Didn't find class on path: DexPathList
aspectjx {
// Gson LeakCanary AOP
// exclude 'androidx', 'com.google', 'com.squareup', 'org.apache', 'com.alipay', 'com.taobao', 'versions.9'
// AOP
include android.defaultConfig.applicationId
}
applicationVariants.all { variant ->
// apk
variant.outputs.all { output ->
outputFileName = rootProject.getName() + '_v' + variant.versionName + '_' + variant.buildType.name
if (variant.buildType.name == buildTypes.release.getName()) {
outputFileName += '_' + new Date().format('MMdd')
}
outputFileName += '.apk'
}
}
}
// https://developer.android.google.cn/studio/build/dependencies
// api implementation https://www.jianshu.com/p/8962d6ba936e
dependencies {
//
implementation project(':library:base')
//
implementation project(':library:widget')
//
implementation project(':library:umeng')
// https://github.com/getActivity/XXPermissions
implementation 'com.github.getActivity:XXPermissions:12.3'
// https://github.com/getActivity/TitleBar
implementation 'com.github.getActivity:TitleBar:9.2'
// https://github.com/getActivity/ToastUtils
implementation 'com.github.getActivity:ToastUtils:9.5'
// https://github.com/getActivity/EasyHttp
implementation 'com.github.getActivity:EasyHttp:10.2'
// OkHttp https://github.com/square/okhttp
// noinspection GradleDependency
implementation 'com.squareup.okhttp3:okhttp:3.12.13'
// Json https://github.com/google/gson
implementation 'com.google.code.gson:gson:2.8.8'
// Gson https://github.com/getActivity/GsonFactory
implementation 'com.github.getActivity:GsonFactory:5.2'
// Shape https://github.com/getActivity/ShapeView
implementation 'com.github.getActivity:ShapeView:6.0'
// AOP https://mvnrepository.com/artifact/org.aspectj/aspectjrt
implementation 'org.aspectj:aspectjrt:1.9.6'
// https://github.com/bumptech/glide
// 使https://github.com/Muyangmin/glide-docs-cn
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
// https://github.com/gyf-dev/ImmersionBar
implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
// ImageViewhttps://github.com/Baseflow/PhotoView
implementation 'com.github.Baseflow:PhotoView:2.3.0'
// Bugly https://bugly.qq.com/docs/user-guide/instruction-manual-android/?v=20190418140644
implementation 'com.tencent.bugly:crashreport:3.4.4'
implementation 'com.tencent.bugly:nativecrashreport:3.9.2'
// https://github.com/airbnb/lottie-android
// https://lottiefiles.comhttps://icons8.com/animated-icons
implementation 'com.airbnb.android:lottie:4.1.0'
// https://github.com/scwang90/SmartRefreshLayout
implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3'
implementation 'com.scwang.smart:refresh-header-material:2.0.3'
// https://github.com/JakeWharton/timber
implementation 'com.jakewharton.timber:timber:4.7.1'
// https://github.com/ongakuer/CircleIndicator
implementation 'me.relex:circleindicator:2.1.6'
// MMKVhttps://github.com/Tencent/MMKV
implementation 'com.tencent:mmkv-static:1.2.10'
// https://github.com/square/leakcanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
previewImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
// https://github.com/getActivity/MultiLanguages
// https://github.com/getActivity/XToast
// https://github.com/getActivity/Logcat
// https://github.com/Blankj/AndroidUtilCode
// https://github.com/bingoogolapple/BGABanner-Android
// https://github.com/bingoogolapple/BGAQRCode-Android
// https://github.com/sunfusheng/MarqueeView
// https://www.jianshu.com/p/f1f888e4a35f
// https://github.com/leavesC/DoKV
// https://github.com/Meituan-Dianping/walle
// http://msa-alliance.cn/col.jsp?id=120
// https://github.com/donkingliang/ConsecutiveScroller
// https://github.com/huage2580/PermissionMonitor
}

View File

@ -2,20 +2,20 @@
#-ignorewarning
# 混淆保护自己项目的部分代码以及引用的第三方jar包
#-libraryjars libs/umeng-analytics-v5.2.4.jar
#-libraryjars libs/xxxxxxxxx.jar
# 不混淆这些包下的字段名
-keepclassmembernames class com.hjq.demo.http.request.** {
# 不混淆这个包下的类
-keep class com.hjq.demo.http.api.** {
<fields>;
}
-keepclassmembernames class com.hjq.demo.http.response.** {
-keep class com.hjq.demo.http.response.** {
<fields>;
}
-keepclassmembernames class com.hjq.demo.http.model.** {
-keep class com.hjq.demo.http.model.** {
<fields>;
}
# 不混淆被 DebugLog 注解的方法信息
# 不混淆被 Log 注解的方法信息
-keepclassmembernames class ** {
@com.hjq.demo.aop.DebugLog <methods>;
@com.hjq.demo.aop.Log <methods>;
}

View File

@ -1,240 +1,280 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 清单文件合并指引https://developer.android.google.cn/studio/build/manifest-merge?hl=zh-cn -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hjq.demo">
<!-- 网络相关 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 外部存储 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<!-- 拍照权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 安装权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- 定位权限(用于 WebView 定位)-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Application 节点详解https://developer.android.google.cn/guide/topics/manifest/application-element -->
<!-- Activity 节点详解https://developer.android.google.cn/guide/topics/manifest/activity-element -->
<application
android:name=".app.AppApplication"
android:allowBackup="false"
android:icon="@mipmap/launcher_ic"
android:label="${app_name}"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="true"
android:roundIcon="@mipmap/launcher_ic"
android:supportsRtl="false"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,LockedOrientationActivity"
tools:replace="android:allowBackup,android:supportsRtl"
tools:targetApi="n">
<!-- 适配 Android 7.0 文件意图 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- 闪屏页 -->
<activity
android:name=".ui.activity.SplashActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
<!-- 程序入口 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 引导页 -->
<activity
android:name=".ui.activity.GuideActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 首页 -->
<activity
android:name=".ui.activity.HomeActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" />
<!-- 登录页 -->
<activity
android:name=".ui.activity.LoginActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 注册页 -->
<activity
android:name=".ui.activity.RegisterActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 崩溃展示(必须在独立进程) -->
<activity
android:name=".ui.activity.CrashActivity"
android:launchMode="singleTop"
android:process=":crash"
android:screenOrientation="landscape" />
<!-- 重启应用(必须在独立进程) -->
<activity
android:name=".ui.activity.RestartActivity"
android:launchMode="singleTop"
android:process=":restart" />
<!-- 设置页 -->
<activity
android:name=".ui.activity.SettingActivity"
android:label="@string/setting_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 忘记密码 -->
<activity
android:name=".ui.activity.PasswordForgetActivity"
android:label="@string/password_forget_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 重置密码 -->
<activity
android:name=".ui.activity.PasswordResetActivity"
android:label="@string/password_reset_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 更换手机 -->
<activity
android:name=".ui.activity.PhoneResetActivity"
android:label="@string/phone_reset_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 关于我们 -->
<activity
android:name=".ui.activity.AboutActivity"
android:label="@string/about_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 个人资料 -->
<activity
android:name=".ui.activity.PersonalDataActivity"
android:label="@string/personal_data_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 网页浏览 -->
<activity
android:name=".ui.activity.BrowserActivity"
android:label="@string/web_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 拍照选择 -->
<activity
android:name=".ui.activity.CameraActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 图片选择 -->
<activity
android:name=".ui.activity.ImageSelectActivity"
android:label="@string/image_select_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 查看大图 -->
<activity
android:name=".ui.activity.ImagePreviewActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 播放视频 -->
<activity
android:name=".ui.activity.VideoPlayActivity"
android:launchMode="singleTop"
android:screenOrientation="landscape"
android:theme="@style/FullScreenTheme" />
<!-- 选择视频 -->
<activity
android:name=".ui.activity.VideoSelectActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 对话框案例 -->
<activity
android:name=".ui.activity.DialogActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 状态案例 -->
<activity
android:name=".ui.activity.StatusActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
</application>
<!-- Android 11 软件包可见性适配https://www.jianshu.com/p/d1ccd425c4ce -->
<queries>
<!-- 拍照意图MediaStore.ACTION_IMAGE_CAPTURE -->
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<!-- 拍摄意图MediaStore.ACTION_VIDEO_CAPTURE -->
<intent>
<action android:name="android.media.action.VIDEO_CAPTURE" />
</intent>
<!-- 图片裁剪意图 -->
<intent>
<action android:name="com.android.camera.action.CROP" />
</intent>
<!-- 打电话意图Intent.ACTION_DIAL -->
<intent>
<action android:name="android.intent.action.DIAL" />
</intent>
<!-- 分享意图Intent.ACTION_SEND -->
<intent>
<action android:name="android.intent.action.SEND" />
</intent>
<!-- 调起其他页面意图Intent.ACTION_VIEW -->
<intent>
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<?xml version="1.0" encoding="utf-8"?>
<!-- 清单文件合并指引https://developer.android.google.cn/studio/build/manifest-merge?hl=zh-cn -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.hjq.demo">
<!-- 网络相关 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 外部存储 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<!-- 拍照权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 安装权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- 定位权限(用于 WebView 定位)-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Application 节点详解https://developer.android.google.cn/guide/topics/manifest/application-element -->
<!-- Activity 节点详解https://developer.android.google.cn/guide/topics/manifest/activity-element -->
<application
android:name=".app.AppApplication"
android:allowBackup="false"
android:icon="@mipmap/launcher_ic"
android:label="${app_name}"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:resizeableActivity="true"
android:roundIcon="@mipmap/launcher_ic"
android:supportsRtl="false"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:ignore="AllowBackup,LockedOrientationActivity"
tools:replace="android:allowBackup,android:supportsRtl"
tools:targetApi="n">
<!-- 表示当前已经适配了分区存储 -->
<meta-data
android:name="ScopedStorage"
android:value="true" />
<!-- 适配 Android 7.0 文件意图 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<!-- 闪屏页 -->
<activity
android:name=".ui.activity.SplashActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
<!-- 程序入口 -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 引导页 -->
<activity
android:name=".ui.activity.GuideActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 首页 -->
<activity
android:name=".ui.activity.HomeActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" />
<!-- 登录页 -->
<activity
android:name=".ui.activity.LoginActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<!-- 注册页 -->
<activity
android:name=".ui.activity.RegisterActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<!-- 崩溃展示(必须在独立进程) -->
<activity
android:name=".ui.activity.CrashActivity"
android:launchMode="singleTop"
android:process=":crash"
android:screenOrientation="landscape" />
<!-- 重启应用(必须在独立进程) -->
<activity
android:name=".ui.activity.RestartActivity"
android:launchMode="singleTop"
android:process=":restart" />
<!-- 设置页 -->
<activity
android:name=".ui.activity.SettingActivity"
android:label="@string/setting_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 忘记密码 -->
<activity
android:name=".ui.activity.PasswordForgetActivity"
android:label="@string/password_forget_title"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<!-- 重置密码 -->
<activity
android:name=".ui.activity.PasswordResetActivity"
android:label="@string/password_reset_title"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<!-- 更换手机 -->
<activity
android:name=".ui.activity.PhoneResetActivity"
android:label="@string/phone_reset_title"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<!-- 关于我们 -->
<activity
android:name=".ui.activity.AboutActivity"
android:label="@string/about_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 个人资料 -->
<activity
android:name=".ui.activity.PersonalDataActivity"
android:label="@string/personal_data_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 网页浏览 -->
<activity
android:name=".ui.activity.BrowserActivity"
android:label="@string/web_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 拍照选择 -->
<activity
android:name=".ui.activity.CameraActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 图片裁剪 -->
<activity
android:name=".ui.activity.ImageCropActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 图片选择 -->
<activity
android:name=".ui.activity.ImageSelectActivity"
android:label="@string/image_select_title"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 查看大图 -->
<activity
android:name=".ui.activity.ImagePreviewActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 播放视频(自适应方向) -->
<activity
android:name=".ui.activity.VideoPlayActivity"
android:launchMode="singleTop"
android:theme="@style/FullScreenTheme" />
<!-- 播放视频(竖屏方向) -->
<activity
android:name=".ui.activity.VideoPlayActivity$Portrait"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/FullScreenTheme" />
<!-- 播放视频(横屏方向) -->
<activity
android:name=".ui.activity.VideoPlayActivity$Landscape"
android:launchMode="singleTop"
android:screenOrientation="landscape"
android:theme="@style/FullScreenTheme" />
<!-- 选择视频 -->
<activity
android:name=".ui.activity.VideoSelectActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 对话框案例 -->
<activity
android:name=".ui.activity.DialogActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 状态案例 -->
<activity
android:name=".ui.activity.StatusActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
<!-- 微信回调(请注意这个 Activity 放置的包名要和当前项目的包名保持一致,否则将不能正常回调) -->
<activity
android:name=".wxapi.WXEntryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
</application>
<!-- Android 11 软件包可见性适配https://www.jianshu.com/p/d1ccd425c4ce -->
<queries>
<!-- 拍照意图MediaStore.ACTION_IMAGE_CAPTURE -->
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<!-- 拍摄意图MediaStore.ACTION_VIDEO_CAPTURE -->
<intent>
<action android:name="android.media.action.VIDEO_CAPTURE" />
</intent>
<!-- 图片裁剪意图 -->
<intent>
<action android:name="com.android.camera.action.CROP" />
</intent>
<!-- 打电话意图Intent.ACTION_DIAL -->
<intent>
<action android:name="android.intent.action.DIAL" />
</intent>
<!-- 调起分享意图Intent.ACTION_SEND -->
<intent>
<action android:name="android.intent.action.SEND" />
</intent>
<!-- 调起其他页面意图Intent.ACTION_VIEW -->
<intent>
<action android:name="android.intent.action.VIEW" />
</intent>
<!-- 调起系统文件选择器Intent.ACTION_GET_CONTENT -->
<intent>
<action android:name="android.intent.action.GET_CONTENT" />
</intent>
</queries>
</manifest>

View File

@ -4,7 +4,6 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.view.View;
import androidx.annotation.DrawableRes;
import androidx.annotation.RawRes;
@ -39,7 +38,7 @@ public interface StatusAction {
layout.show();
layout.setAnimResource(id);
layout.setHint("");
layout.setOnClickListener(null);
layout.setOnRetryListener(null);
}
/**
@ -63,7 +62,7 @@ public interface StatusAction {
/**
* 显示错误提示
*/
default void showError(View.OnClickListener listener) {
default void showError(StatusLayout.OnRetryListener listener) {
StatusLayout layout = getStatusLayout();
Context context = layout.getContext();
ConnectivityManager manager = ContextCompat.getSystemService(context, ConnectivityManager.class);
@ -81,17 +80,17 @@ public interface StatusAction {
/**
* 显示自定义提示
*/
default void showLayout(@DrawableRes int drawableId, @StringRes int stringId, View.OnClickListener listener) {
default void showLayout(@DrawableRes int drawableId, @StringRes int stringId, StatusLayout.OnRetryListener listener) {
StatusLayout layout = getStatusLayout();
Context context = layout.getContext();
showLayout(ContextCompat.getDrawable(context, drawableId), context.getString(stringId), listener);
}
default void showLayout(Drawable drawable, CharSequence hint, View.OnClickListener listener) {
default void showLayout(Drawable drawable, CharSequence hint, StatusLayout.OnRetryListener listener) {
StatusLayout layout = getStatusLayout();
layout.show();
layout.setIcon(drawable);
layout.setHint(hint);
layout.setOnClickListener(listener);
layout.setOnRetryListener(listener);
}
}

View File

@ -20,7 +20,7 @@ public interface TitleBarAction extends OnTitleBarListener {
@Nullable
TitleBar getTitleBar();
/**
* 左项被点击
*
@ -157,11 +157,16 @@ public interface TitleBarAction extends OnTitleBarListener {
* 递归获取 ViewGroup 中的 TitleBar 对象
*/
default TitleBar obtainTitleBar(ViewGroup group) {
if (group == null) {
return null;
}
for (int i = 0; i < group.getChildCount(); i++) {
View view = group.getChildAt(i);
if ((view instanceof TitleBar)) {
return (TitleBar) view;
} else if (view instanceof ViewGroup) {
}
if (view instanceof ViewGroup) {
TitleBar titleBar = obtainTitleBar((ViewGroup) view);
if (titleBar != null) {
return titleBar;

View File

@ -13,7 +13,7 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface DebugLog {
public @interface Log {
String value() default "DebugLog";
String value() default "AppLog";
}

View File

@ -24,41 +24,40 @@ import timber.log.Timber;
* desc : Debug 日志切面
*/
@Aspect
public class DebugLogAspect {
public class LogAspect {
/**
* 构造方法切入点
*/
@Pointcut("execution(@com.hjq.demo.aop.DebugLog *.new(..))")
@Pointcut("execution(@com.hjq.demo.aop.Log *.new(..))")
public void constructor() {}
/**
* 方法切入点
*/
@Pointcut("execution(@com.hjq.demo.aop.DebugLog * *(..))")
@Pointcut("execution(@com.hjq.demo.aop.Log * *(..))")
public void method() {}
/**
* 在连接点进行方法替换
*/
@Around("(method() || constructor()) && @annotation(debugLog)")
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, DebugLog debugLog) throws Throwable {
enterMethod(joinPoint, debugLog);
@Around("(method() || constructor()) && @annotation(log)")
public Object aroundJoinPoint(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
enterMethod(joinPoint, log);
long startNanos = System.nanoTime();
Object result = joinPoint.proceed();
long stopNanos = System.nanoTime();
exitMethod(joinPoint, debugLog, result, TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos));
exitMethod(joinPoint, log, result, TimeUnit.NANOSECONDS.toMillis(stopNanos - startNanos));
return result;
}
/**
* 方法执行前切入
*/
private void enterMethod(ProceedingJoinPoint joinPoint, DebugLog debugLog) {
private void enterMethod(ProceedingJoinPoint joinPoint, Log log) {
CodeSignature codeSignature = (CodeSignature) joinPoint.getSignature();
// 方法所在类
@ -73,9 +72,9 @@ public class DebugLogAspect {
//记录并打印方法的信息
StringBuilder builder = getMethodLogInfo(className, methodName, parameterNames, parameterValues);
log(debugLog.value(), builder.toString());
log(log.value(), builder.toString());
final String section = builder.toString().substring(2);
final String section = builder.substring(2);
Trace.beginSection(section);
}
@ -116,7 +115,7 @@ public class DebugLogAspect {
* @param result 方法执行后的结果
* @param lengthMillis 执行方法所需要的时间
*/
private void exitMethod(ProceedingJoinPoint joinPoint, DebugLog debugLog, Object result, long lengthMillis) {
private void exitMethod(ProceedingJoinPoint joinPoint, Log log, Object result, long lengthMillis) {
Trace.endSection();
Signature signature = joinPoint.getSignature();
@ -138,7 +137,7 @@ public class DebugLogAspect {
builder.append(result.toString());
}
log(debugLog.value(), builder.toString());
log(log.value(), builder.toString());
}
private void log(String tag, String msg) {

View File

@ -5,6 +5,7 @@ import android.app.Activity;
import com.hjq.demo.manager.ActivityManager;
import com.hjq.demo.other.PermissionCallback;
import com.hjq.permissions.XXPermissions;
import com.tencent.bugly.crashreport.CrashReport;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@ -13,6 +14,8 @@ import org.aspectj.lang.annotation.Pointcut;
import java.util.List;
import timber.log.Timber;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
@ -32,14 +35,34 @@ public class PermissionsAspect {
* 在连接点进行方法替换
*/
@Around("method() && @annotation(permissions)")
public void aroundJoinPoint(final ProceedingJoinPoint joinPoint, Permissions permissions) {
Activity activity = ActivityManager.getInstance().getTopActivity();
public void aroundJoinPoint(ProceedingJoinPoint joinPoint, Permissions permissions) {
Activity activity = null;
// 方法参数值集合
Object[] parameterValues = joinPoint.getArgs();
for (Object arg : parameterValues) {
if (!(arg instanceof Activity)) {
continue;
}
activity = (Activity) arg;
break;
}
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
activity = ActivityManager.getInstance().getTopActivity();
}
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
Timber.e("The activity has been destroyed and permission requests cannot be made");
return;
}
requestPermissions(joinPoint, activity, permissions.value());
}
private void requestPermissions(ProceedingJoinPoint joinPoint, Activity activity, String[] permissions) {
XXPermissions.with(activity)
.permission(permissions.value())
.permission(permissions)
.request(new PermissionCallback() {
@Override
@ -49,7 +72,7 @@ public class PermissionsAspect {
// 获得权限执行原方法
joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
}
}
}

View File

@ -25,7 +25,7 @@ import okhttp3.Call;
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2018/10/18
* desc : 业务 Activity 基类
* desc : Activity 业务基类
*/
public abstract class AppActivity extends BaseActivity
implements ToastAction, TitleBarAction, OnHttpListener<Object> {
@ -38,7 +38,7 @@ public abstract class AppActivity extends BaseActivity
/** 加载对话框 */
private BaseDialog mDialog;
/** 对话框数量 */
private int mDialogTotal;
private int mDialogCount;
/**
* 当前加载对话框是否在显示中
@ -51,9 +51,13 @@ public abstract class AppActivity extends BaseActivity
* 显示加载对话框
*/
public void showDialog() {
mDialogTotal++;
if (isFinishing() || isDestroyed()) {
return;
}
mDialogCount++;
postDelayed(() -> {
if (mDialogTotal <= 0 || isFinishing() || isDestroyed()) {
if (mDialogCount <= 0 || isFinishing() || isDestroyed()) {
return;
}
@ -72,13 +76,19 @@ public abstract class AppActivity extends BaseActivity
* 隐藏加载对话框
*/
public void hideDialog() {
if (mDialogTotal > 0) {
mDialogTotal--;
if (isFinishing() || isDestroyed()) {
return;
}
if (mDialogTotal == 0 && mDialog != null && mDialog.isShowing() && !isFinishing()) {
mDialog.dismiss();
if (mDialogCount > 0) {
mDialogCount--;
}
if (mDialogCount != 0 || mDialog == null || !mDialog.isShowing()) {
return;
}
mDialog.dismiss();
}
@Override
@ -134,7 +144,7 @@ public abstract class AppActivity extends BaseActivity
// 默认状态栏字体颜色为黑色
.statusBarDarkFont(isStatusBarDarkFont())
// 指定导航栏背景颜色
.navigationBarColor(android.R.color.white)
.navigationBarColor(R.color.white)
// 状态栏字体和导航栏内容自动变色必须指定状态栏颜色和导航栏颜色才可以自动变色
.autoDarkModeEnable(true, 0.2f);
}

View File

@ -18,7 +18,7 @@ import java.util.List;
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2018/12/19
* desc : 业务 RecyclerView 适配器基类
* desc : RecyclerView 适配器业务基类
*/
public abstract class AppAdapter<T> extends BaseAdapter<BaseAdapter<?>.ViewHolder> {
@ -95,10 +95,30 @@ public abstract class AppAdapter<T> extends BaseAdapter<BaseAdapter<?>.ViewHolde
notifyDataSetChanged();
}
/**
* 是否包含了某个位置上的条目数据
*/
public boolean containsItem(@IntRange(from = 0) int position) {
return containsItem(getItem(position));
}
/**
* 是否包含某个条目数据
*/
public boolean containsItem(T item) {
if (mDataSet == null || item == null) {
return false;
}
return mDataSet.contains(item);
}
/**
* 获取某个位置上的数据
*/
public T getItem(@IntRange(from = 0) int position) {
if (mDataSet == null) {
return null;
}
return mDataSet.get(position);
}
@ -120,7 +140,6 @@ public abstract class AppAdapter<T> extends BaseAdapter<BaseAdapter<?>.ViewHolde
if (mDataSet == null) {
mDataSet = new ArrayList<>();
}
addItem(mDataSet.size(), item);
}

View File

@ -3,23 +3,18 @@ package com.hjq.demo.app;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.os.Build;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import com.hjq.bar.TitleBar;
import com.hjq.bar.initializer.LightBarInitializer;
import com.hjq.demo.R;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.http.glide.GlideApp;
import com.hjq.demo.http.model.RequestHandler;
import com.hjq.demo.http.model.RequestServer;
@ -27,16 +22,23 @@ import com.hjq.demo.manager.ActivityManager;
import com.hjq.demo.other.AppConfig;
import com.hjq.demo.other.CrashHandler;
import com.hjq.demo.other.DebugLoggerTree;
import com.hjq.demo.other.MaterialHeader;
import com.hjq.demo.other.SmartBallPulseFooter;
import com.hjq.demo.other.ToastInterceptor;
import com.hjq.demo.other.TitleBarStyle;
import com.hjq.demo.other.ToastLogInterceptor;
import com.hjq.demo.other.ToastStyle;
import com.hjq.gson.factory.GsonFactory;
import com.hjq.http.EasyConfig;
import com.hjq.http.config.IRequestApi;
import com.hjq.http.config.IRequestInterceptor;
import com.hjq.http.model.HttpHeaders;
import com.hjq.http.model.HttpParams;
import com.hjq.permissions.XXPermissions;
import com.hjq.toast.ToastUtils;
import com.hjq.toast.style.ToastBlackStyle;
import com.hjq.umeng.UmengClient;
import com.scwang.smart.refresh.header.MaterialHeader;
import com.scwang.smart.refresh.layout.SmartRefreshLayout;
import com.tencent.bugly.crashreport.CrashReport;
import com.tencent.mmkv.MMKV;
import okhttp3.OkHttpClient;
import timber.log.Timber;
@ -49,13 +51,18 @@ import timber.log.Timber;
*/
public final class AppApplication extends Application {
@DebugLog("启动耗时")
@Log("启动耗时")
@Override
public void onCreate() {
super.onCreate();
initSdk(this);
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
@Override
public void onLowMemory() {
super.onLowMemory();
@ -74,56 +81,16 @@ public final class AppApplication extends Application {
* 初始化一些第三方框架
*/
public static void initSdk(Application application) {
// 设置调试模式
XXPermissions.setDebugMode(AppConfig.isDebug());
// 初始化吐司
ToastUtils.init(application, new ToastBlackStyle(application) {
@Override
public int getCornerRadius() {
return (int) application.getResources().getDimension(R.dimen.button_round_size);
}
});
// 设置 Toast 拦截器
ToastUtils.setToastInterceptor(new ToastInterceptor());
// 设置标题栏初始化器
TitleBar.setDefaultInitializer(new LightBarInitializer() {
@Override
public Drawable getBackgroundDrawable(Context context) {
return new ColorDrawable(ContextCompat.getColor(application, R.color.common_primary_color));
}
@Override
public Drawable getBackIcon(Context context) {
return ContextCompat.getDrawable(context, R.drawable.arrows_left_ic);
}
@Override
protected TextView createTextView(Context context) {
return new AppCompatTextView(context);
}
});
// 本地异常捕捉
CrashHandler.register(application);
// 友盟统计登录分享 SDK
UmengClient.init(application);
// Bugly 异常捕捉
CrashReport.initCrashReport(application, AppConfig.getBuglyId(), AppConfig.isDebug());
TitleBar.setDefaultStyle(new TitleBarStyle());
// 设置全局的 Header 构建器
SmartRefreshLayout.setDefaultRefreshHeaderCreator((context, layout) ->
new MaterialHeader(context).setColorSchemeColors(ContextCompat.getColor(context, R.color.common_accent_color)));
SmartRefreshLayout.setDefaultRefreshHeaderCreator((cx, layout) ->
new MaterialHeader(application).setColorSchemeColors(ContextCompat.getColor(application, R.color.common_accent_color)));
// 设置全局的 Footer 构建器
SmartRefreshLayout.setDefaultRefreshFooterCreator((context, layout) -> new SmartBallPulseFooter(context));
SmartRefreshLayout.setDefaultRefreshFooterCreator((cx, layout) -> new SmartBallPulseFooter(application));
// 设置全局初始化器
SmartRefreshLayout.setDefaultRefreshInitializer((context, layout) -> {
SmartRefreshLayout.setDefaultRefreshInitializer((cx, layout) -> {
// 刷新头部是否跟随内容偏移
layout.setEnableHeaderTranslationContent(true)
// 刷新尾部是否跟随内容偏移
@ -136,9 +103,28 @@ public final class AppApplication extends Application {
.setEnableOverScrollDrag(false);
});
// 初始化吐司
ToastUtils.init(application, new ToastStyle());
// 设置调试模式
ToastUtils.setDebugMode(AppConfig.isDebug());
// 设置 Toast 拦截器
ToastUtils.setInterceptor(new ToastLogInterceptor());
// 本地异常捕捉
CrashHandler.register(application);
// 友盟统计登录分享 SDK
UmengClient.init(application, AppConfig.isLogEnable());
// Bugly 异常捕捉
CrashReport.initCrashReport(application, AppConfig.getBuglyId(), AppConfig.isDebug());
// Activity 栈管理初始化
ActivityManager.getInstance().init(application);
// MMKV 初始化
MMKV.initialize(application);
// 网络请求框架初始化
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
@ -152,13 +138,24 @@ public final class AppApplication extends Application {
.setHandler(new RequestHandler(application))
// 设置请求重试次数
.setRetryCount(1)
// 添加全局请求参数
//.addParam("token", "6666666")
// 添加全局请求头
//.addHeader("time", "20191030")
// 启用配置
.setInterceptor((api, params, headers) -> {
// 添加全局请求头
headers.put("token", "66666666666");
headers.put("deviceOaid", UmengClient.getDeviceOaid());
headers.put("versionName", AppConfig.getVersionName());
headers.put("versionCode", String.valueOf(AppConfig.getVersionCode()));
// 添加全局请求参数
// params.put("6666666", "6666666");
})
.into();
// 设置 Json 解析容错监听
GsonFactory.setJsonCallback((typeToken, fieldName, jsonToken) -> {
// 上报到 Bugly 错误列表
CrashReport.postCatchedException(new IllegalArgumentException(
"类型解析异常:" + typeToken + "#" + fieldName + ",后台返回的类型为:" + jsonToken));
});
// 初始化日志打印
if (AppConfig.isLogEnable()) {
Timber.plant(new DebugLoggerTree());
@ -171,12 +168,16 @@ public final class AppApplication extends Application {
@Override
public void onLost(@NonNull Network network) {
Activity topActivity = ActivityManager.getInstance().getTopActivity();
if (topActivity instanceof LifecycleOwner) {
LifecycleOwner lifecycleOwner = ((LifecycleOwner) topActivity);
if (lifecycleOwner.getLifecycle().getCurrentState() == Lifecycle.State.RESUMED) {
ToastUtils.show(R.string.common_network_error);
}
if (!(topActivity instanceof LifecycleOwner)) {
return;
}
LifecycleOwner lifecycleOwner = ((LifecycleOwner) topActivity);
if (lifecycleOwner.getLifecycle().getCurrentState() != Lifecycle.State.RESUMED) {
return;
}
ToastUtils.show(R.string.common_network_error);
}
});
}

View File

@ -11,7 +11,7 @@ import okhttp3.Call;
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2018/10/18
* desc : 业务 Fragment 基类
* desc : Fragment 业务基类
*/
public abstract class AppFragment<A extends AppActivity> extends BaseFragment<A>
implements ToastAction, OnHttpListener<Object> {
@ -21,11 +21,10 @@ public abstract class AppFragment<A extends AppActivity> extends BaseFragment<A>
*/
public boolean isShowDialog() {
A activity = getAttachActivity();
if (activity != null) {
return activity.isShowDialog();
if (activity == null) {
return false;
}
return false;
return activity.isShowDialog();
}
/**
@ -33,9 +32,10 @@ public abstract class AppFragment<A extends AppActivity> extends BaseFragment<A>
*/
public void showDialog() {
A activity = getAttachActivity();
if (activity != null) {
activity.showDialog();
if (activity == null) {
return;
}
activity.showDialog();
}
/**
@ -43,9 +43,10 @@ public abstract class AppFragment<A extends AppActivity> extends BaseFragment<A>
*/
public void hideDialog() {
A activity = getAttachActivity();
if (activity != null) {
activity.hideDialog();
if (activity == null) {
return;
}
activity.hideDialog();
}
/**
@ -59,9 +60,10 @@ public abstract class AppFragment<A extends AppActivity> extends BaseFragment<A>
@Override
public void onSucceed(Object result) {
if (result instanceof HttpData) {
toast(((HttpData<?>) result).getMessage());
if (!(result instanceof HttpData)) {
return;
}
toast(((HttpData<?>) result).getMessage());
}
@Override

View File

@ -9,13 +9,14 @@ import androidx.annotation.Nullable;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.bar.TitleBar;
import com.hjq.demo.R;
import com.hjq.demo.action.TitleBarAction;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2020/10/31
* desc : 带标题栏的 Fragment 基类
* desc : 带标题栏的 Fragment 业务基类
*/
public abstract class TitleBarFragment<A extends AppActivity> extends AppFragment<A>
implements TitleBarAction {
@ -28,18 +29,20 @@ public abstract class TitleBarFragment<A extends AppActivity> extends AppFragmen
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 设置标题栏沉浸
if (isStatusBarEnabled() && getTitleBar() != null) {
ImmersionBar.setTitleBar(this, getTitleBar());
}
// 设置标题栏点击监听
if (getTitleBar() != null) {
getTitleBar().setOnTitleBarListener(this);
}
// 初始化沉浸式状态栏
if (isStatusBarEnabled()) {
// 初始化沉浸式状态栏
getStatusBarConfig().init();
if (getTitleBar() != null) {
// 设置标题栏沉浸
ImmersionBar.setTitleBar(this, getTitleBar());
}
}
}
@ -79,7 +82,7 @@ public abstract class TitleBarFragment<A extends AppActivity> extends AppFragmen
// 默认状态栏字体颜色为黑色
.statusBarDarkFont(isStatusBarDarkFont())
// 指定导航栏背景颜色
.navigationBarColor(android.R.color.white)
.navigationBarColor(R.color.white)
// 状态栏字体和导航栏内容自动变色必须指定状态栏颜色和导航栏颜色才可以自动变色
.autoDarkModeEnable(true, 0.2f);
}

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;
@ -14,4 +14,8 @@ public final class CopyApi implements IRequestApi {
public String getApi() {
return "";
}
public final static class Bean {
}
}

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;
@ -29,4 +29,13 @@ public final class LoginApi implements IRequestApi {
this.password = password;
return this;
}
public final static class Bean {
private String token;
public String getToken() {
return token;
}
}
}

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;
@ -36,4 +36,8 @@ public final class RegisterApi implements IRequestApi {
this.password = password;
return this;
}
public final static class Bean {
}
}

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;
@ -14,4 +14,8 @@ public final class UserInfoApi implements IRequestApi {
public String getApi() {
return "user/info";
}
public final class Bean {
}
}

View File

@ -1,4 +1,4 @@
package com.hjq.demo.http.request;
package com.hjq.demo.http.api;
import com.hjq.http.config.IRequestApi;

View File

@ -62,9 +62,9 @@ public final class GlideConfig extends AppGlideModule {
builder.setDefaultRequestOptions(new RequestOptions()
// 设置默认加载中占位图
.placeholder(R.drawable.image_loading_bg)
.placeholder(R.drawable.image_loading_ic)
// 设置默认加载出错占位图
.error(R.drawable.image_error_bg));
.error(R.drawable.image_error_ic));
}
@Override

View File

@ -77,7 +77,9 @@ public final class OkHttpFetcher implements DataFetcher<InputStream>, Callback {
if (mInputStream != null) {
mInputStream.close();
}
} catch (IOException ignored) {}
} catch (IOException e) {
e.printStackTrace();
}
if (mResponseBody != null) {
mResponseBody.close();
@ -87,9 +89,10 @@ public final class OkHttpFetcher implements DataFetcher<InputStream>, Callback {
@Override
public void cancel() {
if (mCall != null) {
mCall.cancel();
if (mCall == null) {
return;
}
mCall.cancel();
}
@NonNull

View File

@ -53,4 +53,4 @@ public final class OkHttpLoader implements ModelLoader<GlideUrl, InputStream> {
@Override
public void teardown() {}
}
}
}

View File

@ -26,4 +26,18 @@ public class HttpData<T> {
public T getData() {
return data;
}
/**
* 是否请求成功
*/
public boolean isRequestSucceed() {
return code == 200;
}
/**
* 是否 Token 失效
*/
public boolean isTokenFailure() {
return code == 1001;
}
}

View File

@ -14,6 +14,7 @@ import com.hjq.demo.manager.ActivityManager;
import com.hjq.demo.ui.activity.LoginActivity;
import com.hjq.gson.factory.GsonFactory;
import com.hjq.http.EasyLog;
import com.hjq.http.config.IRequestApi;
import com.hjq.http.config.IRequestHandler;
import com.hjq.http.exception.CancelException;
import com.hjq.http.exception.DataException;
@ -24,6 +25,7 @@ import com.hjq.http.exception.ResultException;
import com.hjq.http.exception.ServerException;
import com.hjq.http.exception.TimeoutException;
import com.hjq.http.exception.TokenException;
import com.tencent.mmkv.MMKV;
import org.json.JSONArray;
import org.json.JSONException;
@ -48,13 +50,15 @@ import okhttp3.ResponseBody;
public final class RequestHandler implements IRequestHandler {
private final Application mApplication;
private final MMKV mMmkv;
public RequestHandler(Application application) {
mApplication = application;
mMmkv = MMKV.mmkvWithID("http_cache_id");
}
@Override
public Object requestSucceed(LifecycleOwner lifecycle, Response response, Type type) throws Exception {
public Object requestSucceed(LifecycleOwner lifecycle, IRequestApi api, Response response, Type type) throws Exception {
if (Response.class.equals(type)) {
return response;
@ -122,12 +126,15 @@ public final class RequestHandler implements IRequestHandler {
if (result instanceof HttpData) {
HttpData<?> model = (HttpData<?>) result;
if (model.getCode() == 0) {
if (model.isRequestSucceed()) {
// 代表执行成功
return result;
} else if (model.getCode() == 1001) {
}
if (model.isTokenFailure()) {
// 代表登录失效需要重新登录
throw new TokenException(mApplication.getString(R.string.http_account_error));
throw new TokenException(mApplication.getString(R.string.http_token_error));
}
// 代表执行失败
@ -137,7 +144,7 @@ public final class RequestHandler implements IRequestHandler {
}
@Override
public Exception requestFail(LifecycleOwner lifecycle, Exception e) {
public Exception requestFail(LifecycleOwner lifecycle, IRequestApi api, Exception e) {
// 判断这个异常是不是自己抛的
if (e instanceof HttpException) {
if (e instanceof TokenException) {
@ -146,7 +153,7 @@ public final class RequestHandler implements IRequestHandler {
Intent intent = new Intent(application, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
application.startActivity(intent);
// 销毁除了登录页之外的界面
// 销毁除了登录页之外的 Activity
ActivityManager.getInstance().finishAllActivities(LoginActivity.class);
}
return e;
@ -159,13 +166,13 @@ public final class RequestHandler implements IRequestHandler {
if (e instanceof UnknownHostException) {
NetworkInfo info = ((ConnectivityManager) mApplication.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
// 判断网络是否连接
if (info != null && info.isConnected()) {
// 有连接就是服务器的问题
return new ServerException(mApplication.getString(R.string.http_server_error), e);
if (info == null || !info.isConnected()) {
// 没有连接就是网络异常
return new NetworkException(mApplication.getString(R.string.http_network_error), e);
}
// 没有连接就是网络异常
return new NetworkException(mApplication.getString(R.string.http_network_error), e);
// 有连接就是服务器的问题
return new ServerException(mApplication.getString(R.string.http_server_error), e);
}
if (e instanceof IOException) {
@ -175,4 +182,32 @@ public final class RequestHandler implements IRequestHandler {
return new HttpException(e.getMessage(), e);
}
@Override
public Object readCache(LifecycleOwner lifecycle, IRequestApi api, Type type) {
String cacheKey = GsonFactory.getSingletonGson().toJson(api);
String cacheValue = mMmkv.getString(cacheKey, null);
if (cacheValue == null || "".equals(cacheValue) || "{}".equals(cacheValue)) {
return null;
}
EasyLog.print("---------- cacheKey ----------");
EasyLog.json(cacheKey);
EasyLog.print("---------- cacheValue ----------");
EasyLog.json(cacheValue);
return GsonFactory.getSingletonGson().fromJson(cacheValue, type);
}
@Override
public boolean writeCache(LifecycleOwner lifecycle, IRequestApi api, Response response, Object result) {
String cacheKey = GsonFactory.getSingletonGson().toJson(api);
String cacheValue = GsonFactory.getSingletonGson().toJson(result);
if (cacheValue == null || "".equals(cacheValue) || "{}".equals(cacheValue)) {
return false;
}
EasyLog.print("---------- cacheKey ----------");
EasyLog.json(cacheKey);
EasyLog.print("---------- cacheValue ----------");
EasyLog.json(cacheValue);
return mMmkv.putString(cacheKey, cacheValue).commit();
}
}

View File

@ -1,11 +0,0 @@
package com.hjq.demo.http.response;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2019/12/07
* desc : 可进行拷贝的副本
*/
public final class CopyBean {
}

View File

@ -1,16 +0,0 @@
package com.hjq.demo.http.response;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2019/12/07
* desc : 登录返回
*/
public final class LoginBean {
private String token;
public String getToken() {
return token;
}
}

View File

@ -1,11 +0,0 @@
package com.hjq.demo.http.response;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2019/12/07
* desc : 注册返回
*/
public final class RegisterBean {
}

View File

@ -1,11 +0,0 @@
package com.hjq.demo.http.response;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2019/12/07
* desc : 用户信息
*/
public final class UserInfoBean {
}

View File

@ -8,6 +8,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import java.util.ArrayList;
import timber.log.Timber;
/**
@ -20,14 +22,18 @@ public final class ActivityManager implements Application.ActivityLifecycleCallb
private static volatile ActivityManager sInstance;
/** Activity 存放集合 */
private final ArrayMap<String, Activity> mActivitySet = new ArrayMap<>();
/** 应用生命周期回调 */
private final ArrayList<ApplicationLifecycleCallback> mLifecycleCallbacks = new ArrayList<>();
/** 当前应用上下文对象 */
private Application mApplication;
/** 最后一个可见 Activity 标记 */
private String mLastVisibleTag;
/** 最后一个不可见 Activity 标记 */
private String mLastInvisibleTag;
/** 栈顶的 Activity 对象 */
private Activity mTopActivity;
/** 前台并且可见的 Activity 对象 */
private Activity mResumedActivity;
private ActivityManager() {}
@ -57,20 +63,60 @@ public final class ActivityManager implements Application.ActivityLifecycleCallb
/**
* 获取栈顶的 Activity
*/
@Nullable
public Activity getTopActivity() {
return mActivitySet.get(mLastVisibleTag);
return mTopActivity;
}
/**
* 获取前台并且可见的 Activity
*/
@Nullable
public Activity getResumedActivity() {
return mResumedActivity;
}
/**
* 判断当前应用是否处于前台状态
*/
public boolean isForeground() {
// 如果最后一个可见的 Activity 和最后一个不可见的 Activity 是同一个的话
if (mLastVisibleTag.equals(mLastInvisibleTag)) {
return false;
return getResumedActivity() != null;
}
/**
* 注册应用生命周期回调
*/
public void registerApplicationLifecycleCallback(ApplicationLifecycleCallback callback) {
mLifecycleCallbacks.add(callback);
}
/**
* 取消注册应用生命周期回调
*/
public void unregisterApplicationLifecycleCallback(ApplicationLifecycleCallback callback) {
mLifecycleCallbacks.remove(callback);
}
/**
* 销毁指定的 Activity
*/
public void finishActivity(Class<? extends Activity> clazz) {
if (clazz == null) {
return;
}
String[] keys = mActivitySet.keySet().toArray(new String[]{});
for (String key : keys) {
Activity activity = mActivitySet.get(key);
if (activity == null || activity.isFinishing()) {
continue;
}
if (activity.getClass().equals(clazz)) {
activity.finish();
mActivitySet.remove(key);
break;
}
}
Activity activity = getTopActivity();
return activity != null;
}
/**
@ -81,60 +127,86 @@ public final class ActivityManager implements Application.ActivityLifecycleCallb
}
/**
* 销毁所有的 Activity除这些 Class 之外的 Activity
* 销毁所有的 Activity
*
* @param classArray 白名单 Activity
*/
@SafeVarargs
public final void finishAllActivities(Class<? extends Activity>... classArray) {
String[] keys = mActivitySet.keySet().toArray(new String[]{});
for (String key : keys) {
Activity activity = mActivitySet.get(key);
if (activity != null && !activity.isFinishing()) {
boolean whiteClazz = false;
if (classArray != null) {
for (Class<? extends Activity> clazz : classArray) {
if (activity.getClass() == clazz) {
whiteClazz = true;
}
if (activity == null || activity.isFinishing()) {
continue;
}
boolean whiteClazz = false;
if (classArray != null) {
for (Class<? extends Activity> clazz : classArray) {
if (activity.getClass().equals(clazz)) {
whiteClazz = true;
}
}
// 如果不是白名单上面的 Activity 就销毁掉
if (!whiteClazz) {
activity.finish();
mActivitySet.remove(key);
}
}
if (whiteClazz) {
continue;
}
// 如果不是白名单上面的 Activity 就销毁掉
activity.finish();
mActivitySet.remove(key);
}
}
@Override
public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
Timber.i("%s - onCreate", activity.getClass().getSimpleName());
mLastVisibleTag = getObjectTag(activity);
if (mActivitySet.size() == 0) {
for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
callback.onApplicationCreate(activity);
}
Timber.i("%s - onApplicationCreate", activity.getClass().getSimpleName());
}
mActivitySet.put(getObjectTag(activity), activity);
mTopActivity = activity;
}
@Override
public void onActivityStarted(@NonNull Activity activity) {
Timber.i("%s - onStart", activity.getClass().getSimpleName());
mLastVisibleTag = getObjectTag(activity);
}
@Override
public void onActivityResumed(@NonNull Activity activity) {
Timber.i("%s - onResume", activity.getClass().getSimpleName());
mLastVisibleTag = getObjectTag(activity);
if (mTopActivity == activity && mResumedActivity == null) {
for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
callback.onApplicationForeground(activity);
}
Timber.i("%s - onApplicationForeground", activity.getClass().getSimpleName());
}
mTopActivity = activity;
mResumedActivity = activity;
}
@Override
public void onActivityPaused(@NonNull Activity activity) {
Timber.i("%s - onPause", activity.getClass().getSimpleName());
mLastInvisibleTag = getObjectTag(activity);
}
@Override
public void onActivityStopped(@NonNull Activity activity) {
Timber.i("%s - onStop", activity.getClass().getSimpleName());
mLastInvisibleTag = getObjectTag(activity);
if (mResumedActivity == activity) {
mResumedActivity = null;
}
if (mResumedActivity == null) {
for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
callback.onApplicationBackground(activity);
}
Timber.i("%s - onApplicationBackground", activity.getClass().getSimpleName());
}
}
@Override
@ -146,10 +218,14 @@ public final class ActivityManager implements Application.ActivityLifecycleCallb
public void onActivityDestroyed(@NonNull Activity activity) {
Timber.i("%s - onDestroy", activity.getClass().getSimpleName());
mActivitySet.remove(getObjectTag(activity));
mLastInvisibleTag = getObjectTag(activity);
if (getObjectTag(activity).equals(mLastVisibleTag)) {
// 清除当前标记
mLastVisibleTag = null;
if (mTopActivity == activity) {
mTopActivity = null;
}
if (mActivitySet.size() == 0) {
for (ApplicationLifecycleCallback callback : mLifecycleCallbacks) {
callback.onApplicationDestroy(activity);
}
Timber.i("%s - onApplicationDestroy", activity.getClass().getSimpleName());
}
}
@ -160,4 +236,30 @@ public final class ActivityManager implements Application.ActivityLifecycleCallb
// 对象所在的包名 + 对象的内存地址
return object.getClass().getName() + Integer.toHexString(object.hashCode());
}
/**
* 应用生命周期回调
*/
public interface ApplicationLifecycleCallback {
/**
* 第一个 Activity 创建了
*/
void onApplicationCreate(Activity activity);
/**
* 最后一个 Activity 销毁了
*/
void onApplicationDestroy(Activity activity);
/**
* 应用从前台进入到后台
*/
void onApplicationBackground(Activity activity);
/**
* 应用从后台进入到前台
*/
void onApplicationForeground(Activity activity);
}
}

View File

@ -3,6 +3,8 @@ package com.hjq.demo.manager;
import android.content.Context;
import android.os.Environment;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.File;
import java.math.BigDecimal;
@ -39,17 +41,19 @@ public final class CacheDataManager {
* 删除文件夹
*/
private static boolean deleteDir(File dir) {
if (dir != null) {
if (dir.isDirectory()) {
String[] children = dir.list();
if (children != null) {
for (String child : children) {
deleteDir(new File(dir, child));
}
}
} else {
return dir.delete();
}
if (dir == null) {
return false;
}
if (!dir.isDirectory()) {
return dir.delete();
}
String[] children = dir.list();
if (children == null) {
return false;
}
for (String child : children) {
deleteDir(new File(dir, child));
}
return false;
}
@ -61,18 +65,19 @@ public final class CacheDataManager {
long size = 0;
try {
File[] list = file.listFiles();
if (list != null) {
for (File temp : list) {
// 如果下面还有文件
if (temp.isDirectory()) {
size = size + getFolderSize(temp);
} else {
size = size + temp.length();
}
if (list == null) {
return 0;
}
for (File temp : list) {
// 如果下面还有文件
if (temp.isDirectory()) {
size = size + getFolderSize(temp);
} else {
size = size + temp.length();
}
}
} catch (Exception e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
}
return size;
}
@ -89,26 +94,19 @@ public final class CacheDataManager {
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "K";
return new BigDecimal(kiloByte).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "K";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "M";
return new BigDecimal(megaByte).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "M";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "GB";
return new BigDecimal(gigaByte).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
+ "TB";
return new BigDecimal(teraBytes).setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";
}
}

View File

@ -10,6 +10,7 @@ import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
@ -18,7 +19,7 @@ import java.util.List;
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2018/10/18
* desc : 文本输入管理类通过管理多个 TextView 输入是否为空来启用或者禁用按钮的点击事件
* desc : 文本输入管理类通过管理多个 EditText 输入是否为空来启用或者禁用按钮的点击事件
* blog : https://www.jianshu.com/p/fd3795e8a6b3
*/
public final class InputTextManager implements TextWatcher {
@ -32,6 +33,7 @@ public final class InputTextManager implements TextWatcher {
private List<TextView> mViewSet;
/** 输入监听器 */
@Nullable
private OnInputTextListener mListener;
/**
@ -108,14 +110,16 @@ public final class InputTextManager implements TextWatcher {
* 移除 TextView 监听避免内存泄露
*/
public void removeViews(TextView... views) {
if (mViewSet != null && mViewSet.size() > 0) {
for (TextView view : views) {
view.removeTextChangedListener(this);
mViewSet.remove(view);
}
// 触发一次监听
notifyChanged();
if (mViewSet == null || mViewSet.isEmpty()) {
return;
}
for (TextView view : views) {
view.removeTextChangedListener(this);
mViewSet.remove(view);
}
// 触发一次监听
notifyChanged();
}
/**
@ -136,7 +140,7 @@ public final class InputTextManager implements TextWatcher {
/**
* 设置输入监听
*/
public void setListener(OnInputTextListener listener) {
public void setListener(@Nullable OnInputTextListener listener) {
mListener = listener;
}
@ -171,11 +175,12 @@ public final class InputTextManager implements TextWatcher {
}
}
if (mListener != null) {
setEnabled(mListener.onInputChange(this));
} else {
if (mListener == null) {
setEnabled(true);
return;
}
setEnabled(mListener.onInputChange(this));
}
/**
@ -215,10 +220,10 @@ public final class InputTextManager implements TextWatcher {
private boolean isAlpha;
/** TextView集合 */
private final List<TextView> mViewSet = new ArrayList<>();
/** 文本 */
/** 输入变化监听 */
private OnInputTextListener mListener;
private Builder(Activity activity) {
private Builder(@NonNull Activity activity) {
mActivity = activity;
}
@ -246,12 +251,7 @@ public final class InputTextManager implements TextWatcher {
InputTextManager helper = new InputTextManager(mView, isAlpha);
helper.addViews(mViewSet);
helper.setListener(mListener);
TextInputLifecycle lifecycle = new TextInputLifecycle(mActivity, helper);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mActivity.registerActivityLifecycleCallbacks(lifecycle);
} else {
mActivity.getApplication().registerActivityLifecycleCallbacks(lifecycle);
}
TextInputLifecycle.register(mActivity, helper);
return helper;
}
}
@ -266,6 +266,15 @@ public final class InputTextManager implements TextWatcher {
mTextHelper = helper;
}
private static void register(Activity activity, InputTextManager helper) {
TextInputLifecycle lifecycle = new TextInputLifecycle(activity, helper);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
activity.registerActivityLifecycleCallbacks(lifecycle);
} else {
activity.getApplication().registerActivityLifecycleCallbacks(lifecycle);
}
}
@Override
public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) {}
@ -286,16 +295,17 @@ public final class InputTextManager implements TextWatcher {
@Override
public void onActivityDestroyed(@NonNull Activity activity) {
if (mActivity != null && mActivity == activity) {
mTextHelper.removeAllViews();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mActivity.unregisterActivityLifecycleCallbacks(this);
} else {
mActivity.getApplication().unregisterActivityLifecycleCallbacks(this);
}
mTextHelper = null;
mActivity = null;
if (mActivity != activity) {
return;
}
mTextHelper.removeAllViews();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mActivity.unregisterActivityLifecycleCallbacks(this);
} else {
mActivity.getApplication().unregisterActivityLifecycleCallbacks(this);
}
mTextHelper = null;
mActivity = null;
}
}

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSnapHelper;
@ -24,6 +25,7 @@ public final class PickerLayoutManager extends LinearLayoutManager {
private final boolean mAlpha;
private RecyclerView mRecyclerView;
@Nullable
private OnPickerListener mListener;
private PickerLayoutManager(Context context, int orientation, boolean reverseLayout, int maxItem, float scale, boolean alpha) {
@ -89,11 +91,13 @@ public final class PickerLayoutManager extends LinearLayoutManager {
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
// RecyclerView 停止滚动时
if (state == RecyclerView.SCROLL_STATE_IDLE) {
if (mListener != null) {
mListener.onPicked(mRecyclerView, getPickedPosition());
}
if (state != RecyclerView.SCROLL_STATE_IDLE) {
return;
}
if (mListener == null) {
return;
}
mListener.onPicked(mRecyclerView, getPickedPosition());
}
@Override
@ -129,14 +133,15 @@ public final class PickerLayoutManager extends LinearLayoutManager {
float mid = getWidth() / 2.0f;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
if (childView != null) {
float childMid = (getDecoratedLeft(childView) + getDecoratedRight(childView)) / 2.0f;
float scale = 1.0f + (-1 * (1 - mScale)) * (Math.min(mid, Math.abs(mid - childMid))) / mid;
childView.setScaleX(scale);
childView.setScaleY(scale);
if (mAlpha) {
childView.setAlpha(scale);
}
if (childView == null) {
continue;
}
float childMid = (getDecoratedLeft(childView) + getDecoratedRight(childView)) / 2.0f;
float scale = 1.0f + (-1 * (1 - mScale)) * (Math.min(mid, Math.abs(mid - childMid))) / mid;
childView.setScaleX(scale);
childView.setScaleY(scale);
if (mAlpha) {
childView.setAlpha(scale);
}
}
}
@ -148,14 +153,15 @@ public final class PickerLayoutManager extends LinearLayoutManager {
float mid = getHeight() / 2.0f;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
if (childView != null) {
float childMid = (getDecoratedTop(childView) + getDecoratedBottom(childView)) / 2.0f;
float scale = 1.0f + (-1 * (1 - mScale)) * (Math.min(mid, Math.abs(mid - childMid))) / mid;
childView.setScaleX(scale);
childView.setScaleY(scale);
if (mAlpha) {
childView.setAlpha(scale);
}
if (childView == null) {
continue;
}
float childMid = (getDecoratedTop(childView) + getDecoratedBottom(childView)) / 2.0f;
float scale = 1.0f + (-1 * (1 - mScale)) * (Math.min(mid, Math.abs(mid - childMid))) / mid;
childView.setScaleX(scale);
childView.setScaleY(scale);
if (mAlpha) {
childView.setAlpha(scale);
}
}
}
@ -165,16 +171,16 @@ public final class PickerLayoutManager extends LinearLayoutManager {
*/
public int getPickedPosition() {
View itemView = mLinearSnapHelper.findSnapView(this);
if(itemView != null) {
return getPosition(itemView);
if(itemView == null) {
return 0;
}
return 0;
return getPosition(itemView);
}
/**
* 设置监听器
*/
public void setOnPickerListener(OnPickerListener listener) {
public void setOnPickerListener(@Nullable OnPickerListener listener) {
mListener = listener;
}
@ -254,9 +260,7 @@ public final class PickerLayoutManager extends LinearLayoutManager {
*/
public PickerLayoutManager build() {
PickerLayoutManager layoutManager = new PickerLayoutManager(mContext, mOrientation, mReverseLayout, mMaxItem, mScale, mAlpha);
if (mListener != null) {
layoutManager.setOnPickerListener(mListener);
}
layoutManager.setOnPickerListener(mListener);
return layoutManager;
}

View File

@ -1,5 +1,7 @@
package com.hjq.demo.manager;
import com.hjq.base.action.HandlerAction;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@ -15,14 +17,18 @@ public final class ThreadPoolManager extends ThreadPoolExecutor {
private static volatile ThreadPoolManager sInstance;
public ThreadPoolManager() {
super(0, Integer.MAX_VALUE,
30L, TimeUnit.SECONDS,
// 这里最大线程数为什么不是 Int 最大值因为在华为荣耀机子上面有最大线程数限制
// 经过测试华为荣耀手机不能超过 300 个线程否则会出现内存溢出
// java.lang.OutOfMemoryErrorpthread_create (1040KB stack) failed: Out of memory
// 由于应用自身占用了一些线程数故减去 300 - 100 = 200
super(0, 200,
30L, TimeUnit.MILLISECONDS,
new SynchronousQueue<>());
}
public static ThreadPoolManager getInstance() {
if(sInstance == null) {
synchronized (ActivityManager.class) {
synchronized (ThreadPoolManager.class) {
if(sInstance == null) {
sInstance = new ThreadPoolManager();
}

View File

@ -12,7 +12,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@ -20,6 +19,8 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hjq.demo.R;
/**
* author : 王浩 & Android 轮子哥
* github : https://github.com/bingoogolapple/BGATransformersTip-Android
@ -41,16 +42,17 @@ public final class ArrowDrawable extends Drawable {
@Override
public void draw(@NonNull Canvas canvas) {
if (mPath != null) {
if (mBuilder.mShadowSize > 0) {
mPaint.setMaskFilter(new BlurMaskFilter(mBuilder.mShadowSize, BlurMaskFilter.Blur.OUTER));
mPaint.setColor(mBuilder.mShadowColor);
canvas.drawPath(mPath, mPaint);
}
mPaint.setMaskFilter(null);
mPaint.setColor(mBuilder.mBackgroundColor);
if (mPath == null) {
return;
}
if (mBuilder.mShadowSize > 0) {
mPaint.setMaskFilter(new BlurMaskFilter(mBuilder.mShadowSize, BlurMaskFilter.Blur.OUTER));
mPaint.setColor(mBuilder.mShadowColor);
canvas.drawPath(mPath, mPaint);
}
mPaint.setMaskFilter(null);
mPaint.setColor(mBuilder.mBackgroundColor);
canvas.drawPath(mPath, mPaint);
}
@Override
@ -205,8 +207,8 @@ public final class ArrowDrawable extends Drawable {
mContext = context;
mBackgroundColor = 0xFF000000;
mShadowColor = 0x33000000;
mArrowHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, mContext.getResources().getDisplayMetrics());
mRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, mContext.getResources().getDisplayMetrics());
mArrowHeight = (int) context.getResources().getDimension(R.dimen.dp_6);
mRadius = (int) context.getResources().getDimension(R.dimen.dp_4);
mShadowSize = 0;
mArrowOffsetX = 0;
mArrowOffsetY = 0;

View File

@ -8,14 +8,7 @@ import android.content.SharedPreferences;
import androidx.annotation.NonNull;
import com.hjq.demo.ui.activity.CrashActivity;
import com.hjq.demo.ui.activity.HomeActivity;
import com.hjq.demo.ui.activity.LoginActivity;
import com.hjq.demo.ui.activity.RestartActivity;
import com.hjq.demo.ui.activity.SplashActivity;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* author : Android 轮子哥
@ -29,14 +22,14 @@ public final class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String CRASH_FILE_NAME = "crash_file";
/** Crash 时间记录 */
private static final String KEY_CRASH_TIME = "key_crash_time";
/**
* 注册 Crash 监听
*/
public static void register(Application application) {
Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(application));
}
private final Application mApplication;
private final Thread.UncaughtExceptionHandler mNextHandler;
@ -61,11 +54,13 @@ public final class CrashHandler implements Thread.UncaughtExceptionHandler {
// 致命异常标记如果上次崩溃的时间距离当前崩溃小于 5 分钟那么判定为致命异常
boolean deadlyCrash = currentCrashTime - lastCrashTime < 1000 * 60 * 5;
// 如果是致命的异常或者是调试模式下
if (deadlyCrash || AppConfig.isDebug()) {
if (AppConfig.isDebug()) {
CrashActivity.start(mApplication, throwable);
} else {
RestartActivity.start(mApplication);
if (!deadlyCrash) {
// 如果不是致命的异常就自动重启应用
RestartActivity.start(mApplication);
}
}
// 不去触发系统的崩溃处理com.android.internal.os.RuntimeInit$KillApplicationHandler

View File

@ -1,103 +0,0 @@
package com.hjq.demo.other;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2019/05/09
* desc : Intent Key 管理
*/
public final class IntentKey {
// 常用相关
/** id */
public static final String ID = "id";
/** token */
public static final String TOKEN = "token";
/** 标题 */
public static final String TITLE = "title";
/** 索引 */
public static final String INDEX = "index";
/** 位置 */
public static final String POSITION = "position";
/** 状态 */
public static final String STATUS = "status";
/** 类型 */
public static final String TYPE = "type";
/** 订单 */
public static final String ORDER = "order";
/** 余额 */
public static final String BALANCE = "balance";
/** 时间 */
public static final String TIME = "time";
/** 代码 */
public static final String CODE = "code";
/** URL */
public static final String URL = "url";
/** 路径 */
public static final String PATH = "path";
/** 数量 */
public static final String AMOUNT = "amount";
/** 总数 */
public static final String COUNT = "count";
/** 标记 */
public static final String FLAG = "flag";
/** 其他 */
public static final String OTHER = "other";
// 个人信息
/** 姓名 */
public static final String NAME = "name";
/** 年龄 */
public static final String AGE = "age";
/** 性别 */
public static final String SEX = "sex";
/** 手机 */
public static final String PHONE = "phone";
/** 密码 */
public static final String PASSWORD = "password";
/** 会员 */
public static final String VIP = "vip";
/** 描述 */
public static final String DESCRIBE = "describe";
/** 备注 */
public static final String REMARK = "remark";
/** 星座 */
public static final String CONSTELLATION = "constellation";
// 地方
/** 地址 */
public static final String ADDRESS = "address";
/** 省 */
public static final String PROVINCE = "province";
/** 市 */
public static final String CITY = "city";
/** 区 */
public static final String AREA = "area";
// 文件类型相关
/** 文件 */
public static final String FILE = "file";
/** 文本 */
public static final String TEXT = "text";
/** 图片 */
public static final String IMAGE = "picture";
/** 音频 */
public static final String VOICE = "voice";
/** 视频 */
public static final String VIDEO = "video";
// 支付相关
/** 余额支付 */
public static final String BALANCE_PAY = "balance_pay";
/** 微信支付 */
public static final String WECHAT_PAY = "wechat_pay";
/** 支付宝支付 */
public static final String ALI_PAY = "ali_pay";
/** 银联支付 */
public static final String UNION_PAY = "union_pay";
}

View File

@ -11,6 +11,7 @@ import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* author : Android 轮子哥
@ -24,8 +25,9 @@ public final class KeyboardWatcher implements
private Activity mActivity;
private View mContentView;
@Nullable
private SoftKeyboardStateListener mListeners;
private boolean isSoftKeyboardOpened;
private boolean mSoftKeyboardOpened;
private int mStatusBarHeight;
public static KeyboardWatcher with(Activity activity) {
@ -62,30 +64,29 @@ public final class KeyboardWatcher implements
mContentView.getWindowVisibleDisplayFrame(r);
final int heightDiff = mContentView.getRootView().getHeight() - (r.bottom - r.top);
if (!isSoftKeyboardOpened && heightDiff > mContentView.getRootView().getHeight() / 4) {
isSoftKeyboardOpened = true;
if (!mSoftKeyboardOpened && heightDiff > mContentView.getRootView().getHeight() / 4) {
mSoftKeyboardOpened = true;
if (mListeners == null) {
return;
}
if ((mActivity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != WindowManager.LayoutParams.FLAG_FULLSCREEN) {
if (mListeners != null) {
mListeners.onSoftKeyboardOpened(heightDiff - mStatusBarHeight);
}
mListeners.onSoftKeyboardOpened(heightDiff - mStatusBarHeight);
} else {
if (mListeners != null) {
mListeners.onSoftKeyboardOpened(heightDiff);
}
mListeners.onSoftKeyboardOpened(heightDiff);
}
} else if (isSoftKeyboardOpened && heightDiff < mContentView.getRootView().getHeight() / 4) {
isSoftKeyboardOpened = false;
if (mListeners != null) {
mListeners.onSoftKeyboardClosed();
} else if (mSoftKeyboardOpened && heightDiff < mContentView.getRootView().getHeight() / 4) {
mSoftKeyboardOpened = false;
if (mListeners == null) {
return;
}
mListeners.onSoftKeyboardClosed();
}
}
/**
* 设置软键盘弹出监听
*/
public void setListener(SoftKeyboardStateListener listener) {
public void setListener(@Nullable SoftKeyboardStateListener listener) {
mListeners = listener;
}

View File

@ -0,0 +1,300 @@
package com.hjq.demo.other;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import com.hjq.demo.R;
import com.scwang.smart.refresh.header.material.CircleImageView;
import com.scwang.smart.refresh.header.material.MaterialProgressDrawable;
import com.scwang.smart.refresh.layout.api.RefreshHeader;
import com.scwang.smart.refresh.layout.api.RefreshKernel;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.constant.RefreshState;
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
import com.scwang.smart.refresh.layout.simple.SimpleComponent;
import static android.view.View.MeasureSpec.getSize;
/**
* author : 树朾 & Android 轮子哥
* github : https://github.com/scwang90/SmartRefreshLayout/tree/master/refresh-header-material
* time : 2021/02/28
* desc : Material 风格的刷新球参考 {@link com.scwang.smart.refresh.header.MaterialHeader}
*/
public final class MaterialHeader extends SimpleComponent implements RefreshHeader {
/** 刷新球大样式 */
public static final int BALL_STYLE_LARGE = 0;
/** 刷新球默认样式 */
public static final int BALL_STYLE_DEFAULT = 1;
protected static final int CIRCLE_BG_LIGHT = 0xFFFAFAFA;
protected static final float MAX_PROGRESS_ANGLE = 0.8f;
protected boolean mFinished;
protected int mCircleDiameter;
protected ImageView mCircleView;
protected MaterialProgressDrawable mProgressDrawable;
protected int mWaveHeight;
protected int mHeadHeight;
protected Path mBezierPath;
protected Paint mBezierPaint;
protected RefreshState mRefreshState;
protected boolean mShowBezierWave = false;
protected boolean mScrollableWhenRefreshing = true;
public MaterialHeader(Context context) {
this(context, null);
}
public MaterialHeader(Context context, AttributeSet attrs) {
super(context, attrs, 0);
mSpinnerStyle = SpinnerStyle.MatchLayout;
setMinimumHeight((int) getResources().getDimension(R.dimen.dp_100));
mProgressDrawable = new MaterialProgressDrawable(this);
mProgressDrawable.setColorSchemeColors(0xff0099cc, 0xffff4444, 0xff669900, 0xffaa66cc, 0xffff8800);
mCircleView = new CircleImageView(context, CIRCLE_BG_LIGHT);
mCircleView.setImageDrawable(mProgressDrawable);
mCircleView.setAlpha(0f);
addView(mCircleView);
mCircleDiameter = (int) getResources().getDimension(R.dimen.dp_40);
mBezierPath = new Path();
mBezierPaint = new Paint();
mBezierPaint.setAntiAlias(true);
mBezierPaint.setStyle(Paint.Style.FILL);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialHeader);
mShowBezierWave = typedArray.getBoolean(R.styleable.MaterialHeader_srlShowBezierWave, mShowBezierWave);
mScrollableWhenRefreshing = typedArray.getBoolean(R.styleable.MaterialHeader_srlScrollableWhenRefreshing, mScrollableWhenRefreshing);
mBezierPaint.setColor(typedArray.getColor(R.styleable.MaterialHeader_srlPrimaryColor, 0xff11bbff));
if (typedArray.hasValue(R.styleable.MaterialHeader_srlShadowRadius)) {
int radius = typedArray.getDimensionPixelOffset(R.styleable.MaterialHeader_srlShadowRadius, 0);
int color = typedArray.getColor(R.styleable.MaterialHeader_mhShadowColor, 0xff000000);
mBezierPaint.setShadowLayer(radius, 0, 0, color);
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
mShowBezierWave = typedArray.getBoolean(R.styleable.MaterialHeader_mhShowBezierWave, mShowBezierWave);
mScrollableWhenRefreshing = typedArray.getBoolean(R.styleable.MaterialHeader_mhScrollableWhenRefreshing, mScrollableWhenRefreshing);
if (typedArray.hasValue(R.styleable.MaterialHeader_mhPrimaryColor)) {
mBezierPaint.setColor(typedArray.getColor(R.styleable.MaterialHeader_mhPrimaryColor, 0xff11bbff));
}
if (typedArray.hasValue(R.styleable.MaterialHeader_mhShadowRadius)) {
int radius = typedArray.getDimensionPixelOffset(R.styleable.MaterialHeader_mhShadowRadius, 0);
int color = typedArray.getColor(R.styleable.MaterialHeader_mhShadowColor, 0xff000000);
mBezierPaint.setShadowLayer(radius, 0, 0, color);
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
typedArray.recycle();
}
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.setMeasuredDimension(getSize(widthMeasureSpec), getSize(heightMeasureSpec));
mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mCircleDiameter, MeasureSpec.EXACTLY));
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (getChildCount() == 0) {
return;
}
final int width = getMeasuredWidth();
int circleWidth = mCircleView.getMeasuredWidth();
int circleHeight = mCircleView.getMeasuredHeight();
if (isInEditMode() && mHeadHeight > 0) {
int circleTop = mHeadHeight - circleHeight / 2;
mCircleView.layout((width / 2 - circleWidth / 2), circleTop,
(width / 2 + circleWidth / 2), circleTop + circleHeight);
mProgressDrawable.showArrow(true);
mProgressDrawable.setStartEndTrim(0f, MAX_PROGRESS_ANGLE);
mProgressDrawable.setArrowScale(1);
mCircleView.setAlpha(1f);
mCircleView.setVisibility(VISIBLE);
} else {
mCircleView.layout((width / 2 - circleWidth / 2), -circleHeight, (width / 2 + circleWidth / 2), 0);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mShowBezierWave) {
// 重置画笔
mBezierPath.reset();
mBezierPath.lineTo(0, mHeadHeight);
// 绘制贝塞尔曲线
mBezierPath.quadTo(getMeasuredWidth() / 2f, mHeadHeight + mWaveHeight * 1.9f, getMeasuredWidth(), mHeadHeight);
mBezierPath.lineTo(getMeasuredWidth(), 0);
canvas.drawPath(mBezierPath, mBezierPaint);
}
super.dispatchDraw(canvas);
}
@Override
public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) {
if (!mShowBezierWave) {
kernel.requestDefaultTranslationContentFor(this, false);
}
if (isInEditMode()) {
mWaveHeight = mHeadHeight = height / 2;
}
}
@Override
public void onMoving(boolean dragging, float percent, int offset, int height, int maxDragHeight) {
if (mRefreshState == RefreshState.Refreshing) {
return;
}
if (mShowBezierWave) {
mHeadHeight = Math.min(offset, height);
mWaveHeight = Math.max(0, offset - height);
postInvalidate();
}
if (dragging || (!mProgressDrawable.isRunning() && !mFinished)) {
if (mRefreshState != RefreshState.Refreshing) {
float originalDragPercent = 1f * offset / height;
float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
float extraOs = Math.abs(offset) - height;
float tensionSlingshotPercent = Math.max(0, Math.min(extraOs, (float) height * 2)
/ (float) height);
float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
(tensionSlingshotPercent / 4), 2)) * 2f;
float strokeStart = adjustedPercent * .8f;
mProgressDrawable.showArrow(true);
mProgressDrawable.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
mProgressDrawable.setArrowScale(Math.min(1f, adjustedPercent));
float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
mProgressDrawable.setProgressRotation(rotation);
}
float targetY = offset / 2f + mCircleDiameter / 2f;
mCircleView.setTranslationY(Math.min(offset, targetY));
mCircleView.setAlpha(Math.min(1f, 4f * offset / mCircleDiameter));
}
}
@Override
public void onReleased(@NonNull RefreshLayout layout, int height, int maxDragHeight) {
mProgressDrawable.start();
}
@Override
public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) {
mRefreshState = newState;
if (newState == RefreshState.PullDownToRefresh) {
mFinished = false;
mCircleView.setVisibility(VISIBLE);
mCircleView.setTranslationY(0);
mCircleView.setScaleX(1);
mCircleView.setScaleY(1);
}
}
@Override
public int onFinish(@NonNull RefreshLayout layout, boolean success) {
mProgressDrawable.stop();
mCircleView.animate().scaleX(0).scaleY(0);
mFinished = true;
return 0;
}
/**
* 设置背景色
*/
public MaterialHeader setProgressBackgroundResource(@ColorRes int id) {
setProgressBackgroundColor(ContextCompat.getColor(getContext(), id));
return this;
}
public MaterialHeader setProgressBackgroundColor(@ColorInt int color) {
mCircleView.setBackgroundColor(color);
return this;
}
/**
* 设置 ColorScheme
*
* @param colors ColorScheme
*/
public MaterialHeader setColorSchemeColors(@ColorInt int... colors) {
mProgressDrawable.setColorSchemeColors(colors);
return this;
}
/**
* 设置 ColorScheme
*
* @param ids ColorSchemeResources
*/
public MaterialHeader setColorSchemeResources(@ColorRes int... ids) {
int[] colors = new int[ids.length];
for (int i = 0; i < ids.length; i++) {
colors[i] = ContextCompat.getColor(getContext(), ids[i]);
}
return setColorSchemeColors(colors);
}
/**
* 设置刷新球样式
*
* @param style 可传入{@link #BALL_STYLE_LARGE#BALL_STYLE_DEFAULT}
*/
public MaterialHeader setBallStyle(int style) {
if (style != BALL_STYLE_LARGE && style != BALL_STYLE_DEFAULT) {
return this;
}
if (style == BALL_STYLE_LARGE) {
mCircleDiameter = (int) getResources().getDimension(R.dimen.dp_56);
} else {
mCircleDiameter = (int) getResources().getDimension(R.dimen.dp_40);
}
// force the bounds of the progress circle inside the circle view to
// update by setting it to null before updating its size and then
// re-setting it
mCircleView.setImageDrawable(null);
mProgressDrawable.updateSizes(style);
mCircleView.setImageDrawable(mProgressDrawable);
return this;
}
/**
* 是否显示贝塞尔图形
*/
public MaterialHeader setShowBezierWave(boolean show) {
mShowBezierWave = show;
return this;
}
/**
* 设置实在正在刷新的时候可以上下滚动 Header
*/
public MaterialHeader setScrollableWhenRefreshing(boolean scrollable) {
mScrollableWhenRefreshing = scrollable;
return this;
}
}

View File

@ -6,7 +6,6 @@ import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.ColorInt;
@ -19,7 +18,6 @@ import com.scwang.smart.refresh.layout.api.RefreshFooter;
import com.scwang.smart.refresh.layout.api.RefreshLayout;
import com.scwang.smart.refresh.layout.constant.SpinnerStyle;
import com.scwang.smart.refresh.layout.simple.SimpleComponent;
import com.scwang.smart.refresh.layout.util.SmartUtil;
/**
* author : 树朾 & Android 轮子哥
@ -55,7 +53,7 @@ public final class SmartBallPulseFooter extends SimpleComponent implements Refre
public SmartBallPulseFooter(Context context, @Nullable AttributeSet attrs) {
super(context, attrs, 0);
setMinimumHeight(SmartUtil.dp2px(60));
setMinimumHeight((int) getResources().getDimension(R.dimen.dp_60));
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
@ -64,8 +62,8 @@ public final class SmartBallPulseFooter extends SimpleComponent implements Refre
mSpinnerStyle = SpinnerStyle.Translate;
mCircleSpacing = SmartUtil.dp2px(2);
mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, getResources().getDisplayMetrics()));
mCircleSpacing = getResources().getDimension(R.dimen.dp_2);
mPaint.setTextSize(getResources().getDimension(R.dimen.sp_14));
mTextWidth = mPaint.measureText(getContext().getString(R.string.common_no_more_data));
}
@ -76,7 +74,7 @@ public final class SmartBallPulseFooter extends SimpleComponent implements Refre
if (mNoMoreData) {
mPaint.setColor(0xFF898989);
canvas.drawText(getContext().getString(R.string.common_no_more_data),(width - mTextWidth) / 2,(height - mPaint.getTextSize()) / 2, mPaint);
} else{
} else {
float radius = (Math.min(width, height) - mCircleSpacing * 2) / 7;
float x = width / 2f - (radius * 2 + mCircleSpacing);
float y = height / 2f;
@ -172,4 +170,4 @@ public final class SmartBallPulseFooter extends SimpleComponent implements Refre
}
return this;
}
}
}

View File

@ -0,0 +1,98 @@
package com.hjq.demo.other;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.widget.TextView;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.core.content.ContextCompat;
import com.hjq.bar.style.LightBarStyle;
import com.hjq.demo.R;
import com.hjq.widget.view.PressAlphaTextView;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2021/02/27
* desc : 标题栏初始器
*/
public final class TitleBarStyle extends LightBarStyle {
@Override
public TextView newTitleView(Context context) {
return new AppCompatTextView(context);
}
@Override
public TextView newLeftView(Context context) {
return new PressAlphaTextView(context);
}
@Override
public TextView newRightView(Context context) {
return new PressAlphaTextView(context);
}
@Override
public Drawable getTitleBarBackground(Context context) {
return new ColorDrawable(ContextCompat.getColor(context, R.color.common_primary_color));
}
@Override
public Drawable getBackButtonDrawable(Context context) {
return ContextCompat.getDrawable(context, R.drawable.arrows_left_ic);
}
@Override
public Drawable getLeftTitleBackground(Context context) {
return null;
}
@Override
public Drawable getRightTitleBackground(Context context) {
return null;
}
@Override
public int getChildHorizontalPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.dp_12);
}
@Override
public int getChildVerticalPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.dp_14);
}
@Override
public float getTitleSize(Context context) {
return context.getResources().getDimension(R.dimen.sp_15);
}
@Override
public float getLeftTitleSize(Context context) {
return context.getResources().getDimension(R.dimen.sp_13);
}
@Override
public float getRightTitleSize(Context context) {
return context.getResources().getDimension(R.dimen.sp_13);
}
@Override
public int getTitleIconPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.dp_2);
}
@Override
public int getLeftIconPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.dp_2);
}
@Override
public int getRightIconPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.dp_2);
}
}

View File

@ -1,10 +1,9 @@
package com.hjq.demo.other;
import android.widget.Toast;
import com.hjq.demo.action.ToastAction;
import com.hjq.toast.IToastInterceptor;
import com.hjq.toast.ToastUtils;
import com.hjq.toast.config.IToastInterceptor;
import timber.log.Timber;
@ -14,10 +13,10 @@ import timber.log.Timber;
* time : 2020/11/04
* desc : 自定义 Toast 拦截器用于追踪 Toast 调用的位置
*/
public final class ToastInterceptor implements IToastInterceptor {
public final class ToastLogInterceptor implements IToastInterceptor {
@Override
public boolean intercept(Toast toast, CharSequence text) {
public boolean intercept(CharSequence text) {
if (AppConfig.isLogEnable()) {
// 获取调用的堆栈信息
StackTraceElement[] stackTrace = new Throwable().getStackTrace();

View File

@ -0,0 +1,43 @@
package com.hjq.demo.other;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.util.TypedValue;
import com.hjq.demo.R;
import com.hjq.toast.style.BlackToastStyle;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2021/02/27
* desc : Toast 样式配置
*/
public final class ToastStyle extends BlackToastStyle {
@Override
protected Drawable getBackgroundDrawable(Context context) {
GradientDrawable drawable = new GradientDrawable();
// 设置颜色
drawable.setColor(0X88000000);
// 设置圆角
drawable.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, (int) context.getResources().getDimension(R.dimen.button_circle_size), context.getResources().getDisplayMetrics()));
return drawable;
}
@Override
protected float getTextSize(Context context) {
return context.getResources().getDimension(R.dimen.sp_14);
}
@Override
protected int getHorizontalPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.sp_24);
}
@Override
protected int getVerticalPadding(Context context) {
return (int) context.getResources().getDimension(R.dimen.sp_16);
}
}

View File

@ -17,12 +17,8 @@ public final class AboutActivity extends AppActivity {
}
@Override
protected void initView() {
}
protected void initView() {}
@Override
protected void initData() {
}
protected void initData() {}
}

View File

@ -16,9 +16,8 @@ import androidx.annotation.NonNull;
import com.hjq.demo.R;
import com.hjq.demo.action.StatusAction;
import com.hjq.demo.aop.CheckNet;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.widget.BrowserView;
import com.hjq.demo.widget.StatusLayout;
import com.scwang.smart.refresh.layout.SmartRefreshLayout;
@ -34,14 +33,16 @@ import com.scwang.smart.refresh.layout.listener.OnRefreshListener;
public final class BrowserActivity extends AppActivity
implements StatusAction, OnRefreshListener {
private static final String INTENT_KEY_IN_URL = "url";
@CheckNet
@DebugLog
@Log
public static void start(Context context, String url) {
if (TextUtils.isEmpty(url)) {
return;
}
Intent intent = new Intent(context, BrowserActivity.class);
intent.putExtra(IntentKey.URL, url);
intent.putExtra(INTENT_KEY_IN_URL, url);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@ -75,9 +76,9 @@ public final class BrowserActivity extends AppActivity
protected void initData() {
showLoading();
mBrowserView.setBrowserViewClient(new MyBrowserViewClient());
mBrowserView.setBrowserChromeClient(new MyBrowserChromeClient(mBrowserView));
mBrowserView.loadUrl(getString(IntentKey.URL));
mBrowserView.setBrowserViewClient(new AppBrowserViewClient());
mBrowserView.setBrowserChromeClient(new AppBrowserChromeClient(mBrowserView));
mBrowserView.loadUrl(getString(INTENT_KEY_IN_URL));
}
@Override
@ -117,7 +118,7 @@ public final class BrowserActivity extends AppActivity
reload();
}
private class MyBrowserViewClient extends BrowserView.BrowserViewClient {
private class AppBrowserViewClient extends BrowserView.BrowserViewClient {
/**
* 网页加载错误时回调这个方法会在 onPageFinished 之前调用
@ -125,7 +126,7 @@ public final class BrowserActivity extends AppActivity
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// 这里为什么要用延迟呢因为加载出错之后会先调用 onReceivedError 再调用 onPageFinished
post(() -> showError(v -> reload()));
post(() -> showError(listener -> reload()));
}
/**
@ -147,9 +148,9 @@ public final class BrowserActivity extends AppActivity
}
}
private class MyBrowserChromeClient extends BrowserView.BrowserChromeClient {
private class AppBrowserChromeClient extends BrowserView.BrowserChromeClient {
private MyBrowserChromeClient(BrowserView view) {
private AppBrowserChromeClient(BrowserView view) {
super(view);
}
@ -158,16 +159,18 @@ public final class BrowserActivity extends AppActivity
*/
@Override
public void onReceivedTitle(WebView view, String title) {
if (title != null) {
setTitle(title);
if (title == null) {
return;
}
setTitle(title);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
if (icon != null) {
setRightIcon(new BitmapDrawable(getResources(), icon));
if (icon == null) {
return;
}
setRightIcon(new BitmapDrawable(getResources(), icon));
}
/**

View File

@ -11,11 +11,10 @@ import androidx.core.content.FileProvider;
import com.hjq.base.BaseActivity;
import com.hjq.demo.R;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.Permissions;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.other.AppConfig;
import com.hjq.demo.other.IntentKey;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
@ -32,28 +31,47 @@ import java.util.Locale;
*/
public final class CameraActivity extends AppActivity {
public static final String INTENT_KEY_IN_FILE = "file";
public static final String INTENT_KEY_IN_VIDEO = "video";
public static final String INTENT_KEY_OUT_ERROR = "error";
public static void start(BaseActivity activity, OnCameraListener listener) {
start(activity, false, listener);
}
@DebugLog
@Permissions({Permission.MANAGE_EXTERNAL_STORAGE, Permission.CAMERA})
@Log
@Permissions({Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE, Permission.CAMERA})
public static void start(BaseActivity activity, boolean video, OnCameraListener listener) {
File file = createCameraFile(video);
Intent intent = new Intent(activity, CameraActivity.class);
intent.putExtra(IntentKey.FILE, file);
intent.putExtra(IntentKey.VIDEO, video);
intent.putExtra(INTENT_KEY_IN_FILE, file);
intent.putExtra(INTENT_KEY_IN_VIDEO, video);
activity.startActivityForResult(intent, (resultCode, data) -> {
if (listener == null) {
return;
}
if (resultCode == RESULT_OK && file.isFile()) {
listener.onSelected(file);
return;
switch (resultCode) {
case RESULT_OK:
if (file.isFile()) {
listener.onSelected(file);
} else {
listener.onCancel();
}
break;
case RESULT_ERROR:
String details;
if (data == null || (details = data.getStringExtra(INTENT_KEY_OUT_ERROR)) == null) {
details = activity.getString(R.string.common_unknown_error);
}
listener.onError(details);
break;
case RESULT_CANCELED:
default:
listener.onCancel();
break;
}
listener.onCancel();
});
}
@ -63,58 +81,57 @@ public final class CameraActivity extends AppActivity {
}
@Override
protected void initView() {
}
protected void initView() {}
@Override
protected void initData() {
Intent intent;
Intent intent = new Intent();
// 启动系统相机
if (getBoolean(IntentKey.VIDEO)) {
if (getBoolean(INTENT_KEY_IN_VIDEO)) {
// 录制视频
intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
} else {
// 拍摄照片
intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
}
if (XXPermissions.isGrantedPermission(this, new String[]{Permission.MANAGE_EXTERNAL_STORAGE, Permission.CAMERA})
&& intent.resolveActivity(getPackageManager()) != null) {
File file = getSerializable(IntentKey.FILE);
if (file == null) {
toast(R.string.camera_image_error);
setResult(RESULT_CANCELED);
finish();
return;
}
Uri imageUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 通过 FileProvider 创建一个 Content 类型的 Uri 文件
imageUri = FileProvider.getUriForFile(this, AppConfig.getPackageName() + ".provider", file);
} else {
imageUri = Uri.fromFile(file);
}
// 对目标应用临时授权该 Uri 所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 将拍取的照片保存到指定 Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, (resultCode, data) -> {
if (resultCode == RESULT_OK) {
// 通知系统多媒体扫描该文件否则会导致拍摄出来的图片或者视频没有及时显示到相册中而需要通过重启手机才能看到
MediaScannerConnection.scanFile(getApplicationContext(), new String[]{file.getPath()}, null, null);
}
setResult(resultCode);
finish();
});
} else {
toast(R.string.camera_launch_fail);
if (intent.resolveActivity(getPackageManager()) == null ||
!XXPermissions.isGranted(this, Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE, Permission.CAMERA)) {
setResult(RESULT_ERROR, new Intent().putExtra(INTENT_KEY_OUT_ERROR, getString(R.string.camera_launch_fail)));
finish();
return;
}
File file = getSerializable(INTENT_KEY_IN_FILE);
if (file == null) {
setResult(RESULT_ERROR, new Intent().putExtra(INTENT_KEY_OUT_ERROR, getString(R.string.camera_image_error)));
finish();
return;
}
Uri imageUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// 通过 FileProvider 创建一个 Content 类型的 Uri 文件
imageUri = FileProvider.getUriForFile(this, AppConfig.getPackageName() + ".provider", file);
} else {
imageUri = Uri.fromFile(file);
}
// 对目标应用临时授权该 Uri 所代表的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// 将拍取的照片保存到指定 Uri
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, (resultCode, data) -> {
if (resultCode == RESULT_OK) {
// 通知系统多媒体扫描该文件否则会导致拍摄出来的图片或者视频没有及时显示到相册中而需要通过重启手机才能看到
MediaScannerConnection.scanFile(getApplicationContext(), new String[]{file.getPath()}, null, null);
}
setResult(resultCode);
finish();
});
}
/**
* 创建一个拍照图片文件对象
* 创建一个拍照图片文件路径
*/
private static File createCameraFile(boolean video) {
File folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "Camera");
@ -141,6 +158,13 @@ public final class CameraActivity extends AppActivity {
*/
void onSelected(File file);
/**
* 错误回调
*
* @param details 错误详情
*/
void onError(String details);
/**
* 取消回调
*/

View File

@ -6,16 +6,17 @@ import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
@ -25,9 +26,9 @@ import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.manager.ThreadPoolManager;
import com.hjq.demo.other.AppConfig;
import com.hjq.demo.other.IntentKey;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -49,6 +50,12 @@ import java.util.regex.Pattern;
*/
public final class CrashActivity extends AppActivity {
private static final String INTENT_KEY_IN_THROWABLE = "throwable";
/** 系统包前缀列表 */
private static final String[] SYSTEM_PACKAGE_PREFIX_LIST = new String[]
{"android", "com.android", "androidx", "com.google.android", "java", "javax", "dalvik", "kotlin"};
/** 报错代码行数正则表达式 */
private static final Pattern CODE_REGEX = Pattern.compile("\\(\\w+\\.\\w+:\\d+\\)");
@ -57,7 +64,7 @@ public final class CrashActivity extends AppActivity {
return;
}
Intent intent = new Intent(application, CrashActivity.class);
intent.putExtra(IntentKey.OTHER, throwable);
intent.putExtra(INTENT_KEY_IN_THROWABLE, throwable);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
application.startActivity(intent);
}
@ -89,7 +96,7 @@ public final class CrashActivity extends AppActivity {
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
protected void initData() {
Throwable throwable = getSerializable(IntentKey.OTHER);
Throwable throwable = getSerializable(INTENT_KEY_IN_THROWABLE);
if (throwable == null) {
return;
}
@ -107,23 +114,45 @@ public final class CrashActivity extends AppActivity {
Matcher matcher = CODE_REGEX.matcher(mStackTrace);
SpannableStringBuilder spannable = new SpannableStringBuilder(mStackTrace);
if (spannable.length() > 0) {
for (int index = 0; matcher.find(); index++) {
while (matcher.find()) {
// 不包含左括号
int start = matcher.start() + "(".length();
// 不包含右括号
int end = matcher.end() - ")".length();
// 代码信息颜色
int codeColor = 0xFF999999;
int lineIndex = mStackTrace.lastIndexOf("at ", start);
if (lineIndex != -1) {
String lineData = spannable.subSequence(lineIndex, start).toString();
if (TextUtils.isEmpty(lineData)) {
continue;
}
// 是否高亮代码行数
boolean highlight = true;
for (String packagePrefix : SYSTEM_PACKAGE_PREFIX_LIST) {
if (lineData.startsWith("at " + packagePrefix)) {
highlight = false;
break;
}
}
if (highlight) {
codeColor = 0xFF287BDE;
}
}
// 设置前景
spannable.setSpan(new ForegroundColorSpan(index < 3 ? 0xFF287BDE : 0xFF999999), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(codeColor), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置下划线
spannable.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
mMessageView.setText(spannable);
}
Resources res = getResources();
DisplayMetrics displayMetrics = res.getDisplayMetrics();
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
float smallestWidth = Math.min(screenWidth, screenHeight) / displayMetrics.density;
String targetResource;
if (displayMetrics.densityDpi > 480) {
@ -147,7 +176,9 @@ public final class CrashActivity extends AppActivity {
builder.append("\n屏幕宽高\t").append(screenWidth).append(" x ").append(screenHeight)
.append("\n屏幕密度\t").append(displayMetrics.densityDpi)
.append("\n目标资源\t").append(targetResource);
.append("\n密度像素\t").append(displayMetrics.density)
.append("\n目标资源\t").append(targetResource)
.append("\n最小宽度\t").append((int) smallestWidth);
builder.append("\n安卓版本\t").append(Build.VERSION.RELEASE)
.append("\nAPI 版本:\t").append(Build.VERSION.SDK_INT)
@ -165,18 +196,18 @@ public final class CrashActivity extends AppActivity {
List<String> permissions = Arrays.asList(packageInfo.requestedPermissions);
if (permissions.contains(Permission.MANAGE_EXTERNAL_STORAGE)) {
builder.append("\n存储权限\t").append(XXPermissions.isGrantedPermission(this, Permission.MANAGE_EXTERNAL_STORAGE) ? "已获得" : "未获得");
if (permissions.contains(Permission.READ_EXTERNAL_STORAGE) || permissions.contains(Permission.WRITE_EXTERNAL_STORAGE)) {
builder.append("\n存储权限\t").append(XXPermissions.isGranted(this, Permission.Group.STORAGE) ? "已获得" : "未获得");
}
if (permissions.contains(Permission.ACCESS_FINE_LOCATION) || permissions.contains(Permission.ACCESS_COARSE_LOCATION)) {
builder.append("\n定位权限\t");
if (XXPermissions.isGrantedPermission(this, Permission.Group.LOCATION)) {
if (XXPermissions.isGranted(this, Permission.ACCESS_FINE_LOCATION, Permission.ACCESS_COARSE_LOCATION)) {
builder.append("精确、粗略");
} else {
if (XXPermissions.isGrantedPermission(this, Permission.ACCESS_FINE_LOCATION)) {
if (XXPermissions.isGranted(this, Permission.ACCESS_FINE_LOCATION)) {
builder.append("精确");
} else if (XXPermissions.isGrantedPermission(this, Permission.ACCESS_COARSE_LOCATION)) {
} else if (XXPermissions.isGranted(this, Permission.ACCESS_COARSE_LOCATION)) {
builder.append("粗略");
} else {
builder.append("未获得");
@ -185,19 +216,19 @@ public final class CrashActivity extends AppActivity {
}
if (permissions.contains(Permission.CAMERA)) {
builder.append("\n相机权限\t").append(XXPermissions.isGrantedPermission(this, Permission.CAMERA) ? "已获得" : "未获得");
builder.append("\n相机权限\t").append(XXPermissions.isGranted(this, Permission.CAMERA) ? "已获得" : "未获得");
}
if (permissions.contains(Permission.RECORD_AUDIO)) {
builder.append("\n录音权限\t").append(XXPermissions.isGrantedPermission(this, Permission.RECORD_AUDIO) ? "已获得" : "未获得");
builder.append("\n录音权限\t").append(XXPermissions.isGranted(this, Permission.RECORD_AUDIO) ? "已获得" : "未获得");
}
if (permissions.contains(Permission.SYSTEM_ALERT_WINDOW)) {
builder.append("\n悬浮窗权限\t").append(XXPermissions.isGrantedPermission(this, Permission.SYSTEM_ALERT_WINDOW) ? "已获得" : "未获得");
builder.append("\n悬浮窗权限\t").append(XXPermissions.isGranted(this, Permission.SYSTEM_ALERT_WINDOW) ? "已获得" : "未获得");
}
if (permissions.contains(Permission.REQUEST_INSTALL_PACKAGES)) {
builder.append("\n安装包权限\t").append(XXPermissions.isGrantedPermission(this, Permission.REQUEST_INSTALL_PACKAGES) ? "已获得" : "未获得");
builder.append("\n安装包权限\t").append(XXPermissions.isGranted(this, Permission.REQUEST_INSTALL_PACKAGES) ? "已获得" : "未获得");
}
if (permissions.contains(Manifest.permission.INTERNET)) {
@ -217,7 +248,9 @@ public final class CrashActivity extends AppActivity {
mInfoView.setText(builder);
}
} catch (PackageManager.NameNotFoundException ignored) {}
} catch (PackageManager.NameNotFoundException e) {
CrashReport.postCatchedException(e);
}
}
@SingleClick
@ -233,16 +266,23 @@ public final class CrashActivity extends AppActivity {
intent.putExtra(Intent.EXTRA_TEXT, mStackTrace);
startActivity(Intent.createChooser(intent, ""));
} else if (viewId == R.id.iv_crash_restart) {
// 重启应用
RestartActivity.restart(this);
finish();
onBackPressed();
}
}
@Override
public void onBackPressed() {
// 按返回键重启应用
onClick(findViewById(R.id.iv_crash_restart));
// 重启应用
RestartActivity.restart(this);
finish();
}
@NonNull
@Override
protected ImmersionBar createStatusBarConfig() {
return super.createStatusBarConfig()
// 指定导航栏背景颜色
.navigationBarColor(R.color.white);
}
/**

View File

@ -14,7 +14,6 @@ import com.hjq.demo.app.AppActivity;
import com.hjq.demo.manager.DialogManager;
import com.hjq.demo.ui.dialog.AddressDialog;
import com.hjq.demo.ui.dialog.DateDialog;
import com.hjq.demo.ui.dialog.HintDialog;
import com.hjq.demo.ui.dialog.InputDialog;
import com.hjq.demo.ui.dialog.MenuDialog;
import com.hjq.demo.ui.dialog.MessageDialog;
@ -23,13 +22,15 @@ import com.hjq.demo.ui.dialog.SafeDialog;
import com.hjq.demo.ui.dialog.SelectDialog;
import com.hjq.demo.ui.dialog.ShareDialog;
import com.hjq.demo.ui.dialog.TimeDialog;
import com.hjq.demo.ui.dialog.TipsDialog;
import com.hjq.demo.ui.dialog.UpdateDialog;
import com.hjq.demo.ui.dialog.WaitDialog;
import com.hjq.demo.ui.popup.ListPopup;
import com.hjq.demo.wxapi.WXEntryActivity;
import com.hjq.umeng.Platform;
import com.hjq.umeng.UmengClient;
import com.hjq.umeng.UmengShare;
import com.umeng.socialize.media.UMImage;
import com.umeng.socialize.media.UMWeb;
import java.util.ArrayList;
import java.util.Calendar;
@ -239,24 +240,24 @@ public final class DialogActivity extends AppActivity {
} else if (viewId == R.id.btn_dialog_succeed_toast) {
// 成功对话框
new HintDialog.Builder(this)
.setIcon(HintDialog.ICON_FINISH)
new TipsDialog.Builder(this)
.setIcon(TipsDialog.ICON_FINISH)
.setMessage("完成")
.show();
} else if (viewId == R.id.btn_dialog_fail_toast) {
// 失败对话框
new HintDialog.Builder(this)
.setIcon(HintDialog.ICON_ERROR)
new TipsDialog.Builder(this)
.setIcon(TipsDialog.ICON_ERROR)
.setMessage("错误")
.show();
} else if (viewId == R.id.btn_dialog_warn_toast) {
// 警告对话框
new HintDialog.Builder(this)
.setIcon(HintDialog.ICON_WARNING)
new TipsDialog.Builder(this)
.setIcon(TipsDialog.ICON_WARNING)
.setMessage("警告")
.show();
@ -407,18 +408,16 @@ public final class DialogActivity extends AppActivity {
} else if (viewId == R.id.btn_dialog_share) {
toast("记得改好第三方 AppID 和 AppKey否则会调不起来哦");
toast("也别忘了改微信 " + WXEntryActivity.class.getSimpleName() + " 类所在的包名哦");
toast("记得改好第三方 AppID 和 Secret否则会调不起来哦");
UMWeb content = new UMWeb("https://github.com/getActivity/AndroidProject");
content.setTitle("Github");
content.setThumb(new UMImage(this, R.mipmap.launcher_ic));
content.setDescription(getString(R.string.app_name));
// 分享对话框
new ShareDialog.Builder(this)
// 分享标题
.setShareTitle("Github")
// 分享描述
.setShareDescription("AndroidProject")
// 分享缩略图
.setShareLogo(R.mipmap.launcher_ic)
// 分享链接
.setShareUrl("https://github.com/getActivity/AndroidProject")
.setShareLink(content)
.setListener(new UmengShare.OnShareListener() {
@Override
@ -428,7 +427,7 @@ public final class DialogActivity extends AppActivity {
@Override
public void onError(Platform platform, Throwable t) {
toast("分享出错");
toast(t.getMessage());
}
@Override
@ -447,11 +446,11 @@ public final class DialogActivity extends AppActivity {
// 是否强制更新
.setForceUpdate(false)
// 更新日志
.setUpdateLog("到底更新了啥\n到底更新了啥\n到底更新了啥\n到底更新了啥\n到底更新了啥")
.setUpdateLog("到底更新了啥\n到底更新了啥\n到底更新了啥\n到底更新了啥\n到底更新了啥\n到底更新了啥")
// 下载 URL
.setDownloadUrl("https://dldir1.qq.com/weixin/android/weixin7014android1660.apk")
.setDownloadUrl("https://dldir1.qq.com/weixin/android/weixin807android1920_arm64.apk")
// 文件 MD5
.setFileMd5("6ec99cb762ffd9158e8b27dc33d9680d")
.setFileMd5("df2f045dfa854d8461d9cefe08b813c8")
.show();
} else if (viewId == R.id.btn_dialog_safe) {
@ -511,13 +510,6 @@ public final class DialogActivity extends AppActivity {
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 友盟分享回调
UmengClient.onActivityResult(this, requestCode, resultCode, data);
}
@Override
public void onRightClick(View view) {
// 菜单弹窗
@ -528,4 +520,11 @@ public final class DialogActivity extends AppActivity {
.setListener((ListPopup.OnListener<String>) (popupWindow, position, s) -> toast("点击了:" + s))
.showAsDropDown(view);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 友盟回调
UmengClient.onActivityResult(this, requestCode, resultCode, data);
}
}

View File

@ -1,9 +1,13 @@
package com.hjq.demo.ui.activity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import androidx.annotation.NonNull;
import androidx.viewpager2.widget.ViewPager2;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
@ -41,9 +45,6 @@ public final class GuideActivity extends AppActivity {
@Override
protected void initData() {
mAdapter = new GuideAdapter(this);
mAdapter.addItem(R.drawable.guide_1_bg);
mAdapter.addItem(R.drawable.guide_2_bg);
mAdapter.addItem(R.drawable.guide_3_bg);
mViewPager.setAdapter(mAdapter);
mViewPager.registerOnPageChangeCallback(mCallback);
mIndicatorView.setViewPager(mViewPager);
@ -64,22 +65,44 @@ public final class GuideActivity extends AppActivity {
mViewPager.unregisterOnPageChangeCallback(mCallback);
}
@NonNull
@Override
protected ImmersionBar createStatusBarConfig() {
return super.createStatusBarConfig()
// 指定导航栏背景颜色
.navigationBarColor(R.color.white);
}
private final ViewPager2.OnPageChangeCallback mCallback = new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mViewPager.getCurrentItem() == mAdapter.getItemCount() - 1 && positionOffsetPixels > 0) {
mIndicatorView.setVisibility(View.VISIBLE);
mCompleteView.setVisibility(View.INVISIBLE);
if (mViewPager.getCurrentItem() != mAdapter.getCount() - 1 || positionOffsetPixels <= 0) {
return;
}
mIndicatorView.setVisibility(View.VISIBLE);
mCompleteView.setVisibility(View.INVISIBLE);
mCompleteView.clearAnimation();
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager2.SCROLL_STATE_IDLE) {
boolean last = mViewPager.getCurrentItem() == mAdapter.getItemCount() - 1;
mIndicatorView.setVisibility(last ? View.INVISIBLE : View.VISIBLE);
mCompleteView.setVisibility(last ? View.VISIBLE : View.INVISIBLE);
if (state != ViewPager2.SCROLL_STATE_IDLE) {
return;
}
boolean lastItem = mViewPager.getCurrentItem() == mAdapter.getCount() - 1;
mIndicatorView.setVisibility(lastItem ? View.INVISIBLE : View.VISIBLE);
mCompleteView.setVisibility(lastItem ? View.VISIBLE : View.INVISIBLE);
if (lastItem) {
// 按钮呼吸动效
ScaleAnimation animation = new ScaleAnimation(1.0f, 1.1f, 1.0f, 1.1f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(350);
animation.setRepeatMode(Animation.REVERSE);
animation.setRepeatCount(Animation.INFINITE);
mCompleteView.startAnimation(animation);
}
}
};

View File

@ -3,24 +3,25 @@ package com.hjq.demo.ui.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.view.Menu;
import android.view.MenuItem;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.base.FragmentPagerAdapter;
import com.hjq.demo.R;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.app.AppFragment;
import com.hjq.demo.manager.ActivityManager;
import com.hjq.demo.other.DoubleClickHelper;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.adapter.NavigationAdapter;
import com.hjq.demo.ui.fragment.FindFragment;
import com.hjq.demo.ui.fragment.HomeFragment;
import com.hjq.demo.ui.fragment.MeFragment;
import com.hjq.demo.ui.fragment.MessageFragment;
import com.hjq.demo.ui.fragment.MineFragment;
/**
* author : Android 轮子哥
@ -29,11 +30,15 @@ import com.hjq.demo.ui.fragment.MessageFragment;
* desc : 首页界面
*/
public final class HomeActivity extends AppActivity
implements BottomNavigationView.OnNavigationItemSelectedListener {
implements NavigationAdapter.OnNavigationListener {
private static final String INTENT_KEY_IN_FRAGMENT_INDEX = "fragmentIndex";
private static final String INTENT_KEY_IN_FRAGMENT_CLASS = "fragmentClass";
private ViewPager mViewPager;
private BottomNavigationView mBottomNavigationView;
private RecyclerView mNavigationView;
private NavigationAdapter mNavigationAdapter;
private FragmentPagerAdapter<AppFragment<?>> mPagerAdapter;
public static void start(Context context) {
@ -42,7 +47,7 @@ public final class HomeActivity extends AppActivity
public static void start(Context context, Class<? extends AppFragment<?>> fragmentClass) {
Intent intent = new Intent(context, HomeActivity.class);
intent.putExtra(IntentKey.INDEX, fragmentClass);
intent.putExtra(INTENT_KEY_IN_FRAGMENT_CLASS, fragmentClass);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@ -57,18 +62,19 @@ public final class HomeActivity extends AppActivity
@Override
protected void initView() {
mViewPager = findViewById(R.id.vp_home_pager);
mBottomNavigationView = findViewById(R.id.bv_home_navigation);
mNavigationView = findViewById(R.id.rv_home_navigation);
// 不使用图标默认变色
mBottomNavigationView.setItemIconTintList(null);
// 设置导航栏条目点击事件
mBottomNavigationView.setOnNavigationItemSelectedListener(this);
// 屏蔽底部导航栏长按文本提示
Menu menu = mBottomNavigationView.getMenu();
for (int i = 0; i < menu.size(); i++) {
mBottomNavigationView.findViewById(menu.getItem(i).getItemId()).setOnLongClickListener(v -> true);
}
mNavigationAdapter = new NavigationAdapter(this);
mNavigationAdapter.addItem(new NavigationAdapter.MenuItem(getString(R.string.home_nav_index),
ContextCompat.getDrawable(this, R.drawable.home_home_selector)));
mNavigationAdapter.addItem(new NavigationAdapter.MenuItem(getString(R.string.home_nav_found),
ContextCompat.getDrawable(this, R.drawable.home_found_selector)));
mNavigationAdapter.addItem(new NavigationAdapter.MenuItem(getString(R.string.home_nav_message),
ContextCompat.getDrawable(this, R.drawable.home_message_selector)));
mNavigationAdapter.addItem(new NavigationAdapter.MenuItem(getString(R.string.home_nav_me),
ContextCompat.getDrawable(this, R.drawable.home_me_selector)));
mNavigationAdapter.setOnNavigationListener(this);
mNavigationView.setAdapter(mNavigationAdapter);
}
@Override
@ -77,7 +83,7 @@ public final class HomeActivity extends AppActivity
mPagerAdapter.addFragment(HomeFragment.newInstance());
mPagerAdapter.addFragment(FindFragment.newInstance());
mPagerAdapter.addFragment(MessageFragment.newInstance());
mPagerAdapter.addFragment(MeFragment.newInstance());
mPagerAdapter.addFragment(MineFragment.newInstance());
mViewPager.setAdapter(mPagerAdapter);
onNewIntent(getIntent());
@ -86,24 +92,35 @@ public final class HomeActivity extends AppActivity
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
int fragmentIndex = mPagerAdapter.getFragmentIndex(getSerializable(IntentKey.INDEX));
switchFragment(mPagerAdapter.getFragmentIndex(getSerializable(INTENT_KEY_IN_FRAGMENT_CLASS)));
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// 保存当前 Fragment 索引位置
outState.putInt(INTENT_KEY_IN_FRAGMENT_INDEX, mViewPager.getCurrentItem());
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 恢复当前 Fragment 索引位置
switchFragment(savedInstanceState.getInt(INTENT_KEY_IN_FRAGMENT_INDEX));
}
private void switchFragment(int fragmentIndex) {
if (fragmentIndex == -1) {
return;
}
mViewPager.setCurrentItem(fragmentIndex);
switch (fragmentIndex) {
case 0:
mBottomNavigationView.setSelectedItemId(R.id.menu_home);
break;
case 1:
mBottomNavigationView.setSelectedItemId(R.id.home_found);
break;
case 2:
mBottomNavigationView.setSelectedItemId(R.id.home_message);
break;
case 3:
mBottomNavigationView.setSelectedItemId(R.id.home_me);
mViewPager.setCurrentItem(fragmentIndex);
mNavigationAdapter.setSelectedPosition(fragmentIndex);
break;
default:
break;
@ -111,26 +128,29 @@ public final class HomeActivity extends AppActivity
}
/**
* {@link BottomNavigationView.OnNavigationItemSelectedListener}
* {@link NavigationAdapter.OnNavigationListener}
*/
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.menu_home) {
mViewPager.setCurrentItem(0);
return true;
} else if (itemId == R.id.home_found) {
mViewPager.setCurrentItem(1);
return true;
} else if (itemId == R.id.home_message) {
mViewPager.setCurrentItem(2);
return true;
} else if (itemId == R.id.home_me) {
mViewPager.setCurrentItem(3);
return true;
public boolean onNavigationItemSelected(int position) {
switch (position) {
case 0:
case 1:
case 2:
case 3:
mViewPager.setCurrentItem(position);
return true;
default:
return false;
}
return false;
}
@NonNull
@Override
protected ImmersionBar createStatusBarConfig() {
return super.createStatusBarConfig()
// 指定导航栏背景颜色
.navigationBarColor(R.color.white);
}
@Override
@ -154,6 +174,7 @@ public final class HomeActivity extends AppActivity
protected void onDestroy() {
super.onDestroy();
mViewPager.setAdapter(null);
mBottomNavigationView.setOnNavigationItemSelectedListener(null);
mNavigationView.setAdapter(null);
mNavigationAdapter.setOnNavigationListener(null);
}
}

View File

@ -0,0 +1,232 @@
package com.hjq.demo.ui.activity;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import androidx.core.content.FileProvider;
import com.hjq.base.BaseActivity;
import com.hjq.demo.R;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.Permissions;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.other.AppConfig;
import com.hjq.permissions.Permission;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2021/09/19
* desc : 图片裁剪
*/
public final class ImageCropActivity extends AppActivity {
private static final String INTENT_KEY_IN_SOURCE_IMAGE_PATH = "imagePath";
private static final String INTENT_KEY_IN_CROP_RATIO_X = "cropRatioX";
private static final String INTENT_KEY_IN_CROP_RATIO_Y = "cropRatioY";
public static final String INTENT_KEY_OUT_FILE_URI = "fileUri";
public static final String INTENT_KEY_OUT_FILE_NAME = "fileName";
public static final String INTENT_KEY_OUT_ERROR = "error";
public static void start(BaseActivity activity, File file, OnCropListener listener) {
start(activity, file, 0, 0, listener);
}
@Log
@Permissions({Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE})
public static void start(BaseActivity activity, File file, int cropRatioX, int cropRatioY, OnCropListener listener) {
Intent intent = new Intent(activity, ImageCropActivity.class);
intent.putExtra(INTENT_KEY_IN_SOURCE_IMAGE_PATH, file.toString());
intent.putExtra(INTENT_KEY_IN_CROP_RATIO_X, cropRatioX);
intent.putExtra(INTENT_KEY_IN_CROP_RATIO_Y, cropRatioY);
activity.startActivityForResult(intent, (resultCode, data) -> {
if (listener == null) {
return;
}
switch (resultCode) {
case RESULT_OK:
Uri uri = null;
if (data != null) {
uri = data.getParcelableExtra(INTENT_KEY_OUT_FILE_URI);
}
if (uri != null) {
listener.onSucceed(uri, data.getStringExtra(INTENT_KEY_OUT_FILE_NAME));
} else {
listener.onCancel();
}
break;
case RESULT_ERROR:
String details;
if (data == null || (details = data.getStringExtra(INTENT_KEY_OUT_ERROR)) == null) {
details = activity.getString(R.string.common_unknown_error);
}
listener.onError(details);
break;
case RESULT_CANCELED:
default:
listener.onCancel();
break;
}
});
}
@Override
protected int getLayoutId() {
return 0;
}
@Override
protected void initView() {}
@Override
protected void initData() {
File sourceFile = new File(getString(INTENT_KEY_IN_SOURCE_IMAGE_PATH));
int cropRatioX = getInt(INTENT_KEY_IN_CROP_RATIO_X);
int cropRatioY = getInt(INTENT_KEY_IN_CROP_RATIO_Y);
Intent intent = new Intent("com.android.camera.action.CROP");
Uri sourceUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sourceUri = FileProvider.getUriForFile(getContext(), AppConfig.getPackageName() + ".provider", sourceFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} else {
sourceUri = Uri.fromFile(sourceFile);
}
String fileName = "CROP_" + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()) +
"." + getImageFormat(sourceFile).toString().toLowerCase();
String subFolderName = "CropImage";
intent.setDataAndType(sourceUri, "image/*");
// 是否进行裁剪
intent.putExtra("crop", String.valueOf(true));
// 是否裁剪成圆形注意在某些手机上没有任何效果例如华为机
//intent.putExtra("circleCrop", true);
// 宽高裁剪大小
//intent.putExtra("outputX", builder.getCropWidth());
//intent.putExtra("outputY", builder.getCropHeight());
if (cropRatioX != 0 && cropRatioY != 0) {
// 宽高裁剪比例
if (cropRatioX == cropRatioY &&
Build.MANUFACTURER.toUpperCase().contains("HUAWEI")) {
// 华为手机特殊处理否则不会显示正方形裁剪区域而是显示圆形裁剪区域
// https://blog.csdn.net/wapchief/article/details/80669647
intent.putExtra("aspectX", 9998);
intent.putExtra("aspectY", 9999);
} else {
intent.putExtra("aspectX", cropRatioX);
intent.putExtra("aspectY", cropRatioY);
}
}
// 是否保持比例不变
intent.putExtra("scale", true);
// 裁剪区域小于输出大小时是否放大图像
intent.putExtra("scaleUpIfNeeded", true);
// 是否将数据以 Bitmap 的形式保存
intent.putExtra("return-data", false);
// 设置裁剪后保存的文件路径
Uri outputUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 适配 Android 10 分区存储特性
ContentValues values = new ContentValues();
// 设置显示的文件名
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
// 设置输出的路径信息
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DCIM + File.separator + subFolderName);
// 生成一个新的 uri 路径
outputUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
File folderFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + File.separator + subFolderName);
if (!folderFile.isDirectory()) {
folderFile.delete();
}
if (!folderFile.exists()) {
folderFile.mkdirs();
}
outputUri = Uri.fromFile(new File(folderFile, fileName));
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
// 设置裁剪后保存的文件格式
intent.putExtra("outputFormat", getImageFormat(sourceFile).toString());
try {
// 因为有人反馈 Intent.resolveActivity 在某些手机上判断不准确
// 手机明明有裁剪界面但是系统偏偏就告诉应用没有
// 出现这个问题的机型信息一加手机 8Android 11
startActivityForResult(intent, (resultCode, data) -> {
if (resultCode == RESULT_OK) {
setResult(RESULT_OK, new Intent()
.putExtra(INTENT_KEY_OUT_FILE_URI, outputUri)
.putExtra(INTENT_KEY_OUT_FILE_NAME, fileName));
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 删除这个 uri避免重复占用
getContentResolver().delete(outputUri, null, null);
}
setResult(RESULT_CANCELED);
}
finish();
});
} catch (ActivityNotFoundException e) {
CrashReport.postCatchedException(e);
setResult(RESULT_ERROR, new Intent().putExtra(INTENT_KEY_OUT_ERROR, getString(R.string.image_crop_error_not_support)));
finish();
}
}
/**
* 获取图片文件的格式
*/
private static Bitmap.CompressFormat getImageFormat(File file) {
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".png")) {
return Bitmap.CompressFormat.PNG;
} else if (fileName.endsWith(".webp")) {
return Bitmap.CompressFormat.WEBP;
}
return Bitmap.CompressFormat.JPEG;
}
/**
* 裁剪图片监听
*/
public interface OnCropListener {
/**
* 裁剪成功回调
*
* @param fileUri 文件路径
* @param fileName 文件名称
*/
void onSucceed(Uri fileUri, String fileName);
/**
* 错误回调
*
* @param details 错误详情
*/
void onError(String details);
/**
* 取消回调
*/
default void onCancel() {}
}
}

View File

@ -16,10 +16,8 @@ import com.gyf.immersionbar.ImmersionBar;
import com.hjq.base.BaseAdapter;
import com.hjq.base.RecyclerPagerAdapter;
import com.hjq.demo.R;
import com.hjq.demo.aop.CheckNet;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.adapter.ImagePreviewAdapter;
import java.util.ArrayList;
@ -38,6 +36,9 @@ public final class ImagePreviewActivity extends AppActivity
implements ViewPager.OnPageChangeListener,
BaseAdapter.OnItemClickListener {
private static final String INTENT_KEY_IN_IMAGE_LIST = "imageList";
private static final String INTENT_KEY_IN_IMAGE_INDEX = "imageIndex";
public static void start(Context context, String url) {
ArrayList<String> images = new ArrayList<>(1);
images.add(url);
@ -48,27 +49,26 @@ public final class ImagePreviewActivity extends AppActivity
start(context, urls, 0);
}
@CheckNet
@DebugLog
@Log
public static void start(Context context, List<String> urls, int index) {
if (urls == null || urls.isEmpty()) {
return;
}
Intent intent = new Intent(context, ImagePreviewActivity.class);
if (urls.size() > 2500) {
if (urls.size() > 2000) {
// 请注意如果传输的数据量过大会抛出此异常并且这种异常是不能被捕获的
// 所以当图片数量过多的时候我们应当只显示一张这种一般是手机图片过多导致的
// 经过测试传入 3121 张图片集合的时候会抛出此异常所以保险值应当是 2500
// 经过测试传入 3121 张图片集合的时候会抛出此异常所以保险值应当是 2000
// android.os.TransactionTooLargeException: data parcel size 521984 bytes
urls = Collections.singletonList(urls.get(index));
}
if (urls instanceof ArrayList) {
intent.putExtra(IntentKey.IMAGE, (ArrayList<String>) urls);
intent.putExtra(INTENT_KEY_IN_IMAGE_LIST, (ArrayList<String>) urls);
} else {
intent.putExtra(IntentKey.IMAGE, new ArrayList<>(urls));
intent.putExtra(INTENT_KEY_IN_IMAGE_LIST, new ArrayList<>(urls));
}
intent.putExtra(IntentKey.INDEX, index);
intent.putExtra(INTENT_KEY_IN_IMAGE_INDEX, index);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@ -97,7 +97,7 @@ public final class ImagePreviewActivity extends AppActivity
@Override
protected void initData() {
ArrayList<String> images = getStringArrayList(IntentKey.IMAGE);
ArrayList<String> images = getStringArrayList(INTENT_KEY_IN_IMAGE_LIST);
if (images == null || images.isEmpty()) {
finish();
return;
@ -117,7 +117,7 @@ public final class ImagePreviewActivity extends AppActivity
mViewPager.addOnPageChangeListener(this);
}
int index = getInt(IntentKey.INDEX);
int index = getInt(INTENT_KEY_IN_IMAGE_INDEX);
if (index < images.size()) {
mViewPager.setCurrentItem(index);
onPageSelected(index);
@ -148,7 +148,7 @@ public final class ImagePreviewActivity extends AppActivity
@SuppressLint("SetTextI18n")
@Override
public void onPageSelected(int position) {
mTextIndicatorView.setText((position + 1) + "/" + mAdapter.getItemCount());
mTextIndicatorView.setText((position + 1) + "/" + mAdapter.getCount());
}
@Override
@ -168,9 +168,10 @@ public final class ImagePreviewActivity extends AppActivity
*/
@Override
public void onItemClick(RecyclerView recyclerView, View itemView, int position) {
// 单击图片退出当前的 Activity
if (!isFinishing()) {
finish();
if (isFinishing() || isDestroyed()) {
return;
}
// 单击图片退出当前的 Activity
finish();
}
}

View File

@ -6,29 +6,28 @@ import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AnimationUtils;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hjq.base.BaseActivity;
import com.hjq.base.BaseAdapter;
import com.hjq.demo.R;
import com.hjq.demo.action.StatusAction;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.Permissions;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.manager.ThreadPoolManager;
import com.hjq.demo.other.GridSpaceDecoration;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.adapter.ImageSelectAdapter;
import com.hjq.demo.ui.dialog.AlbumDialog;
import com.hjq.demo.widget.StatusLayout;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.hjq.widget.view.FloatActionButton;
import java.io.File;
import java.util.ArrayList;
@ -49,26 +48,35 @@ public final class ImageSelectActivity extends AppActivity
BaseAdapter.OnItemLongClickListener,
BaseAdapter.OnChildClickListener {
private static final String INTENT_KEY_IN_MAX_SELECT = "maxSelect";
private static final String INTENT_KEY_OUT_IMAGE_LIST = "imageList";
public static void start(BaseActivity activity, OnPhotoSelectListener listener) {
start(activity, 1, listener);
}
@DebugLog
@Permissions({Permission.MANAGE_EXTERNAL_STORAGE})
@Log
@Permissions({Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE})
public static void start(BaseActivity activity, int maxSelect, OnPhotoSelectListener listener) {
if (maxSelect < 1) {
// 最少要选择一个图片
throw new IllegalArgumentException("are you ok?");
}
Intent intent = new Intent(activity, ImageSelectActivity.class);
intent.putExtra(IntentKey.AMOUNT, maxSelect);
intent.putExtra(INTENT_KEY_IN_MAX_SELECT, maxSelect);
activity.startActivityForResult(intent, (resultCode, data) -> {
if (listener == null || data == null) {
if (listener == null) {
return;
}
ArrayList<String> list = data.getStringArrayListExtra(IntentKey.IMAGE);
if (data == null) {
listener.onCancel();
return;
}
ArrayList<String> list = data.getStringArrayListExtra(INTENT_KEY_OUT_IMAGE_LIST);
if (list == null || list.isEmpty()) {
listener.onCancel();
return;
@ -91,7 +99,7 @@ public final class ImageSelectActivity extends AppActivity
private StatusLayout mStatusLayout;
private RecyclerView mRecyclerView;
private FloatingActionButton mFloatingView;
private FloatActionButton mFloatingView;
private ImageSelectAdapter mAdapter;
@ -128,13 +136,29 @@ public final class ImageSelectActivity extends AppActivity
// 禁用动画效果
mRecyclerView.setItemAnimator(null);
// 添加分割线
mRecyclerView.addItemDecoration(new GridSpaceDecoration((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics())));
mRecyclerView.addItemDecoration(new GridSpaceDecoration((int) getResources().getDimension(R.dimen.dp_3)));
// 设置滚动监听
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:
mFloatingView.hide();
break;
case RecyclerView.SCROLL_STATE_IDLE:
mFloatingView.show();
break;
default:
break;
}
}
});
}
@Override
protected void initData() {
// 获取最大的选择数
mMaxSelect = getInt(IntentKey.AMOUNT, mMaxSelect);
mMaxSelect = getInt(INTENT_KEY_IN_MAX_SELECT, mMaxSelect);
// 显示加载进度条
showLoading();
@ -160,10 +184,11 @@ public final class ImageSelectActivity extends AppActivity
Set<String> keys = mAllAlbum.keySet();
for (String key : keys) {
List<String> list = mAllAlbum.get(key);
if (list != null && !list.isEmpty()) {
count += list.size();
data.add(new AlbumDialog.AlbumInfo(list.get(0), key, String.format(getString(R.string.image_select_total), list.size()), mAdapter.getData() == list));
if (list == null || list.isEmpty()) {
continue;
}
count += list.size();
data.add(new AlbumDialog.AlbumInfo(list.get(0), key, String.format(getString(R.string.image_select_total), list.size()), mAdapter.getData() == list));
}
data.add(0, new AlbumDialog.AlbumInfo(mAllImage.get(0), getString(R.string.image_select_all), String.format(getString(R.string.image_select_total), count), mAdapter.getData() == mAllImage));
@ -180,7 +205,7 @@ public final class ImageSelectActivity extends AppActivity
mAdapter.setData(mAllAlbum.get(bean.getName()));
}
// 执行列表动画
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.from_right_layout));
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.layout_from_right));
mRecyclerView.scheduleLayoutAnimation();
});
}
@ -204,18 +229,20 @@ public final class ImageSelectActivity extends AppActivity
mAllImage.remove(path);
File parentFile = file.getParentFile();
if (parentFile != null) {
List<String> data = mAllAlbum.get(parentFile.getName());
if (data != null) {
data.remove(path);
}
mAdapter.notifyDataSetChanged();
if (parentFile == null) {
continue;
}
if (mSelectImage.isEmpty()) {
mFloatingView.setImageResource(R.drawable.camera_ic);
} else {
mFloatingView.setImageResource(R.drawable.succeed_ic);
}
List<String> data = mAllAlbum.get(parentFile.getName());
if (data != null) {
data.remove(path);
}
mAdapter.notifyDataSetChanged();
if (mSelectImage.isEmpty()) {
mFloatingView.setImageResource(R.drawable.camera_ic);
} else {
mFloatingView.setImageResource(R.drawable.succeed_ic);
}
}
}
@ -226,24 +253,31 @@ public final class ImageSelectActivity extends AppActivity
if (view.getId() == R.id.fab_image_select_floating) {
if (mSelectImage.isEmpty()) {
// 点击拍照
CameraActivity.start(this, file -> {
CameraActivity.start(this, new CameraActivity.OnCameraListener() {
@Override
public void onSelected(File file) {
// 当前选中图片的数量必须小于最大选中数
if (mSelectImage.size() < mMaxSelect) {
mSelectImage.add(file.getPath());
}
// 当前选中图片的数量必须小于最大选中数
if (mSelectImage.size() < mMaxSelect) {
mSelectImage.add(file.getPath());
// 这里需要延迟刷新否则可能会找不到拍照的图片
postDelayed(() -> {
// 重新加载图片列表
ThreadPoolManager.getInstance().execute(ImageSelectActivity.this);
}, 1000);
}
// 这里需要延迟刷新否则可能会找不到拍照的图片
postDelayed(() -> {
// 重新加载图片列表
ThreadPoolManager.getInstance().execute(ImageSelectActivity.this);
}, 1000);
@Override
public void onError(String details) {
toast(details);
}
});
return;
}
// 完成选择
setResult(RESULT_OK, new Intent().putStringArrayListExtra(IntentKey.IMAGE, mSelectImage));
setResult(RESULT_OK, new Intent().putStringArrayListExtra(INTENT_KEY_OUT_IMAGE_LIST, mSelectImage));
finish();
}
}
@ -296,11 +330,7 @@ public final class ImageSelectActivity extends AppActivity
mSelectImage.remove(path);
if (mSelectImage.isEmpty()) {
mFloatingView.hide();
postDelayed(() -> {
mFloatingView.setImageResource(R.drawable.camera_ic);
mFloatingView.show();
}, 200);
mFloatingView.setImageResource(R.drawable.camera_ic);
}
mAdapter.notifyItemChanged(position);
@ -323,11 +353,7 @@ public final class ImageSelectActivity extends AppActivity
mSelectImage.add(path);
if (mSelectImage.size() == 1) {
mFloatingView.hide();
postDelayed(() -> {
mFloatingView.setImageResource(R.drawable.succeed_ic);
mFloatingView.show();
}, 200);
mFloatingView.setImageResource(R.drawable.succeed_ic);
}
} else {
toast(String.format(getString(R.string.image_select_max_hint), mMaxSelect));
@ -352,7 +378,7 @@ public final class ImageSelectActivity extends AppActivity
MediaStore.MediaColumns.HEIGHT, MediaStore.MediaColumns.SIZE};
Cursor cursor = null;
if (XXPermissions.isGrantedPermission(this, Permission.MANAGE_EXTERNAL_STORAGE)) {
if (XXPermissions.isGranted(this, Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE)) {
cursor = contentResolver.query(contentUri, projections, selection, new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)}, sortOrder);
}
if (cursor != null && cursor.moveToFirst()) {
@ -380,18 +406,20 @@ public final class ImageSelectActivity extends AppActivity
}
File parentFile = file.getParentFile();
if (parentFile != null) {
// 获取目录名作为专辑名称
String albumName = parentFile.getName();
List<String> data = mAllAlbum.get(albumName);
if (data == null) {
data = new ArrayList<>();
mAllAlbum.put(albumName, data);
}
data.add(path);
mAllImage.add(path);
if (parentFile == null) {
continue;
}
// 获取目录名作为专辑名称
String albumName = parentFile.getName();
List<String> data = mAllAlbum.get(albumName);
if (data == null) {
data = new ArrayList<>();
mAllAlbum.put(albumName, data);
}
data.add(path);
mAllImage.add(path);
} while (cursor.moveToNext());
cursor.close();
@ -410,7 +438,7 @@ public final class ImageSelectActivity extends AppActivity
}
// 执行列表动画
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.fall_down_layout));
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.layout_fall_down));
mRecyclerView.scheduleLayoutAnimation();
if (mAllImage.isEmpty()) {

View File

@ -15,20 +15,20 @@ import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.demo.R;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.LoginApi;
import com.hjq.demo.http.glide.GlideApp;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.LoginApi;
import com.hjq.demo.http.response.LoginBean;
import com.hjq.demo.manager.InputTextManager;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.other.KeyboardWatcher;
import com.hjq.demo.ui.fragment.MeFragment;
import com.hjq.demo.ui.fragment.MineFragment;
import com.hjq.demo.wxapi.WXEntryActivity;
import com.hjq.http.EasyConfig;
import com.hjq.http.EasyHttp;
@ -51,11 +51,14 @@ public final class LoginActivity extends AppActivity
KeyboardWatcher.SoftKeyboardStateListener,
TextView.OnEditorActionListener {
@DebugLog
private static final String INTENT_KEY_IN_PHONE = "phone";
private static final String INTENT_KEY_IN_PASSWORD = "password";
@Log
public static void start(Context context, String phone, String password) {
Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(IntentKey.PHONE, phone);
intent.putExtra(IntentKey.PASSWORD, password);
intent.putExtra(INTENT_KEY_IN_PHONE, phone);
intent.putExtra(INTENT_KEY_IN_PASSWORD, password);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@ -131,14 +134,15 @@ public final class LoginActivity extends AppActivity
}
// 自动填充手机号和密码
mPhoneView.setText(getString(IntentKey.PHONE));
mPasswordView.setText(getString(IntentKey.PASSWORD));
mPhoneView.setText(getString(INTENT_KEY_IN_PHONE));
mPasswordView.setText(getString(INTENT_KEY_IN_PASSWORD));
}
@Override
public void onRightClick(View view) {
// 跳转到注册界面
RegisterActivity.start(this, mPhoneView.getText().toString(), mPasswordView.getText().toString(), (phone, password) -> {
RegisterActivity.start(this, mPhoneView.getText().toString(),
mPasswordView.getText().toString(), (phone, password) -> {
// 如果已经注册成功就执行登录操作
mPhoneView.setText(phone);
mPasswordView.setText(password);
@ -172,7 +176,7 @@ public final class LoginActivity extends AppActivity
postDelayed(() -> {
mCommitView.showSucceed();
postDelayed(() -> {
HomeActivity.start(getContext(), MeFragment.class);
HomeActivity.start(getContext(), MineFragment.class);
finish();
}, 1000);
}, 2000);
@ -183,7 +187,7 @@ public final class LoginActivity extends AppActivity
.api(new LoginApi()
.setPhone(mPhoneView.getText().toString())
.setPassword(mPasswordView.getText().toString()))
.request(new HttpCallback<HttpData<LoginBean>>(this) {
.request(new HttpCallback<HttpData<LoginApi.Bean>>(this) {
@Override
public void onStart(Call call) {
@ -194,7 +198,7 @@ public final class LoginActivity extends AppActivity
public void onEnd(Call call) {}
@Override
public void onSucceed(HttpData<LoginBean> data) {
public void onSucceed(HttpData<LoginApi.Bean> data) {
// 更新 Token
EasyConfig.getInstance()
.addParam("token", data.getData().getToken());
@ -202,7 +206,7 @@ public final class LoginActivity extends AppActivity
mCommitView.showSucceed();
postDelayed(() -> {
// 跳转到首页
HomeActivity.start(getContext(), MeFragment.class);
HomeActivity.start(getContext(), MineFragment.class);
finish();
}, 1000);
}, 1000);
@ -220,7 +224,7 @@ public final class LoginActivity extends AppActivity
}
if (view == mQQView || view == mWeChatView) {
toast("记得改好第三方 AppID 和 AppKey,否则会调不起来哦");
toast("记得改好第三方 AppID 和 Secret,否则会调不起来哦");
Platform platform;
if (view == mQQView) {
platform = Platform.QQ;
@ -237,7 +241,7 @@ public final class LoginActivity extends AppActivity
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 友盟登录回调
// 友盟回调
UmengClient.onActivityResult(this, requestCode, resultCode, data);
}
@ -273,9 +277,10 @@ public final class LoginActivity extends AppActivity
.circleCrop()
.into(mLogoView);
toast("昵称:" + data.getName() + "\n" + "性别:" + data.getSex());
toast("id" + data.getId());
toast("token" + data.getToken());
toast("昵称:" + data.getName() + "\n" +
"性别:" + data.getSex() + "\n" +
"id" + data.getId() + "\n" +
"token" + data.getToken());
}
/**
@ -289,16 +294,6 @@ public final class LoginActivity extends AppActivity
toast("第三方登录出错:" + t.getMessage());
}
/**
* 授权取消的回调
*
* @param platform 平台名称
*/
@Override
public void onCancel(Platform platform) {
toast("取消第三方登录");
}
/**
* {@link KeyboardWatcher.SoftKeyboardStateListener}
*/
@ -359,4 +354,12 @@ public final class LoginActivity extends AppActivity
}
return false;
}
@NonNull
@Override
protected ImmersionBar createStatusBarConfig() {
return super.createStatusBarConfig()
// 指定导航栏背景颜色
.navigationBarColor(R.color.white);
}
}

View File

@ -11,9 +11,9 @@ import android.widget.TextView;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.GetCodeApi;
import com.hjq.demo.http.api.VerifyCodeApi;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.GetCodeApi;
import com.hjq.demo.http.request.VerifyCodeApi;
import com.hjq.demo.manager.InputTextManager;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.HttpCallback;

View File

@ -12,14 +12,13 @@ import android.widget.EditText;
import android.widget.TextView;
import com.hjq.demo.R;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.PasswordApi;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.PasswordApi;
import com.hjq.demo.manager.InputTextManager;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.dialog.HintDialog;
import com.hjq.demo.ui.dialog.TipsDialog;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.HttpCallback;
@ -32,11 +31,14 @@ import com.hjq.http.listener.HttpCallback;
public final class PasswordResetActivity extends AppActivity
implements TextView.OnEditorActionListener {
@DebugLog
private static final String INTENT_KEY_IN_PHONE = "phone";
private static final String INTENT_KEY_IN_CODE = "code";
@Log
public static void start(Context context, String phone, String code) {
Intent intent = new Intent(context, PasswordResetActivity.class);
intent.putExtra(IntentKey.PHONE, phone);
intent.putExtra(IntentKey.CODE, code);
intent.putExtra(INTENT_KEY_IN_PHONE, phone);
intent.putExtra(INTENT_KEY_IN_CODE, code);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@ -76,8 +78,8 @@ public final class PasswordResetActivity extends AppActivity
@Override
protected void initData() {
mPhoneNumber = getString(IntentKey.PHONE);
mVerifyCode = getString(IntentKey.CODE);
mPhoneNumber = getString(INTENT_KEY_IN_PHONE);
mVerifyCode = getString(INTENT_KEY_IN_CODE);
}
@SingleClick
@ -96,8 +98,8 @@ public final class PasswordResetActivity extends AppActivity
hideKeyboard(getCurrentFocus());
if (true) {
new HintDialog.Builder(this)
.setIcon(HintDialog.ICON_FINISH)
new TipsDialog.Builder(this)
.setIcon(TipsDialog.ICON_FINISH)
.setMessage(R.string.password_reset_success)
.setDuration(2000)
.addOnDismissListener(dialog -> finish())
@ -115,8 +117,8 @@ public final class PasswordResetActivity extends AppActivity
@Override
public void onSucceed(HttpData<Void> data) {
new HintDialog.Builder(getActivity())
.setIcon(HintDialog.ICON_FINISH)
new TipsDialog.Builder(getActivity())
.setIcon(TipsDialog.ICON_FINISH)
.setMessage(R.string.password_reset_success)
.setDuration(2000)
.addOnDismissListener(dialog -> finish())

View File

@ -1,37 +1,30 @@
package com.hjq.demo.ui.activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.core.content.FileProvider;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.UpdateImageApi;
import com.hjq.demo.http.glide.GlideApp;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.UpdateImageApi;
import com.hjq.demo.other.AppConfig;
import com.hjq.demo.ui.dialog.AddressDialog;
import com.hjq.demo.ui.dialog.InputDialog;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.HttpCallback;
import com.hjq.http.model.FileContentResolver;
import com.hjq.widget.layout.SettingBar;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.net.URI;
import java.net.URISyntaxException;
/**
* author : Android 轮子哥
@ -55,7 +48,7 @@ public final class PersonalDataActivity extends AppActivity {
private String mArea = "天河区";
/** 头像地址 */
private String mAvatarUrl;
private Uri mAvatarUrl;
@Override
protected int getLayoutId() {
@ -94,12 +87,12 @@ public final class PersonalDataActivity extends AppActivity {
if (view == mAvatarLayout) {
ImageSelectActivity.start(this, data -> {
// 裁剪头像
cropImage(new File(data.get(0)));
cropImageFile(new File(data.get(0)));
});
} else if (view == mAvatarView) {
if (!TextUtils.isEmpty(mAvatarUrl)) {
if (mAvatarUrl != null) {
// 查看头像
ImagePreviewActivity.start(getActivity(), mAvatarUrl);
ImagePreviewActivity.start(getActivity(), mAvatarUrl.toString());
} else {
// 选择头像
onClick(mAvatarLayout);
@ -146,74 +139,44 @@ public final class PersonalDataActivity extends AppActivity {
/**
* 裁剪图片
*/
private void cropImage(File sourceFile) {
Intent intent = new Intent("com.android.camera.action.CROP");
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(getContext(), AppConfig.getPackageName() + ".provider", sourceFile);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} else {
uri = Uri.fromFile(sourceFile);
}
private void cropImageFile(File sourceFile) {
ImageCropActivity.start(this, sourceFile, 1, 1, new ImageCropActivity.OnCropListener() {
String regex = "^(.+)(\\..+)$";
String fileName = sourceFile.getName().replaceFirst(regex, "$1_crop_" + new SimpleDateFormat("HHmmss", Locale.getDefault()).format(new Date())+ "$2");
File outputFile = new File(sourceFile.getParent(), fileName);
if (outputFile.exists()) {
outputFile.delete();
}
intent.setDataAndType(uri, "image/*");
// 是否进行裁剪
intent.putExtra("crop", String.valueOf(true));
// 宽高裁剪比例
if (Build.MANUFACTURER.toUpperCase().contains("HUAWEI")) {
// 华为手机特殊处理否则不会显示正方形裁剪区域而是显示圆形裁剪区域
// https://blog.csdn.net/wapchief/article/details/80669647
intent.putExtra("aspectX", 9998);
intent.putExtra("aspectY", 9999);
} else {
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
}
// 是否裁剪成圆形在华为手机上没有任何效果
// intent.putExtra("circleCrop", false);
// 宽高裁剪大小
// intent.putExtra("outputX", 200);
// intent.putExtra("outputY", 200);
// 是否保持比例不变
intent.putExtra("scale", true);
// 裁剪区域小于输出大小时是否放大图像
intent.putExtra("scaleUpIfNeeded", true);
// 是否将数据以 Bitmap 的形式保存
intent.putExtra("return-data", false);
// 设置裁剪后保存的文件路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(outputFile));
// 设置裁剪后保存的文件格式
intent.putExtra("outputFormat", getImageFormat(sourceFile).toString());
// 判断手机是否有裁剪功能
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, (resultCode, data) -> {
if (resultCode == RESULT_OK) {
updateImage(outputFile, true);
@Override
public void onSucceed(Uri fileUri, String fileName) {
File outputFile;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
outputFile = new FileContentResolver(getActivity(), fileUri, fileName);
} else {
try {
outputFile = new File(new URI(fileUri.toString()));
} catch (URISyntaxException e) {
e.printStackTrace();
outputFile = new File(fileUri.toString());
}
}
});
return;
}
updateCropImage(outputFile, true);
}
// 没有的话就不裁剪直接上传原图片
// 但是这种情况极其少见可以忽略不计
updateImage(sourceFile, false);
@Override
public void onError(String details) {
// 没有的话就不裁剪直接上传原图片
// 但是这种情况极其少见可以忽略不计
updateCropImage(sourceFile, false);
}
});
}
/**
* 上传图片
* 上传裁剪后的图片
*/
private void updateImage(File file, boolean deleteFile) {
private void updateCropImage(File file, boolean deleteFile) {
if (true) {
mAvatarUrl = file.getPath();
if (file instanceof FileContentResolver) {
mAvatarUrl = ((FileContentResolver) file).getContentUri();
} else {
mAvatarUrl = Uri.fromFile(file);
}
GlideApp.with(getActivity())
.load(mAvatarUrl)
.transform(new MultiTransformation<>(new CenterCrop(), new CircleCrop()))
@ -224,11 +187,11 @@ public final class PersonalDataActivity extends AppActivity {
EasyHttp.post(this)
.api(new UpdateImageApi()
.setImage(file))
.request(new HttpCallback<HttpData<String>>(PersonalDataActivity.this) {
.request(new HttpCallback<HttpData<String>>(this) {
@Override
public void onSucceed(HttpData<String> data) {
mAvatarUrl = data.getData();
mAvatarUrl = Uri.parse(data.getData());
GlideApp.with(getActivity())
.load(mAvatarUrl)
.transform(new MultiTransformation<>(new CenterCrop(), new CircleCrop()))
@ -239,17 +202,4 @@ public final class PersonalDataActivity extends AppActivity {
}
});
}
/**
* 获取图片文件的格式
*/
private Bitmap.CompressFormat getImageFormat(File file) {
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".png")) {
return Bitmap.CompressFormat.PNG;
} else if (fileName.endsWith(".webp")) {
return Bitmap.CompressFormat.WEBP;
}
return Bitmap.CompressFormat.JPEG;
}
}

View File

@ -12,15 +12,14 @@ import android.widget.EditText;
import android.widget.TextView;
import com.hjq.demo.R;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.GetCodeApi;
import com.hjq.demo.http.api.PhoneApi;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.GetCodeApi;
import com.hjq.demo.http.request.PhoneApi;
import com.hjq.demo.manager.InputTextManager;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.dialog.HintDialog;
import com.hjq.demo.ui.dialog.TipsDialog;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.HttpCallback;
import com.hjq.toast.ToastUtils;
@ -35,10 +34,12 @@ import com.hjq.widget.view.CountdownView;
public final class PhoneResetActivity extends AppActivity
implements TextView.OnEditorActionListener {
@DebugLog
private static final String INTENT_KEY_IN_CODE = "code";
@Log
public static void start(Context context, String code) {
Intent intent = new Intent(context, PhoneResetActivity.class);
intent.putExtra(IntentKey.CODE, code);
intent.putExtra(INTENT_KEY_IN_CODE, code);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
@ -78,7 +79,7 @@ public final class PhoneResetActivity extends AppActivity
@Override
protected void initData() {
mVerifyCode = getString(IntentKey.CODE);
mVerifyCode = getString(INTENT_KEY_IN_CODE);
}
@SingleClick
@ -127,8 +128,8 @@ public final class PhoneResetActivity extends AppActivity
hideKeyboard(getCurrentFocus());
if (true) {
new HintDialog.Builder(this)
.setIcon(HintDialog.ICON_FINISH)
new TipsDialog.Builder(this)
.setIcon(TipsDialog.ICON_FINISH)
.setMessage(R.string.phone_reset_commit_succeed)
.setDuration(2000)
.addOnDismissListener(dialog -> finish())
@ -146,8 +147,8 @@ public final class PhoneResetActivity extends AppActivity
@Override
public void onSucceed(HttpData<Void> data) {
new HintDialog.Builder(getActivity())
.setIcon(HintDialog.ICON_FINISH)
new TipsDialog.Builder(getActivity())
.setIcon(TipsDialog.ICON_FINISH)
.setMessage(R.string.phone_reset_commit_succeed)
.setDuration(2000)
.addOnDismissListener(dialog -> finish())

View File

@ -13,15 +13,13 @@ import androidx.annotation.NonNull;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.base.BaseActivity;
import com.hjq.demo.R;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.GetCodeApi;
import com.hjq.demo.http.api.RegisterApi;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.GetCodeApi;
import com.hjq.demo.http.request.RegisterApi;
import com.hjq.demo.http.response.RegisterBean;
import com.hjq.demo.manager.InputTextManager;
import com.hjq.demo.other.IntentKey;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.HttpCallback;
import com.hjq.widget.view.CountdownView;
@ -38,11 +36,14 @@ import okhttp3.Call;
public final class RegisterActivity extends AppActivity
implements TextView.OnEditorActionListener {
@DebugLog
private static final String INTENT_KEY_PHONE = "phone";
private static final String INTENT_KEY_PASSWORD = "password";
@Log
public static void start(BaseActivity activity, String phone, String password, OnRegisterListener listener) {
Intent intent = new Intent(activity, RegisterActivity.class);
intent.putExtra(IntentKey.PHONE, phone);
intent.putExtra(IntentKey.PASSWORD, password);
intent.putExtra(INTENT_KEY_PHONE, phone);
intent.putExtra(INTENT_KEY_PASSWORD, password);
activity.startActivityForResult(intent, (resultCode, data) -> {
if (listener == null || data == null) {
@ -50,7 +51,7 @@ public final class RegisterActivity extends AppActivity
}
if (resultCode == RESULT_OK) {
listener.onSucceed(data.getStringExtra(IntentKey.PHONE), data.getStringExtra(IntentKey.PASSWORD));
listener.onSucceed(data.getStringExtra(INTENT_KEY_PHONE), data.getStringExtra(INTENT_KEY_PASSWORD));
} else {
listener.onCancel();
}
@ -100,9 +101,9 @@ public final class RegisterActivity extends AppActivity
@Override
protected void initData() {
// 自动填充手机号和密码
mPhoneView.setText(getString(IntentKey.PHONE));
mFirstPassword.setText(getString(IntentKey.PASSWORD));
mSecondPassword.setText(getString(IntentKey.PASSWORD));
mPhoneView.setText(getString(INTENT_KEY_PHONE));
mFirstPassword.setText(getString(INTENT_KEY_PASSWORD));
mSecondPassword.setText(getString(INTENT_KEY_PASSWORD));
}
@SingleClick
@ -171,8 +172,8 @@ public final class RegisterActivity extends AppActivity
mCommitView.showSucceed();
postDelayed(() -> {
setResult(RESULT_OK, new Intent()
.putExtra(IntentKey.PHONE, mPhoneView.getText().toString())
.putExtra(IntentKey.PASSWORD, mFirstPassword.getText().toString()));
.putExtra(INTENT_KEY_PHONE, mPhoneView.getText().toString())
.putExtra(INTENT_KEY_PASSWORD, mFirstPassword.getText().toString()));
finish();
}, 1000);
}, 2000);
@ -185,7 +186,7 @@ public final class RegisterActivity extends AppActivity
.setPhone(mPhoneView.getText().toString())
.setCode(mCodeView.getText().toString())
.setPassword(mFirstPassword.getText().toString()))
.request(new HttpCallback<HttpData<RegisterBean>>(this) {
.request(new HttpCallback<HttpData<RegisterApi.Bean>>(this) {
@Override
public void onStart(Call call) {
@ -196,13 +197,13 @@ public final class RegisterActivity extends AppActivity
public void onEnd(Call call) {}
@Override
public void onSucceed(HttpData<RegisterBean> data) {
public void onSucceed(HttpData<RegisterApi.Bean> data) {
postDelayed(() -> {
mCommitView.showSucceed();
postDelayed(() -> {
setResult(RESULT_OK, new Intent()
.putExtra(IntentKey.PHONE, mPhoneView.getText().toString())
.putExtra(IntentKey.PASSWORD, mFirstPassword.getText().toString()));
.putExtra(INTENT_KEY_PHONE, mPhoneView.getText().toString())
.putExtra(INTENT_KEY_PASSWORD, mFirstPassword.getText().toString()));
finish();
}, 1000);
}, 1000);
@ -223,6 +224,8 @@ public final class RegisterActivity extends AppActivity
@Override
protected ImmersionBar createStatusBarConfig() {
return super.createStatusBarConfig()
// 指定导航栏背景颜色
.navigationBarColor(R.color.white)
// 不要把整个布局顶上去
.keyboardEnable(true);
}

View File

@ -29,9 +29,7 @@ public final class RestartActivity extends AppActivity {
}
@Override
protected void initView() {
}
protected void initView() {}
@Override
protected void initData() {

View File

@ -7,12 +7,12 @@ import com.hjq.base.BaseDialog;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.LogoutApi;
import com.hjq.demo.http.glide.GlideApp;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.LogoutApi;
import com.hjq.demo.manager.ActivityManager;
import com.hjq.demo.manager.ThreadPoolManager;
import com.hjq.demo.manager.CacheDataManager;
import com.hjq.demo.manager.ThreadPoolManager;
import com.hjq.demo.other.AppConfig;
import com.hjq.demo.ui.dialog.MenuDialog;
import com.hjq.demo.ui.dialog.SafeDialog;
@ -116,7 +116,7 @@ public final class SettingActivity extends AppActivity
} else if (viewId == R.id.sb_setting_agreement) {
BrowserActivity.start(this, "https://github.com/getActivity/AndroidProject");
BrowserActivity.start(this, "https://github.com/getActivity/Donate");
} else if (viewId == R.id.sb_setting_about) {

View File

@ -12,9 +12,8 @@ import com.gyf.immersionbar.BarHide;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.demo.R;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.http.api.UserInfoApi;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.UserInfoApi;
import com.hjq.demo.http.response.UserInfoBean;
import com.hjq.demo.other.AppConfig;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.HttpCallback;
@ -67,10 +66,10 @@ public final class SplashActivity extends AppActivity {
// 刷新用户信息
EasyHttp.post(this)
.api(new UserInfoApi())
.request(new HttpCallback<HttpData<UserInfoBean>>(this) {
.request(new HttpCallback<HttpData<UserInfoApi.Bean>>(this) {
@Override
public void onSucceed(HttpData<UserInfoBean> data) {
public void onSucceed(HttpData<UserInfoApi.Bean> data) {
}
});

View File

@ -41,7 +41,7 @@ public final class StatusActivity extends AppActivity
postDelayed(this::showComplete, 2500);
break;
case 1:
showError(v -> {
showError(listener -> {
showLoading();
postDelayed(this::showEmpty, 2500);
});

View File

@ -3,6 +3,8 @@ package com.hjq.demo.ui.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@ -12,7 +14,6 @@ import com.gyf.immersionbar.BarHide;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.demo.R;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.widget.PlayerView;
import java.io.File;
@ -23,8 +24,10 @@ import java.io.File;
* time : 2020/02/16
* desc : 视频播放界面
*/
public final class VideoPlayActivity extends AppActivity
implements PlayerView.onPlayListener {
public class VideoPlayActivity extends AppActivity
implements PlayerView.OnPlayListener {
public static final String INTENT_KEY_PARAMETERS = "parameters";
private PlayerView mPlayerView;
private VideoPlayActivity.Builder mBuilder;
@ -43,20 +46,22 @@ public final class VideoPlayActivity extends AppActivity
@Override
protected void initData() {
mBuilder = getParcelable(IntentKey.VIDEO);
mBuilder = getParcelable(INTENT_KEY_PARAMETERS);
if (mBuilder == null) {
throw new IllegalArgumentException("are you ok?");
}
mPlayerView.setVideoTitle(mBuilder.getVideoTitle());
mPlayerView.setVideoSource(mBuilder.getVideoSource());
mPlayerView.setGestureEnabled(mBuilder.isGestureEnabled());
if (mBuilder.isAutoPlay()) {
mPlayerView.start();
}
}
/**
* {@link PlayerView.onPlayListener}
* {@link PlayerView.OnPlayListener}
*/
@Override
public void onClickBack(PlayerView view) {
@ -71,12 +76,21 @@ public final class VideoPlayActivity extends AppActivity
}
}
@Override
public void onPlayProgress(PlayerView view) {
// 记录播放进度
mBuilder.setPlayProgress(view.getProgress());
}
@Override
public void onPlayEnd(PlayerView view) {
if (mBuilder.isLoopPlay()) {
mPlayerView.setProgress(0);
mPlayerView.start();
} else if (mBuilder.isAutoOver()) {
return;
}
if (mBuilder.isAutoOver()) {
finish();
}
}
@ -89,119 +103,161 @@ public final class VideoPlayActivity extends AppActivity
.hideBar(BarHide.FLAG_HIDE_BAR);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
// 保存播放进度
outState.putParcelable(INTENT_KEY_PARAMETERS, mBuilder);
}
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 读取播放进度
mBuilder = savedInstanceState.getParcelable(INTENT_KEY_PARAMETERS);
}
/** 竖屏播放 */
public static final class Portrait extends VideoPlayActivity {}
/** 横屏播放 */
public static final class Landscape extends VideoPlayActivity {}
/**
* 播放参数构建
*/
public static final class Builder implements Parcelable {
/** 视频源 */
private String mVideoSource;
private String videoSource;
/** 视频标题 */
private String mVideoTitle;
private String videoTitle;
/** 播放进度 */
private int mPlayProgress;
private int playProgress;
/** 手势开关 */
private boolean mGestureEnabled = true;
private boolean gestureEnabled = true;
/** 循环播放 */
private boolean mLoopPlay = false;
private boolean loopPlay = false;
/** 自动播放 */
private boolean mAutoPlay = true;
private boolean autoPlay = true;
/** 播放完关闭 */
private boolean mAutoOver = true;
private boolean autoOver = true;
/** 播放方向 */
private int activityOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
public Builder() {}
protected Builder(Parcel in) {
mVideoSource = in.readString();
mVideoTitle = in.readString();
mPlayProgress = in.readInt();
mGestureEnabled = in.readByte() != 0;
mLoopPlay = in.readByte() != 0;
mAutoPlay = in.readByte() != 0;
mAutoOver = in.readByte() != 0;
videoSource = in.readString();
videoTitle = in.readString();
activityOrientation = in.readInt();
playProgress = in.readInt();
gestureEnabled = in.readByte() != 0;
loopPlay = in.readByte() != 0;
autoPlay = in.readByte() != 0;
autoOver = in.readByte() != 0;
}
public Builder setVideoSource(File file) {
mVideoSource = file.getPath();
if (mVideoTitle == null) {
mVideoTitle = file.getName();
videoSource = file.getPath();
if (videoTitle == null) {
videoTitle = file.getName();
}
return this;
}
public Builder setVideoSource(String url) {
mVideoSource = url;
videoSource = url;
return this;
}
private String getVideoSource() {
return mVideoSource;
return videoSource;
}
public Builder setVideoTitle(String title) {
mVideoTitle = title;
videoTitle = title;
return this;
}
private String getVideoTitle() {
return mVideoTitle;
return videoTitle;
}
public Builder setPlayProgress(int progress) {
mPlayProgress = progress;
playProgress = progress;
return this;
}
private int getPlayProgress() {
return mPlayProgress;
return playProgress;
}
public Builder setGestureEnabled(boolean enabled) {
mGestureEnabled = enabled;
gestureEnabled = enabled;
return this;
}
private boolean isGestureEnabled() {
return mGestureEnabled;
return gestureEnabled;
}
public Builder setLoopPlay(boolean enabled) {
mLoopPlay = enabled;
loopPlay = enabled;
return this;
}
private boolean isLoopPlay() {
return mLoopPlay;
return loopPlay;
}
public Builder setAutoPlay(boolean enabled) {
mAutoPlay = enabled;
autoPlay = enabled;
return this;
}
public boolean isAutoPlay() {
return mAutoPlay;
return autoPlay;
}
public Builder setAutoOver(boolean enabled) {
mAutoOver = enabled;
autoOver = enabled;
return this;
}
private boolean isAutoOver() {
return mAutoOver;
return autoOver;
}
public Builder setActivityOrientation(int orientation) {
activityOrientation = orientation;
return this;
}
public void start(Context context) {
Intent intent = new Intent(context, VideoPlayActivity.class);
intent.putExtra(IntentKey.VIDEO, this);
Intent intent = new Intent();
switch (activityOrientation) {
case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
intent.setClass(context, VideoPlayActivity.Landscape.class);
break;
case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
intent.setClass(context, VideoPlayActivity.Portrait.class);
break;
default:
intent.setClass(context, VideoPlayActivity.class);
break;
}
intent.putExtra(INTENT_KEY_PARAMETERS, this);
if (!(context instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
}
@Override
public int describeContents() {
return 0;
@ -209,13 +265,14 @@ public final class VideoPlayActivity extends AppActivity
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mVideoSource);
dest.writeString(mVideoTitle);
dest.writeInt(mPlayProgress);
dest.writeByte(mGestureEnabled ? (byte) 1 : (byte) 0);
dest.writeByte(mLoopPlay ? (byte) 1 : (byte) 0);
dest.writeByte(mAutoPlay ? (byte) 1 : (byte) 0);
dest.writeByte(mAutoOver ? (byte) 1 : (byte) 0);
dest.writeString(videoSource);
dest.writeString(videoTitle);
dest.writeInt(activityOrientation);
dest.writeInt(playProgress);
dest.writeByte(gestureEnabled ? (byte) 1 : (byte) 0);
dest.writeByte(loopPlay ? (byte) 1 : (byte) 0);
dest.writeByte(autoPlay ? (byte) 1 : (byte) 0);
dest.writeByte(autoOver ? (byte) 1 : (byte) 0);
}
public static final Parcelable.Creator<Builder> CREATOR = new Parcelable.Creator<Builder>() {

View File

@ -2,6 +2,7 @@ package com.hjq.demo.ui.activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
@ -9,7 +10,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.AnimationUtils;
@ -17,23 +17,23 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hjq.base.BaseActivity;
import com.hjq.base.BaseAdapter;
import com.hjq.demo.R;
import com.hjq.demo.action.StatusAction;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.aop.Permissions;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.manager.ThreadPoolManager;
import com.hjq.demo.other.GridSpaceDecoration;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.adapter.VideoSelectAdapter;
import com.hjq.demo.ui.dialog.AlbumDialog;
import com.hjq.demo.widget.StatusLayout;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.hjq.widget.view.FloatActionButton;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.File;
import java.util.ArrayList;
@ -54,26 +54,35 @@ public final class VideoSelectActivity extends AppActivity
BaseAdapter.OnItemLongClickListener,
BaseAdapter.OnChildClickListener {
private static final String INTENT_KEY_IN_MAX_SELECT = "maxSelect";
private static final String INTENT_KEY_OUT_VIDEO_LIST = "videoList";
public static void start(BaseActivity activity, OnVideoSelectListener listener) {
start(activity, 1, listener);
}
@DebugLog
@Permissions({Permission.MANAGE_EXTERNAL_STORAGE})
@Log
@Permissions({Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE})
public static void start(BaseActivity activity, int maxSelect, OnVideoSelectListener listener) {
if (maxSelect < 1) {
// 最少要选择一个视频
throw new IllegalArgumentException("are you ok?");
}
Intent intent = new Intent(activity, VideoSelectActivity.class);
intent.putExtra(IntentKey.AMOUNT, maxSelect);
intent.putExtra(INTENT_KEY_IN_MAX_SELECT, maxSelect);
activity.startActivityForResult(intent, (resultCode, data) -> {
if (listener == null || data == null) {
if (listener == null) {
return;
}
ArrayList<VideoBean> list = data.getParcelableArrayListExtra(IntentKey.VIDEO);
if (data == null) {
listener.onCancel();
return;
}
ArrayList<VideoBean> list = data.getParcelableArrayListExtra(INTENT_KEY_OUT_VIDEO_LIST);
if (list == null || list.isEmpty()) {
listener.onCancel();
return;
@ -96,7 +105,7 @@ public final class VideoSelectActivity extends AppActivity
private StatusLayout mStatusLayout;
private RecyclerView mRecyclerView;
private FloatingActionButton mFloatingView;
private FloatActionButton mFloatingView;
private VideoSelectAdapter mAdapter;
@ -133,19 +142,34 @@ public final class VideoSelectActivity extends AppActivity
// 禁用动画效果
mRecyclerView.setItemAnimator(null);
// 添加分割线
mRecyclerView.addItemDecoration(new GridSpaceDecoration((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics())));
mRecyclerView.addItemDecoration(new GridSpaceDecoration((int) getResources().getDimension(R.dimen.dp_5)));
// 设置滚动监听
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
switch (newState) {
case RecyclerView.SCROLL_STATE_DRAGGING:
mFloatingView.hide();
break;
case RecyclerView.SCROLL_STATE_IDLE:
mFloatingView.show();
break;
default:
break;
}
}
});
}
@SuppressWarnings("all")
@Override
protected void initData() {
// 获取最大的选择数
mMaxSelect = getInt(IntentKey.AMOUNT, mMaxSelect);
mMaxSelect = getInt(INTENT_KEY_IN_MAX_SELECT, mMaxSelect);
// 显示加载进度条
showLoading();
// 加载视频列表
ThreadPoolManager.getInstance().execute(VideoSelectActivity.this);
ThreadPoolManager.getInstance().execute(this);
}
@Override
@ -166,10 +190,11 @@ public final class VideoSelectActivity extends AppActivity
Set<String> keys = mAllAlbum.keySet();
for (String key : keys) {
List<VideoBean> list = mAllAlbum.get(key);
if (list != null && !list.isEmpty()) {
count += list.size();
data.add(new AlbumDialog.AlbumInfo(list.get(0).getVideoPath(), key, String.format(getString(R.string.video_select_total), list.size()), mAdapter.getData() == list));
if (list == null || list.isEmpty()) {
continue;
}
count += list.size();
data.add(new AlbumDialog.AlbumInfo(list.get(0).getVideoPath(), key, String.format(getString(R.string.video_select_total), list.size()), mAdapter.getData() == list));
}
data.add(0, new AlbumDialog.AlbumInfo(mAllVideo.get(0).getVideoPath(), getString(R.string.video_select_all), String.format(getString(R.string.video_select_total), count), mAdapter.getData() == mAllVideo));
@ -186,7 +211,7 @@ public final class VideoSelectActivity extends AppActivity
mAdapter.setData(mAllAlbum.get(bean.getName()));
}
// 执行列表动画
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.from_right_layout));
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.layout_from_right));
mRecyclerView.scheduleLayoutAnimation();
});
}
@ -211,18 +236,20 @@ public final class VideoSelectActivity extends AppActivity
mAllVideo.remove(bean);
File parentFile = file.getParentFile();
if (parentFile != null) {
List<VideoBean> data = mAllAlbum.get(parentFile.getName());
if (data != null) {
data.remove(bean);
}
mAdapter.notifyDataSetChanged();
if (parentFile == null) {
continue;
}
if (mSelectVideo.isEmpty()) {
mFloatingView.setImageResource(R.drawable.videocam_ic);
} else {
mFloatingView.setImageResource(R.drawable.succeed_ic);
}
List<VideoBean> data = mAllAlbum.get(parentFile.getName());
if (data != null) {
data.remove(bean);
}
mAdapter.notifyDataSetChanged();
if (mSelectVideo.isEmpty()) {
mFloatingView.setImageResource(R.drawable.videocam_ic);
} else {
mFloatingView.setImageResource(R.drawable.succeed_ic);
}
}
}
@ -233,24 +260,31 @@ public final class VideoSelectActivity extends AppActivity
if (view.getId() == R.id.fab_video_select_floating) {
if (mSelectVideo.isEmpty()) {
// 点击拍照
CameraActivity.start(this, true, file -> {
CameraActivity.start(this, true, new CameraActivity.OnCameraListener() {
@Override
public void onSelected(File file) {
// 当前选中视频的数量必须小于最大选中数
if (mSelectVideo.size() < mMaxSelect) {
mSelectVideo.add(VideoBean.newInstance(file.getPath()));
}
// 当前选中视频的数量必须小于最大选中数
if (mSelectVideo.size() < mMaxSelect) {
mSelectVideo.add(VideoBean.newInstance(file.getPath()));
// 这里需要延迟刷新否则可能会找不到拍照的视频
postDelayed(() -> {
// 重新加载视频列表
ThreadPoolManager.getInstance().execute(VideoSelectActivity.this);
}, 1000);
}
// 这里需要延迟刷新否则可能会找不到拍照的视频
postDelayed(() -> {
// 重新加载视频列表
ThreadPoolManager.getInstance().execute(VideoSelectActivity.this);
}, 1000);
@Override
public void onError(String details) {
toast(details);
}
});
return;
}
// 完成选择
setResult(RESULT_OK, new Intent().putParcelableArrayListExtra(IntentKey.VIDEO, mSelectVideo));
setResult(RESULT_OK, new Intent().putParcelableArrayListExtra(INTENT_KEY_OUT_VIDEO_LIST, mSelectVideo));
finish();
}
}
@ -263,8 +297,11 @@ public final class VideoSelectActivity extends AppActivity
*/
@Override
public void onItemClick(RecyclerView recyclerView, View itemView, int position) {
VideoBean bean = mAdapter.getItem(position);
new VideoPlayActivity.Builder()
.setVideoSource(new File(mAdapter.getItem(position).getVideoPath()))
.setVideoSource(new File(bean.getVideoPath()))
.setActivityOrientation(bean.getVideoWidth() > bean.getVideoHeight() ?
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
.start(getActivity());
}
@ -305,11 +342,7 @@ public final class VideoSelectActivity extends AppActivity
mSelectVideo.remove(bean);
if (mSelectVideo.isEmpty()) {
mFloatingView.hide();
postDelayed(() -> {
mFloatingView.setImageResource(R.drawable.videocam_ic);
mFloatingView.show();
}, 200);
mFloatingView.setImageResource(R.drawable.videocam_ic);
}
mAdapter.notifyItemChanged(position);
@ -332,11 +365,7 @@ public final class VideoSelectActivity extends AppActivity
mSelectVideo.add(bean);
if (mSelectVideo.size() == 1) {
mFloatingView.hide();
postDelayed(() -> {
mFloatingView.setImageResource(R.drawable.succeed_ic);
mFloatingView.show();
}, 200);
mFloatingView.setImageResource(R.drawable.succeed_ic);
}
} else {
toast(String.format(getString(R.string.video_select_max_hint), mMaxSelect));
@ -361,7 +390,7 @@ public final class VideoSelectActivity extends AppActivity
MediaStore.MediaColumns.HEIGHT, MediaStore.MediaColumns.SIZE, MediaStore.Video.Media.DURATION};
Cursor cursor = null;
if (XXPermissions.isGrantedPermission(this, Permission.MANAGE_EXTERNAL_STORAGE)) {
if (XXPermissions.isGranted(this, Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE)) {
cursor = contentResolver.query(contentUri, projections, selection, new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)}, sortOrder);
}
if (cursor != null && cursor.moveToFirst()) {
@ -370,6 +399,8 @@ public final class VideoSelectActivity extends AppActivity
int mimeTypeIndex = cursor.getColumnIndex(MediaStore.MediaColumns.MIME_TYPE);
int sizeIndex = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE);
int durationIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION);
int widthIndex = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH);
int heightIndex = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT);
do {
long duration = cursor.getLong(durationIndex);
@ -396,20 +427,25 @@ public final class VideoSelectActivity extends AppActivity
}
File parentFile = file.getParentFile();
if (parentFile != null) {
// 获取目录名作为专辑名称
String albumName = parentFile.getName();
List<VideoBean> data = mAllAlbum.get(albumName);
if (data == null) {
data = new ArrayList<>();
mAllAlbum.put(albumName, data);
}
VideoBean bean = new VideoBean(path, duration, size);
data.add(bean);
mAllVideo.add(bean);
if (parentFile == null) {
continue;
}
// 获取目录名作为专辑名称
String albumName = parentFile.getName();
List<VideoBean> data = mAllAlbum.get(albumName);
if (data == null) {
data = new ArrayList<>();
mAllAlbum.put(albumName, data);
}
int width = cursor.getInt(widthIndex);
int height = cursor.getInt(heightIndex);
VideoBean bean = new VideoBean(path, width, height, duration, size);
data.add(bean);
mAllVideo.add(bean);
} while (cursor.moveToNext());
cursor.close();
@ -428,7 +464,7 @@ public final class VideoSelectActivity extends AppActivity
}
// 执行列表动画
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.fall_down_layout));
mRecyclerView.setLayoutAnimation(AnimationUtils.loadLayoutAnimation(getActivity(), R.anim.layout_fall_down));
mRecyclerView.scheduleLayoutAnimation();
if (mAllVideo.isEmpty()) {
@ -450,49 +486,79 @@ public final class VideoSelectActivity extends AppActivity
*/
public static class VideoBean implements Parcelable {
private final String videoPath;
private final long duration;
private final long size;
private final String mVideoPath;
private final int mVideoWidth;
private final int mVideoHeight;
private final long mVideoDuration;
private final long mVideoSize;
public static VideoBean newInstance(String videoPath) {
int duration = 0;
int videoWidth = 0;
int videoHeight = 0;
long videoDuration = 0;
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(videoPath);
String widthMetadata = retriever.extractMetadata
(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
if (widthMetadata != null && !"".equals(widthMetadata)) {
videoWidth = Integer.parseInt(widthMetadata);
}
String heightMetadata = retriever.extractMetadata
(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
if (heightMetadata != null && !"".equals(heightMetadata)) {
videoHeight = Integer.parseInt(heightMetadata);
}
String durationMetadata = retriever.extractMetadata
(MediaMetadataRetriever.METADATA_KEY_DURATION);
if (durationMetadata != null && !"".equals(durationMetadata)) {
videoDuration = Long.parseLong(durationMetadata);
}
} catch (RuntimeException e) {
// 荣耀 LLD AL20 Android 8.0 出现java.lang.IllegalArgumentException
// 荣耀 HLK AL00 Android 10.0 出现java.lang.RuntimeExceptionsetDataSource failed: status = 0x80000000
retriever.setDataSource(videoPath);
duration = Integer.parseInt(retriever.extractMetadata
(MediaMetadataRetriever.METADATA_KEY_DURATION));
} catch (RuntimeException e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
}
long size = new File(videoPath).length();
return new VideoBean(videoPath, duration, size);
long videoSize = new File(videoPath).length();
return new VideoBean(videoPath, videoWidth, videoHeight, videoDuration, videoSize);
}
public VideoBean(String videoPath, long videoDuration, long videoSize) {
this.videoPath = videoPath;
this.duration = videoDuration;
this.size = videoSize;
public VideoBean(String path, int width, int height, long duration, long size) {
mVideoPath = path;
mVideoWidth = width;
mVideoHeight = height;
mVideoDuration = duration;
mVideoSize = size;
}
public int getVideoWidth() {
return mVideoWidth;
}
public int getVideoHeight() {
return mVideoHeight;
}
public String getVideoPath() {
return videoPath;
return mVideoPath;
}
public long getVideoDuration() {
return duration;
return mVideoDuration;
}
public long getVideoSize() {
return size;
return mVideoSize;
}
@Override
public boolean equals(@Nullable Object obj) {
if (obj instanceof VideoBean) {
return videoPath.equals(((VideoBean) obj).videoPath);
return mVideoPath.equals(((VideoBean) obj).mVideoPath);
}
return false;
}
@ -500,7 +566,7 @@ public final class VideoSelectActivity extends AppActivity
@NonNull
@Override
public String toString() {
return videoPath;
return mVideoPath;
}
@Override
@ -510,15 +576,19 @@ public final class VideoSelectActivity extends AppActivity
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.videoPath);
dest.writeLong(this.duration);
dest.writeLong(this.size);
dest.writeString(mVideoPath);
dest.writeInt(mVideoWidth);
dest.writeInt(mVideoHeight);
dest.writeLong(mVideoDuration);
dest.writeLong(mVideoSize);
}
protected VideoBean(Parcel in) {
this.videoPath = in.readString();
this.duration = in.readLong();
this.size = in.readLong();
mVideoPath = in.readString();
mVideoWidth = in.readInt();
mVideoHeight = in.readInt();
mVideoDuration = in.readLong();
mVideoSize = in.readLong();
}
public static final Parcelable.Creator<VideoBean> CREATOR = new Parcelable.Creator<VideoBean>() {

View File

@ -19,6 +19,9 @@ public final class GuideAdapter extends AppAdapter<Integer> {
public GuideAdapter(Context context) {
super(context);
addItem(R.drawable.guide_1_bg);
addItem(R.drawable.guide_2_bg);
addItem(R.drawable.guide_3_bg);
}
@NonNull

View File

@ -0,0 +1,133 @@
package com.hjq.demo.ui.adapter;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.base.BaseAdapter;
import com.hjq.demo.R;
import com.hjq.demo.app.AppAdapter;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2021/02/28
* desc : 导航栏适配器
*/
public final class NavigationAdapter extends AppAdapter<NavigationAdapter.MenuItem>
implements BaseAdapter.OnItemClickListener {
/** 当前选中条目位置 */
private int mSelectedPosition = 0;
/** 导航栏点击监听 */
@Nullable
private OnNavigationListener mListener;
public NavigationAdapter(Context context) {
super(context);
setOnItemClickListener(this);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder();
}
@Override
protected RecyclerView.LayoutManager generateDefaultLayoutManager(Context context) {
return new GridLayoutManager(context, getCount(), RecyclerView.VERTICAL, false);
}
public int getSelectedPosition() {
return mSelectedPosition;
}
public void setSelectedPosition(int position) {
mSelectedPosition = position;
notifyDataSetChanged();
}
/**
* 设置导航栏监听
*/
public void setOnNavigationListener(@Nullable OnNavigationListener listener) {
mListener = listener;
}
/**
* {@link BaseAdapter.OnItemClickListener}
*/
@Override
public void onItemClick(RecyclerView recyclerView, View itemView, int position) {
if (mSelectedPosition == position) {
return;
}
if (mListener == null) {
mSelectedPosition = position;
notifyDataSetChanged();
return;
}
if (mListener.onNavigationItemSelected(position)) {
mSelectedPosition = position;
notifyDataSetChanged();
}
}
private final class ViewHolder extends AppAdapter<?>.ViewHolder {
private final ImageView mIconView;
private final TextView mTitleView;
private ViewHolder() {
super(R.layout.home_navigation_item);
mIconView = findViewById(R.id.iv_home_navigation_icon);
mTitleView = findViewById(R.id.tv_home_navigation_title);
}
@Override
public void onBindView(int position) {
MenuItem item = getItem(position);
mIconView.setImageDrawable(item.getDrawable());
mTitleView.setText(item.getText());
mIconView.setSelected(mSelectedPosition == position);
mTitleView.setSelected(mSelectedPosition == position);
}
}
public static class MenuItem {
private final String mText;
private final Drawable mDrawable;
public MenuItem(String text, Drawable drawable) {
mText = text;
mDrawable = drawable;
}
public String getText() {
return mText;
}
public Drawable getDrawable() {
return mDrawable;
}
}
public interface OnNavigationListener {
boolean onNavigationItemSelected(int position);
}
}

View File

@ -0,0 +1,276 @@
package com.hjq.demo.ui.adapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.base.BaseAdapter;
import com.hjq.demo.R;
import com.hjq.demo.app.AppAdapter;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2021/02/28
* desc : Tab 适配器
*/
public final class TabAdapter extends AppAdapter<String> implements BaseAdapter.OnItemClickListener {
public static final int TAB_MODE_DESIGN = 1;
public static final int TAB_MODE_SLIDING = 2;
/** 当前选中条目位置 */
private int mSelectedPosition = 0;
/** 导航栏监听对象 */
@Nullable
private OnTabListener mListener;
/** Tab 样式 */
private final int mTabMode;
/** Tab 宽度是否固定 */
private final boolean mFixed;
public TabAdapter(Context context) {
this(context, TAB_MODE_DESIGN, true);
}
public TabAdapter(Context context, int tabMode, boolean fixed) {
super(context);
mTabMode = tabMode;
mFixed = fixed;
setOnItemClickListener(this);
registerAdapterDataObserver(new TabAdapterDataObserver());
}
@Override
public int getItemViewType(int position) {
return mTabMode;
}
@NonNull
@Override
public BaseAdapter<?>.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case TAB_MODE_DESIGN:
return new DesignViewHolder();
case TAB_MODE_SLIDING:
return new SlidingViewHolder();
default:
throw new IllegalArgumentException("are you ok?");
}
}
@Override
protected RecyclerView.LayoutManager generateDefaultLayoutManager(Context context) {
if (mFixed) {
int count = getCount();
if (count < 1) {
count = 1;
}
return new GridLayoutManager(context, count, RecyclerView.VERTICAL, false);
} else {
return new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false);
}
}
@Override
public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
// 禁用 RecyclerView 条目动画
recyclerView.setItemAnimator(null);
}
public int getSelectedPosition() {
return mSelectedPosition;
}
public void setSelectedPosition(int position) {
if (mSelectedPosition == position) {
return;
}
notifyItemChanged(mSelectedPosition);
mSelectedPosition = position;
notifyItemChanged(position);
}
/**
* 设置导航栏监听
*/
public void setOnTabListener(@Nullable OnTabListener listener) {
mListener = listener;
}
/**
* {@link BaseAdapter.OnItemClickListener}
*/
@Override
public void onItemClick(RecyclerView recyclerView, View itemView, int position) {
if (mSelectedPosition == position) {
return;
}
if (mListener == null) {
mSelectedPosition = position;
notifyDataSetChanged();
return;
}
if (mListener.onTabSelected(recyclerView, position)) {
mSelectedPosition = position;
notifyDataSetChanged();
}
}
private final class DesignViewHolder extends AppAdapter<?>.ViewHolder {
private final TextView mTitleView;
private final View mLineView;
private DesignViewHolder() {
super(R.layout.tab_item_design);
mTitleView = findViewById(R.id.tv_tab_design_title);
mLineView = findViewById(R.id.v_tab_design_line);
if (!mFixed) {
return;
}
View itemView = getItemView();
ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
itemView.setLayoutParams(layoutParams);
}
@Override
public void onBindView(int position) {
mTitleView.setText(getItem(position));
mTitleView.setSelected(mSelectedPosition == position);
mLineView.setVisibility(mSelectedPosition == position ? View.VISIBLE : View.INVISIBLE);
}
}
private final class SlidingViewHolder extends AppAdapter<?>.ViewHolder
implements ValueAnimator.AnimatorUpdateListener {
private final int mDefaultTextSize;
private final int mSelectedTextSize;
private final TextView mTitleView;
private final View mLineView;
private SlidingViewHolder() {
super(R.layout.tab_item_sliding);
mTitleView = findViewById(R.id.tv_tab_sliding_title);
mLineView = findViewById(R.id.v_tab_sliding_line);
mDefaultTextSize = (int) getResources().getDimension(R.dimen.sp_14);
mSelectedTextSize = (int) getResources().getDimension(R.dimen.sp_15);
mTitleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mDefaultTextSize);
if (!mFixed) {
return;
}
View itemView = getItemView();
ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
itemView.setLayoutParams(layoutParams);
}
@Override
public void onBindView(int position) {
mTitleView.setText(getItem(position));
mTitleView.setSelected(mSelectedPosition == position);
mLineView.setVisibility(mSelectedPosition == position ? View.VISIBLE : View.INVISIBLE);
int textSize = (int) mTitleView.getTextSize();
if (mSelectedPosition == position) {
if (textSize != mSelectedTextSize) {
startAnimator(mDefaultTextSize, mSelectedTextSize);
}
return;
}
if (textSize != mDefaultTextSize) {
startAnimator(mSelectedTextSize, mDefaultTextSize);
}
}
private void startAnimator(int start, int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end);
valueAnimator.addUpdateListener(this);
valueAnimator.setDuration(100);
valueAnimator.start();
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTitleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) animation.getAnimatedValue());
}
}
/**
* 数据改变监听器
*/
private final class TabAdapterDataObserver extends RecyclerView.AdapterDataObserver {
@Override
public void onChanged() {
refreshLayoutManager();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
refreshLayoutManager();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
refreshLayoutManager();
if (getSelectedPosition() > positionStart - itemCount) {
setSelectedPosition(positionStart - itemCount);
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {}
private void refreshLayoutManager() {
if (!mFixed) {
return;
}
RecyclerView recyclerView = getRecyclerView();
if (recyclerView == null) {
return;
}
recyclerView.setLayoutManager(generateDefaultLayoutManager(getContext()));
}
}
/**
* Tab 监听器
*/
public interface OnTabListener {
/**
* Tab 被选中了
*/
boolean onTabSelected(RecyclerView recyclerView, int position);
}
}

View File

@ -10,15 +10,17 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.hjq.base.BaseDialog;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.app.AppAdapter;
import com.hjq.demo.ui.adapter.TabAdapter;
import com.tencent.bugly.crashreport.CrashReport;
import org.json.JSONArray;
import org.json.JSONException;
@ -30,7 +32,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_DRAGGING;
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE;
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_SETTLING;
@ -45,18 +46,21 @@ public final class AddressDialog {
public static final class Builder
extends BaseDialog.Builder<Builder>
implements TabLayout.OnTabSelectedListener,
implements TabAdapter.OnTabListener,
Runnable, RecyclerViewAdapter.OnSelectListener,
BaseDialog.OnShowListener, BaseDialog.OnDismissListener {
private final TextView mTitleView;
private final ImageView mCloseView;
private final TabLayout mTabLayout;
private final RecyclerView mTabView;
private final ViewPager2 mViewPager;
private final TabAdapter mTabAdapter;
private final RecyclerViewAdapter mAdapter;
private final ViewPager2.OnPageChangeCallback mCallback;
@Nullable
private OnListener mListener;
private String mProvince = null;
@ -78,11 +82,13 @@ public final class AddressDialog {
mTitleView = findViewById(R.id.tv_address_title);
mCloseView = findViewById(R.id.iv_address_closer);
mTabLayout = findViewById(R.id.tb_address_tab);
mTabView = findViewById(R.id.rv_address_tab);
setOnClickListener(mCloseView);
mTabLayout.addTab(mTabLayout.newTab().setText(getString(R.string.address_hint)), true);
mTabLayout.addOnTabSelectedListener(this);
mTabAdapter = new TabAdapter(context, TabAdapter.TAB_MODE_SLIDING, false);
mTabAdapter.addItem(getString(R.string.address_hint));
mTabAdapter.setOnTabListener(this);
mTabView.setAdapter(mTabAdapter);
mCallback = new ViewPager2.OnPageChangeCallback() {
@ -92,18 +98,14 @@ public final class AddressDialog {
public void onPageScrollStateChanged(int state) {
mPreviousScrollState = mScrollState;
mScrollState = state;
if (state == ViewPager2.SCROLL_STATE_IDLE && mTabLayout.getSelectedTabPosition() != mViewPager.getCurrentItem()) {
if (state == ViewPager2.SCROLL_STATE_IDLE && mTabAdapter.getSelectedPosition() != mViewPager.getCurrentItem()) {
final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE || (mScrollState == SCROLL_STATE_SETTLING && mPreviousScrollState == SCROLL_STATE_IDLE);
mTabLayout.selectTab(mTabLayout.getTabAt(mViewPager.getCurrentItem()), updateIndicator);
onTabSelected(mTabView, mViewPager.getCurrentItem());
}
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final boolean updateText = mScrollState != SCROLL_STATE_SETTLING || mPreviousScrollState == SCROLL_STATE_DRAGGING;
final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING && mPreviousScrollState == SCROLL_STATE_IDLE);
mTabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
};
// 显示省份列表
@ -124,16 +126,21 @@ public final class AddressDialog {
* 设置默认省份
*/
public Builder setProvince(String province) {
if (!TextUtils.isEmpty(province)) {
List<AddressBean> data = mAdapter.getItem(0);
if (data != null && !data.isEmpty()) {
for (int i = 0; i < data.size(); i++) {
if (province.equals(data.get(i).getName())) {
selectedAddress(0, i, false);
break;
}
}
if (TextUtils.isEmpty(province)) {
return this;
}
List<AddressBean> data = mAdapter.getItem(0);
if (data == null || data.isEmpty()) {
return this;
}
for (int i = 0; i < data.size(); i++) {
if (!province.equals(data.get(i).getName())) {
continue;
}
selectedAddress(0, i, false);
break;
}
return this;
}
@ -146,19 +153,24 @@ public final class AddressDialog {
// 已经忽略了县级区域的选择不能选定指定的城市
throw new IllegalStateException("The selection of county-level regions has been ignored. The designated city cannot be selected");
}
if (!TextUtils.isEmpty(city)) {
List<AddressBean> data = mAdapter.getItem(1);
if (data != null && !data.isEmpty()) {
for (int i = 0; i < data.size(); i++) {
if (city.equals(data.get(i).getName())) {
// 避开直辖市因为选择省的时候已经自动跳过市区了
if (mAdapter.getItem(1).size() > 1) {
selectedAddress(1, i, false);
}
break;
}
}
if (TextUtils.isEmpty(city)) {
return this;
}
List<AddressBean> data = mAdapter.getItem(1);
if (data == null || data.isEmpty()) {
return this;
}
for (int i = 0; i < data.size(); i++) {
if (!city.equals(data.get(i).getName())) {
continue;
}
// 避开直辖市因为选择省的时候已经自动跳过市区了
if (mAdapter.getItem(1).size() > 1) {
selectedAddress(1, i, false);
}
break;
}
return this;
}
@ -167,7 +179,7 @@ public final class AddressDialog {
* 不选择县级区域
*/
public Builder setIgnoreArea() {
if (mAdapter.getItemCount() == 3) {
if (mAdapter.getCount() == 3) {
// 已经指定了城市则不能忽略县级区域
throw new IllegalStateException("Cities have been designated and county-level areas can no longer be ignored");
}
@ -201,9 +213,10 @@ public final class AddressDialog {
case 0:
// 记录当前选择的省份
mProvince = mAdapter.getItem(type).get(position).getName();
mTabLayout.getTabAt(mTabLayout.getSelectedTabPosition()).setText(mProvince);
mTabAdapter.setItem(type, mProvince);
mTabLayout.addTab(mTabLayout.newTab().setText(getString(R.string.address_hint)), true);
mTabAdapter.addItem(getString(R.string.address_hint));
mTabAdapter.setSelectedPosition(type + 1);
mAdapter.addItem(AddressManager.getCityList(mAdapter.getItem(type).get(position).getNext()));
mViewPager.setCurrentItem(type + 1, smoothScroll);
@ -215,7 +228,7 @@ public final class AddressDialog {
case 1:
// 记录当前选择的城市
mCity = mAdapter.getItem(type).get(position).getName();
mTabLayout.getTabAt(mTabLayout.getSelectedTabPosition()).setText(mCity);
mTabAdapter.setItem(type, mCity);
if (mIgnoreArea) {
@ -227,7 +240,8 @@ public final class AddressDialog {
postDelayed(this::dismiss, 300);
} else {
mTabLayout.addTab(mTabLayout.newTab().setText(getString(R.string.address_hint)), true);
mTabAdapter.addItem(getString(R.string.address_hint));
mTabAdapter.setSelectedPosition(type + 1);
mAdapter.addItem(AddressManager.getAreaList(mAdapter.getItem(type).get(position).getNext()));
mViewPager.setCurrentItem(type + 1, smoothScroll);
}
@ -236,7 +250,7 @@ public final class AddressDialog {
case 2:
// 记录当前选择的区域
mArea = mAdapter.getItem(type).get(position).getName();
mTabLayout.getTabAt(mTabLayout.getSelectedTabPosition()).setText(mArea);
mTabAdapter.setItem(type, mArea);
if (mListener != null) {
mListener.onSelected(getDialog(), mProvince, mCity, mArea);
@ -262,40 +276,42 @@ public final class AddressDialog {
public void onClick(View view) {
if (view == mCloseView) {
dismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
/**
* {@link TabLayout.OnTabSelectedListener}
* {@link TabAdapter.OnTabListener}
*/
@Override
public void onTabSelected(TabLayout.Tab tab) {
public boolean onTabSelected(RecyclerView recyclerView, int position) {
synchronized (this) {
if (mViewPager.getCurrentItem() != tab.getPosition()) {
mViewPager.setCurrentItem(tab.getPosition());
if (mViewPager.getCurrentItem() != position) {
mViewPager.setCurrentItem(position);
}
tab.setText(getString(R.string.address_hint));
switch (tab.getPosition()) {
mTabAdapter.setItem(position, getString(R.string.address_hint));
switch (position) {
case 0:
mProvince = mCity = mArea = null;
if (mTabLayout.getTabAt(2) != null) {
mTabLayout.removeTabAt(2);
if (mTabAdapter.getCount() > 2) {
mTabAdapter.removeItem(2);
mAdapter.removeItem(2);
}
if (mTabLayout.getTabAt(1) != null) {
mTabLayout.removeTabAt(1);
if (mTabAdapter.getCount() > 1) {
mTabAdapter.removeItem(1);
mAdapter.removeItem(1);
}
break;
case 1:
mCity = mArea = null;
if (mTabLayout.getTabAt(2) != null) {
mTabLayout.removeTabAt(2);
if (mTabAdapter.getCount() > 2) {
mTabAdapter.removeItem(2);
mAdapter.removeItem(2);
}
break;
@ -306,14 +322,9 @@ public final class AddressDialog {
break;
}
}
return true;
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
/**
* {@link BaseDialog.OnShowListener}
*/
@ -337,6 +348,7 @@ public final class AddressDialog {
private final static class RecyclerViewAdapter extends AppAdapter<List<AddressBean>> {
@Nullable
private OnSelectListener mListener;
private RecyclerViewAdapter(Context context) {
@ -370,13 +382,14 @@ public final class AddressDialog {
@Override
public void onItemClick(RecyclerView recyclerView, View itemView, int position) {
if (mListener != null) {
mListener.onSelected(getViewHolderPosition(), position);
if (mListener == null) {
return;
}
mListener.onSelected(getViewHolderPosition(), position);
}
}
private void setOnSelectListener(OnSelectListener listener) {
private void setOnSelectListener(@Nullable OnSelectListener listener) {
mListener = listener;
}
@ -399,12 +412,12 @@ public final class AddressDialog {
textView.setGravity(Gravity.CENTER_VERTICAL);
textView.setBackgroundResource(R.drawable.transparent_selector);
textView.setTextColor(0xFF222222);
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.sp_14));
textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView.setPadding((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics()),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
textView.setPadding((int) getResources().getDimension(R.dimen.dp_20),
(int) getResources().getDimension(R.dimen.dp_10),
(int) getResources().getDimension(R.dimen.dp_20),
(int) getResources().getDimension(R.dimen.dp_10));
return new ViewHolder(textView);
}
@ -458,20 +471,21 @@ public final class AddressDialog {
// 省市区Json数据文件来源https://github.com/getActivity/ProvinceJson
JSONArray jsonArray = getProvinceJson(context);
if (jsonArray != null) {
int length = jsonArray.length();
ArrayList<AddressBean> list = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
list.add(new AddressBean(jsonObject.getString("name"), jsonObject));
}
return list;
if (jsonArray == null) {
return null;
}
int length = jsonArray.length();
ArrayList<AddressBean> list = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
list.add(new AddressBean(jsonObject.getString("name"), jsonObject));
}
return list;
} catch (JSONException e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
}
return null;
}
@ -494,7 +508,7 @@ public final class AddressDialog {
return list;
} catch (JSONException e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
return null;
}
}
@ -517,7 +531,7 @@ public final class AddressDialog {
}
return list;
} catch (JSONException e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
return null;
}
}
@ -538,7 +552,7 @@ public final class AddressDialog {
inputStream.close();
return new JSONArray(outStream.toString());
} catch (IOException | JSONException e) {
e.printStackTrace();
CrashReport.postCatchedException(e);
}
return null;
}

View File

@ -8,6 +8,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.base.BaseAdapter;
@ -31,6 +32,7 @@ public final class AlbumDialog {
extends BaseDialog.Builder<Builder>
implements BaseAdapter.OnItemClickListener {
@Nullable
private OnListener mListener;
private final RecyclerView mRecyclerView;

View File

@ -30,7 +30,8 @@ public final class DateDialog {
public static final class Builder
extends CommonDialog.Builder<Builder>
implements PickerLayoutManager.OnPickerListener {
implements Runnable,
PickerLayoutManager.OnPickerListener {
private final int mStartYear;
@ -162,11 +163,11 @@ public final class DateDialog {
int index = year - mStartYear;
if (index < 0) {
index = 0;
} else if (index > mYearAdapter.getItemCount() - 1) {
index = mYearAdapter.getItemCount() - 1;
} else if (index > mYearAdapter.getCount() - 1) {
index = mYearAdapter.getCount() - 1;
}
mYearView.scrollToPosition(index);
onPicked(mYearView, index);
refreshMonthMaximumDay();
return this;
}
@ -178,11 +179,11 @@ public final class DateDialog {
int index = month - 1;
if (index < 0) {
index = 0;
} else if (index > mMonthAdapter.getItemCount() - 1) {
index = mMonthAdapter.getItemCount() - 1;
} else if (index > mMonthAdapter.getCount() - 1) {
index = mMonthAdapter.getCount() - 1;
}
mMonthView.scrollToPosition(index);
onPicked(mMonthView, index);
refreshMonthMaximumDay();
return this;
}
@ -194,53 +195,71 @@ public final class DateDialog {
int index = day - 1;
if (index < 0) {
index = 0;
} else if (index > mDayAdapter.getItemCount() - 1) {
index = mDayAdapter.getItemCount() - 1;
} else if (index > mDayAdapter.getCount() - 1) {
index = mDayAdapter.getCount() - 1;
}
mDayView.scrollToPosition(index);
onPicked(mDayView, index);
refreshMonthMaximumDay();
return this;
}
/**
* {@link PickerLayoutManager.OnPickerListener}
*
* @param recyclerView RecyclerView 对象
* @param position 当前滚动的位置
*/
@Override
public void onPicked(RecyclerView recyclerView, int position) {
// 获取这个月最多有多少天
Calendar calendar = Calendar.getInstance(Locale.CHINA);
calendar.set(mStartYear + mYearManager.getPickedPosition(), mMonthManager.getPickedPosition(), 1);
int day = calendar.getActualMaximum(Calendar.DATE);
if (mDayAdapter.getItemCount() != day) {
ArrayList<String> dayData = new ArrayList<>(day);
for (int i = 1; i <= day; i++) {
dayData.add(i + " " + getString(R.string.common_day));
}
mDayAdapter.setData(dayData);
}
}
@SingleClick
@Override
public void onClick(View view) {
int viewId = view.getId();
if (viewId == R.id.tv_ui_confirm) {
autoDismiss();
if (mListener != null) {
mListener.onSelected(getDialog(), mStartYear + mYearManager.getPickedPosition(), mMonthManager.getPickedPosition() + 1, mDayManager.getPickedPosition() + 1);
if (mListener == null) {
return;
}
mListener.onSelected(getDialog(),
mStartYear + mYearManager.getPickedPosition(),
mMonthManager.getPickedPosition() + 1,
mDayManager.getPickedPosition() + 1);
} else if (viewId == R.id.tv_ui_cancel) {
autoDismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
/**
* {@link PickerLayoutManager.OnPickerListener}
*
* @param recyclerView RecyclerView 对象
* @param position 当前滚动的位置
*/
@Override
public void onPicked(RecyclerView recyclerView, int position) {
refreshMonthMaximumDay();
}
@Override
public void run() {
// 获取这个月最多有多少天
Calendar calendar = Calendar.getInstance(Locale.CHINA);
calendar.set(mStartYear + mYearManager.getPickedPosition(), mMonthManager.getPickedPosition(), 1);
int day = calendar.getActualMaximum(Calendar.DATE);
if (mDayAdapter.getCount() != day) {
ArrayList<String> dayData = new ArrayList<>(day);
for (int i = 1; i <= day; i++) {
dayData.add(i + " " + getString(R.string.common_day));
}
mDayAdapter.setData(dayData);
}
}
/**
* 刷新每个月天最大天数
*/
private void refreshMonthMaximumDay() {
mYearView.removeCallbacks(this);
mYearView.post(this);
}
private static final class PickerAdapter extends AppAdapter<String> {
private PickerAdapter(Context context) {
@ -259,7 +278,7 @@ public final class DateDialog {
ViewHolder() {
super(R.layout.picker_item);
mPickerView = (TextView) findViewById(R.id.tv_picker_name);
mPickerView = findViewById(R.id.tv_picker_name);
}
@Override

View File

@ -1,17 +1,19 @@
package com.hjq.demo.ui.dialog;
import android.content.Context;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.hjq.base.BaseDialog;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.widget.view.RegexEditText;
/**
* author : Android 轮子哥
@ -26,8 +28,9 @@ public final class InputDialog {
implements BaseDialog.OnShowListener,
TextView.OnEditorActionListener {
@Nullable
private OnListener mListener;
private final EditText mInputView;
private final RegexEditText mInputView;
public Builder(Context context) {
super(context);
@ -52,11 +55,21 @@ public final class InputDialog {
}
public Builder setContent(CharSequence text) {
mInputView.setText(text);
int index = mInputView.getText().toString().length();
if (index > 0) {
mInputView.requestFocus();
mInputView.setSelection(index);
Editable editable = mInputView.getText();
if (editable == null) {
return this;
}
int index = editable.length();
if (index <= 0) {
return this;
}
mInputView.requestFocus();
mInputView.setSelection(index);
return this;
}
public Builder setInputRegex(String regex) {
mInputView.setInputRegex(regex);
return this;
}
@ -79,14 +92,17 @@ public final class InputDialog {
int viewId = view.getId();
if (viewId == R.id.tv_ui_confirm) {
autoDismiss();
if (mListener != null) {
mListener.onConfirm(getDialog(), mInputView.getText().toString());
if (mListener == null) {
return;
}
Editable editable = mInputView.getText();
mListener.onConfirm(getDialog(), editable != null ? editable.toString() : "");
} else if (viewId == R.id.tv_ui_cancel) {
autoDismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}

View File

@ -9,6 +9,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;
@ -36,6 +37,7 @@ public final class MenuDialog {
View.OnLayoutChangeListener, Runnable {
@SuppressWarnings("rawtypes")
@Nullable
private OnListener mListener;
private boolean mAutoDismiss = true;
@ -122,9 +124,10 @@ public final class MenuDialog {
}
if (view == mCancelView) {
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
@ -138,9 +141,10 @@ public final class MenuDialog {
dismiss();
}
if (mListener != null) {
mListener.onSelected(getDialog(), position, mAdapter.getItem(position));
if (mListener == null) {
return;
}
mListener.onSelected(getDialog(), position, mAdapter.getItem(position));
}
/**
@ -162,11 +166,12 @@ public final class MenuDialog {
params.height = maxHeight;
mRecyclerView.setLayoutParams(params);
}
} else {
if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
mRecyclerView.setLayoutParams(params);
}
return;
}
if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
mRecyclerView.setLayoutParams(params);
}
}
@ -199,7 +204,7 @@ public final class MenuDialog {
ViewHolder() {
super(R.layout.menu_item);
mTextView = (TextView) findViewById(R.id.tv_menu_text);
mTextView = findViewById(R.id.tv_menu_text);
mLineView = findViewById(R.id.v_menu_line);
}
@ -209,12 +214,12 @@ public final class MenuDialog {
if (position == 0) {
// 当前是否只有一个条目
if (getItemCount() == 1) {
if (getCount() == 1) {
mLineView.setVisibility(View.GONE);
} else {
mLineView.setVisibility(View.VISIBLE);
}
} else if (position == getItemCount() - 1) {
} else if (position == getCount() - 1) {
mLineView.setVisibility(View.GONE);
} else {
mLineView.setVisibility(View.VISIBLE);

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.hjq.base.BaseDialog;
@ -21,6 +22,7 @@ public final class MessageDialog {
public static final class Builder
extends CommonDialog.Builder<Builder> {
@Nullable
private OnListener mListener;
private final TextView mMessageView;
@ -59,14 +61,16 @@ public final class MessageDialog {
int viewId = view.getId();
if (viewId == R.id.tv_ui_confirm) {
autoDismiss();
if (mListener != null) {
mListener.onConfirm(getDialog());
if (mListener == null) {
return;
}
mListener.onConfirm(getDialog());
} else if (viewId == R.id.tv_ui_cancel) {
autoDismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
}

View File

@ -7,6 +7,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -36,6 +37,7 @@ public final class PayPasswordDialog {
/** 输入键盘文本 */
private static final String[] KEYBOARD_TEXT = new String[]{"1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", ""};
@Nullable
private OnListener mListener;
private boolean mAutoDismiss = true;
private final LinkedList<String> mRecordList = new LinkedList<>();
@ -139,9 +141,10 @@ public final class PayPasswordDialog {
for (String s : mRecordList) {
password.append(s);
}
if (mListener != null) {
mListener.onCompleted(getDialog(), password.toString());
if (mListener == null) {
return;
}
mListener.onCompleted(getDialog(), password.toString());
}, 300);
}
break;
@ -157,9 +160,10 @@ public final class PayPasswordDialog {
dismiss();
}
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
}

View File

@ -5,12 +5,14 @@ import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.Nullable;
import com.hjq.base.BaseDialog;
import com.hjq.demo.R;
import com.hjq.demo.aop.SingleClick;
import com.hjq.demo.http.api.GetCodeApi;
import com.hjq.demo.http.api.VerifyCodeApi;
import com.hjq.demo.http.model.HttpData;
import com.hjq.demo.http.request.GetCodeApi;
import com.hjq.demo.http.request.VerifyCodeApi;
import com.hjq.http.EasyHttp;
import com.hjq.http.listener.OnHttpListener;
import com.hjq.toast.ToastUtils;
@ -31,6 +33,7 @@ public final class SafeDialog {
private final EditText mCodeView;
private final CountdownView mCountdownView;
@Nullable
private OnListener mListener;
/** 当前手机号 */
@ -98,9 +101,10 @@ public final class SafeDialog {
if (true) {
autoDismiss();
if (mListener != null) {
mListener.onConfirm(getDialog(), mPhoneNumber, mCodeView.getText().toString());
if (mListener == null) {
return;
}
mListener.onConfirm(getDialog(), mPhoneNumber, mCodeView.getText().toString());
return;
}
@ -114,9 +118,10 @@ public final class SafeDialog {
@Override
public void onSucceed(HttpData<Void> data) {
autoDismiss();
if (mListener != null) {
mListener.onConfirm(getDialog(), mPhoneNumber, mCodeView.getText().toString());
if (mListener == null) {
return;
}
mListener.onConfirm(getDialog(), mPhoneNumber, mCodeView.getText().toString());
}
@Override
@ -126,9 +131,10 @@ public final class SafeDialog {
});
} else if (viewId == R.id.tv_ui_cancel) {
autoDismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
}

View File

@ -10,6 +10,7 @@ import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.base.BaseAdapter;
@ -37,6 +38,7 @@ public final class SelectDialog {
implements View.OnLayoutChangeListener, Runnable {
@SuppressWarnings("rawtypes")
@Nullable
private OnListener mListener;
private final RecyclerView mRecyclerView;
@ -119,17 +121,19 @@ public final class SelectDialog {
HashMap<Integer, Object> data = mAdapter.getSelectSet();
if (data.size() >= mAdapter.getMinSelect()) {
autoDismiss();
if (mListener != null) {
mListener.onSelected(getDialog(), data);
if (mListener == null) {
return;
}
mListener.onSelected(getDialog(), data);
} else {
ToastUtils.show(String.format(getString(R.string.select_min_hint), mAdapter.getMinSelect()));
}
} else if (viewId == R.id.tv_ui_cancel) {
autoDismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
@ -259,8 +263,8 @@ public final class SelectDialog {
ViewHolder() {
super(R.layout.select_item);
mTextView = (TextView) findViewById(R.id.tv_select_text);
mCheckBox = (CheckBox) findViewById(R.id.tv_select_checkbox);
mTextView = findViewById(R.id.tv_select_text);
mCheckBox = findViewById(R.id.tv_select_checkbox);
}
@Override

View File

@ -1,5 +1,6 @@
package com.hjq.demo.ui.dialog;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
@ -9,8 +10,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -22,6 +23,15 @@ import com.hjq.toast.ToastUtils;
import com.hjq.umeng.Platform;
import com.hjq.umeng.UmengClient;
import com.hjq.umeng.UmengShare;
import com.umeng.socialize.ShareAction;
import com.umeng.socialize.ShareContent;
import com.umeng.socialize.media.UMEmoji;
import com.umeng.socialize.media.UMImage;
import com.umeng.socialize.media.UMMin;
import com.umeng.socialize.media.UMQQMini;
import com.umeng.socialize.media.UMVideo;
import com.umeng.socialize.media.UMWeb;
import com.umeng.socialize.media.UMusic;
import java.util.ArrayList;
import java.util.List;
@ -38,14 +48,17 @@ public final class ShareDialog {
extends BaseDialog.Builder<Builder>
implements BaseAdapter.OnItemClickListener {
private final RecyclerView mRecyclerView;
private final ShareAdapter mAdapter;
private final UmengShare.ShareData mData;
private final ShareAction mShareAction;
private final ShareBean mCopyLink;
@Nullable
private UmengShare.OnShareListener mListener;
public Builder(Context context) {
super(context);
public Builder(Activity activity) {
super(activity);
setContentView(R.layout.share_dialog);
@ -54,44 +67,95 @@ public final class ShareDialog {
data.add(new ShareBean(getDrawable(R.drawable.share_moment_ic), getString(R.string.share_platform_moment), Platform.CIRCLE));
data.add(new ShareBean(getDrawable(R.drawable.share_qq_ic), getString(R.string.share_platform_qq), Platform.QQ));
data.add(new ShareBean(getDrawable(R.drawable.share_qzone_ic), getString(R.string.share_platform_qzone), Platform.QZONE));
data.add(new ShareBean(getDrawable(R.drawable.share_link_ic), getString(R.string.share_platform_link), null));
mAdapter = new ShareAdapter(context);
mCopyLink = new ShareBean(getDrawable(R.drawable.share_link_ic), getString(R.string.share_platform_link), null);
mAdapter = new ShareAdapter(activity);
mAdapter.setData(data);
mAdapter.setOnItemClickListener(this);
RecyclerView recyclerView = findViewById(R.id.rv_share_list);
recyclerView.setLayoutManager(new GridLayoutManager(context, data.size()));
recyclerView.setAdapter(mAdapter);
mRecyclerView = findViewById(R.id.rv_share_list);
mRecyclerView.setLayoutManager(new GridLayoutManager(activity, data.size()));
mRecyclerView.setAdapter(mAdapter);
mData = new UmengShare.ShareData(context);
mShareAction = new ShareAction(activity);
}
public Builder setShareTitle(String title) {
mData.setShareTitle(title);
/**
* 分享网页链接https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu7F51u9875u94FEu63A51
*/
public Builder setShareLink(UMWeb content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
public Builder setShareDescription(String description) {
mData.setShareDescription(description);
/**
* 分享图片https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu56FEu72473
*/
public Builder setShareImage(UMImage content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
public Builder setShareLogo(String url) {
mData.setShareLogo(url);
/**
* 分享纯文本https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu7EAFu6587u672C5
*/
public Builder setShareText(String content) {
mShareAction.withText(content);
refreshShareOptions();
return this;
}
public Builder setShareLogo(@DrawableRes int id) {
mData.setShareLogo(id);
/**
* 分享音乐https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu97F3u4E507
*/
public Builder setShareMusic(UMusic content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
public Builder setShareUrl(String url) {
mData.setShareUrl(url);
/**
* 分享视频https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu89C6u98916
*/
public Builder setShareVideo(UMVideo content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
/**
* 分享 Gif 表情https://developer.umeng.com/docs/128606/detail/193883#h2--gif-8
*/
public Builder setShareEmoji(UMEmoji content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
/**
* 分享微信小程序https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu5C0Fu7A0Bu5E8F2
*/
public Builder setShareMin(UMMin content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
/**
* 分享 QQ 小程序https://developer.umeng.com/docs/128606/detail/193883#h2-u5206u4EABu5C0Fu7A0Bu5E8F2
*/
public Builder setShareMin(UMQQMini content) {
mShareAction.withMedia(content);
refreshShareOptions();
return this;
}
/**
* 设置回调监听器
*/
public Builder setListener(UmengShare.OnShareListener listener) {
mListener = listener;
return this;
@ -102,16 +166,38 @@ public final class ShareDialog {
*/
@Override
public void onItemClick(RecyclerView recyclerView, View itemView, int position) {
Platform platform = mAdapter.getItem(position).getSharePlatform();
Platform platform = mAdapter.getItem(position).sharePlatform;
if (platform != null) {
UmengClient.share(getActivity(), platform, mData, mListener);
UmengClient.share(getActivity(), platform, mShareAction, mListener);
} else {
// 复制到剪贴板
getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText("url", mData.getShareUrl()));
ToastUtils.show(R.string.share_platform_copy_hint);
if (mShareAction.getShareContent().getShareType() == ShareContent.WEB_STYLE) {
// 复制到剪贴板
getSystemService(ClipboardManager.class).setPrimaryClip(ClipData.newPlainText("url", mShareAction.getShareContent().mMedia.toUrl()));
ToastUtils.show(R.string.share_platform_copy_hint);
}
}
dismiss();
}
/**
* 刷新分享选项
*/
private void refreshShareOptions() {
switch (mShareAction.getShareContent().getShareType()) {
case ShareContent.WEB_STYLE:
if (!mAdapter.containsItem(mCopyLink)) {
mAdapter.addItem(mCopyLink);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), mAdapter.getCount()));
}
break;
default:
if (mAdapter.containsItem(mCopyLink)) {
mAdapter.removeItem(mCopyLink);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), mAdapter.getCount()));
}
break;
}
}
}
private static class ShareAdapter extends AppAdapter<ShareBean> {
@ -133,45 +219,32 @@ public final class ShareDialog {
private ViewHolder() {
super(R.layout.share_item);
mImageView = (ImageView) findViewById(R.id.iv_share_image);
mTextView = (TextView) findViewById(R.id.tv_share_text);
mImageView = findViewById(R.id.iv_share_image);
mTextView = findViewById(R.id.tv_share_text);
}
@Override
public void onBindView(int position) {
ShareBean bean = getItem(position);
mImageView.setImageDrawable(bean.getShareIcon());
mTextView.setText(bean.getShareName());
mImageView.setImageDrawable(bean.shareIcon);
mTextView.setText(bean.shareName);
}
}
}
private static class ShareBean {
/** 分享图标 */
private final Drawable mShareIcon;
final Drawable shareIcon;
/** 分享名称 */
private final String mShareName;
final String shareName;
/** 分享平台 */
private final Platform mSharePlatform;
final Platform sharePlatform;
private ShareBean(Drawable icon, String name, Platform platform) {
mShareIcon = icon;
mShareName = name;
mSharePlatform = platform;
}
private Drawable getShareIcon() {
return mShareIcon;
}
private String getShareName() {
return mShareName;
}
private Platform getSharePlatform() {
return mSharePlatform;
shareIcon = icon;
shareName = name;
sharePlatform = platform;
}
}
}

View File

@ -6,6 +6,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.base.BaseDialog;
@ -26,7 +27,7 @@ import java.util.Calendar;
public final class TimeDialog {
public static final class Builder
extends CommonDialog.Builder<Builder> implements Runnable {
extends CommonDialog.Builder<Builder> {
private final RecyclerView mHourView;
private final RecyclerView mMinuteView;
@ -40,6 +41,7 @@ public final class TimeDialog {
private final PickerAdapter mMinuteAdapter;
private final PickerAdapter mSecondAdapter;
@Nullable
private OnListener mListener;
@SuppressWarnings("all")
@ -97,27 +99,6 @@ public final class TimeDialog {
setHour(calendar.get(Calendar.HOUR_OF_DAY));
setMinute(calendar.get(Calendar.MINUTE));
setSecond(calendar.get(Calendar.SECOND));
postDelayed(this, 1000);
}
@Override
public void run() {
if (mHourView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE &&
mMinuteView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE &&
mSecondView.getScrollState() == RecyclerView.SCROLL_STATE_IDLE) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, mHourManager.getPickedPosition());
calendar.set(Calendar.MINUTE, mMinuteManager.getPickedPosition());
calendar.set(Calendar.SECOND, mSecondManager.getPickedPosition());
if (System.currentTimeMillis() - calendar.getTimeInMillis() < 3000) {
calendar = Calendar.getInstance();
setHour(calendar.get(Calendar.HOUR_OF_DAY));
setMinute(calendar.get(Calendar.MINUTE));
setSecond(calendar.get(Calendar.SECOND));
postDelayed(this, 1000);
}
}
}
public Builder setListener(OnListener listener) {
@ -156,8 +137,8 @@ public final class TimeDialog {
int index = hour;
if (index < 0 || hour == 24) {
index = 0;
} else if (index > mHourAdapter.getItemCount() - 1) {
index = mHourAdapter.getItemCount() - 1;
} else if (index > mHourAdapter.getCount() - 1) {
index = mHourAdapter.getCount() - 1;
}
mHourView.scrollToPosition(index);
return this;
@ -171,8 +152,8 @@ public final class TimeDialog {
int index = minute;
if (index < 0) {
index = 0;
} else if (index > mMinuteAdapter.getItemCount() - 1) {
index = mMinuteAdapter.getItemCount() - 1;
} else if (index > mMinuteAdapter.getCount() - 1) {
index = mMinuteAdapter.getCount() - 1;
}
mMinuteView.scrollToPosition(index);
return this;
@ -186,8 +167,8 @@ public final class TimeDialog {
int index = second;
if (index < 0) {
index = 0;
} else if (index > mSecondAdapter.getItemCount() - 1) {
index = mSecondAdapter.getItemCount() - 1;
} else if (index > mSecondAdapter.getCount() - 1) {
index = mSecondAdapter.getCount() - 1;
}
mSecondView.scrollToPosition(index);
return this;
@ -199,14 +180,16 @@ public final class TimeDialog {
int viewId = view.getId();
if (viewId == R.id.tv_ui_confirm) {
autoDismiss();
if (mListener != null) {
mListener.onSelected(getDialog(), mHourManager.getPickedPosition(), mMinuteManager.getPickedPosition(), mSecondManager.getPickedPosition());
if (mListener == null) {
return;
}
mListener.onSelected(getDialog(), mHourManager.getPickedPosition(), mMinuteManager.getPickedPosition(), mSecondManager.getPickedPosition());
} else if (viewId == R.id.tv_ui_cancel) {
autoDismiss();
if (mListener != null) {
mListener.onCancel(getDialog());
if (mListener == null) {
return;
}
mListener.onCancel(getDialog());
}
}
}
@ -229,7 +212,7 @@ public final class TimeDialog {
ViewHolder() {
super(R.layout.picker_item);
mPickerView = (TextView) findViewById(R.id.tv_picker_name);
mPickerView = findViewById(R.id.tv_picker_name);
}
@Override

View File

@ -15,13 +15,13 @@ import com.hjq.demo.R;
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
* time : 2018/12/2
* desc : Toast 效果对话框
* desc : 提示对话框
*/
public final class HintDialog {
public final class TipsDialog {
public final static int ICON_FINISH = R.drawable.finish_ic;
public final static int ICON_ERROR = R.drawable.error_ic;
public final static int ICON_WARNING = R.drawable.warning_ic;
public final static int ICON_FINISH = R.drawable.tips_finish_ic;
public final static int ICON_ERROR = R.drawable.tips_error_ic;
public final static int ICON_WARNING = R.drawable.tips_warning_ic;
public static final class Builder
extends BaseDialog.Builder<Builder>
@ -34,13 +34,13 @@ public final class HintDialog {
public Builder(Context context) {
super(context);
setContentView(R.layout.hint_dialog);
setContentView(R.layout.tips_dialog);
setAnimStyle(BaseDialog.ANIM_TOAST);
setBackgroundDimEnabled(false);
setCancelable(false);
mMessageView = findViewById(R.id.tv_hint_message);
mIconView = findViewById(R.id.iv_status_icon);
mMessageView = findViewById(R.id.tv_tips_message);
mIconView = findViewById(R.id.iv_tips_icon);
addOnShowListener(this);
}
@ -85,9 +85,10 @@ public final class HintDialog {
@Override
public void run() {
if (isShowing()) {
dismiss();
if (!isShowing()) {
return;
}
dismiss();
}
}
}

View File

@ -9,6 +9,7 @@ import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -74,6 +75,9 @@ public final class UpdateDialog {
mUpdateView = findViewById(R.id.tv_update_update);
mCloseView = findViewById(R.id.tv_update_close);
setOnClickListener(mUpdateView, mCloseView);
// TextView 支持滚动
mContentView.setMovementMethod(new ScrollingMovementMethod());
}
/**
@ -145,7 +149,7 @@ public final class UpdateDialog {
* 下载 Apk
*/
@CheckNet
@Permissions({Permission.MANAGE_EXTERNAL_STORAGE})
@Permissions({Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE, Permission.REQUEST_INSTALL_PACKAGES})
private void downloadApk() {
// 设置对话框不能被取消
setCancelable(false);
@ -183,7 +187,7 @@ public final class UpdateDialog {
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
// 创建要下载的文件对象
mApkFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
mApkFile = new File(getContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
getString(R.string.app_name) + "_v" + mNameView.getText().toString() + ".apk");
EasyHttp.download(getDialog())
.method(HttpMethod.GET)

View File

@ -10,10 +10,9 @@ import androidx.annotation.NonNull;
import com.hjq.demo.R;
import com.hjq.demo.action.StatusAction;
import com.hjq.demo.aop.CheckNet;
import com.hjq.demo.aop.DebugLog;
import com.hjq.demo.aop.Log;
import com.hjq.demo.app.AppActivity;
import com.hjq.demo.app.AppFragment;
import com.hjq.demo.other.IntentKey;
import com.hjq.demo.ui.activity.BrowserActivity;
import com.hjq.demo.widget.BrowserView;
import com.hjq.demo.widget.StatusLayout;
@ -30,11 +29,13 @@ import com.scwang.smart.refresh.layout.listener.OnRefreshListener;
public final class BrowserFragment extends AppFragment<AppActivity>
implements StatusAction, OnRefreshListener {
@DebugLog
private static final String INTENT_KEY_IN_URL = "url";
@Log
public static BrowserFragment newInstance(String url) {
BrowserFragment fragment = new BrowserFragment();
Bundle bundle = new Bundle();
bundle.putString(IntentKey.URL, url);
bundle.putString(INTENT_KEY_IN_URL, url);
fragment.setArguments(bundle);
return fragment;
}
@ -54,14 +55,17 @@ public final class BrowserFragment extends AppFragment<AppActivity>
mRefreshLayout = findViewById(R.id.sl_browser_refresh);
mBrowserView = findViewById(R.id.wv_browser_view);
// 设置 WebView 生命周期回调
mBrowserView.setLifecycleOwner(this);
// 设置网页刷新监听
mRefreshLayout.setOnRefreshListener(this);
}
@Override
protected void initData() {
mBrowserView.setBrowserViewClient(new MyBrowserViewClient());
mBrowserView.loadUrl(getString(IntentKey.URL));
mBrowserView.setBrowserViewClient(new AppBrowserViewClient());
mBrowserView.setBrowserChromeClient(new BrowserView.BrowserChromeClient(mBrowserView));
mBrowserView.loadUrl(getString(INTENT_KEY_IN_URL));
showLoading();
}
@ -87,13 +91,7 @@ public final class BrowserFragment extends AppFragment<AppActivity>
reload();
}
@Override
public void onDestroy() {
super.onDestroy();
mBrowserView.onDestroy();
}
private class MyBrowserViewClient extends BrowserView.BrowserViewClient {
private class AppBrowserViewClient extends BrowserView.BrowserViewClient {
/**
* 网页加载错误时回调这个方法会在 onPageFinished 之前调用
@ -101,7 +99,7 @@ public final class BrowserFragment extends AppFragment<AppActivity>
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// 这里为什么要用延迟呢因为加载出错之后会先调用 onReceivedError 再调用 onPageFinished
post(() -> showError(v -> reload()));
post(() -> showError(listener -> reload()));
}
/**

View File

@ -1,6 +1,5 @@
package com.hjq.demo.ui.fragment;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
@ -54,14 +53,14 @@ public final class FindFragment extends TitleBarFragment<HomeActivity>
protected void initData() {
// 显示圆形的 ImageView
GlideApp.with(this)
.load(R.drawable.example_bg)
.load(R.drawable.update_app_top_bg)
.transform(new MultiTransformation<>(new CenterCrop(), new CircleCrop()))
.into(mCircleView);
// 显示圆角的 ImageView
GlideApp.with(this)
.load(R.drawable.example_bg)
.transform(new MultiTransformation<>(new CenterCrop(), new RoundedCorners((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()))))
.load(R.drawable.update_app_top_bg)
.transform(new MultiTransformation<>(new CenterCrop(), new RoundedCorners((int) getResources().getDimension(R.dimen.dp_10))))
.into(mCornerView);
}

View File

@ -7,15 +7,16 @@ import android.widget.TextView;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import com.gyf.immersionbar.ImmersionBar;
import com.hjq.base.FragmentPagerAdapter;
import com.hjq.demo.R;
import com.hjq.demo.app.AppFragment;
import com.hjq.demo.app.TitleBarFragment;
import com.hjq.demo.ui.activity.HomeActivity;
import com.hjq.demo.ui.adapter.TabAdapter;
import com.hjq.demo.widget.XCollapsingToolbarLayout;
/**
@ -25,7 +26,8 @@ import com.hjq.demo.widget.XCollapsingToolbarLayout;
* desc : 首页 Fragment
*/
public final class HomeFragment extends TitleBarFragment<HomeActivity>
implements XCollapsingToolbarLayout.OnScrimsListener {
implements TabAdapter.OnTabListener, ViewPager.OnPageChangeListener,
XCollapsingToolbarLayout.OnScrimsListener {
private XCollapsingToolbarLayout mCollapsingToolbarLayout;
private Toolbar mToolbar;
@ -34,8 +36,10 @@ public final class HomeFragment extends TitleBarFragment<HomeActivity>
private TextView mHintView;
private AppCompatImageView mSearchView;
private TabLayout mTabLayout;
private RecyclerView mTabView;
private ViewPager mViewPager;
private TabAdapter mTabAdapter;
private FragmentPagerAdapter<AppFragment<?>> mPagerAdapter;
public static HomeFragment newInstance() {
@ -56,14 +60,17 @@ public final class HomeFragment extends TitleBarFragment<HomeActivity>
mHintView = findViewById(R.id.tv_home_hint);
mSearchView = findViewById(R.id.iv_home_search);
mTabLayout = findViewById(R.id.tl_home_tab);
mTabView = findViewById(R.id.rv_home_tab);
mViewPager = findViewById(R.id.vp_home_pager);
mPagerAdapter = new FragmentPagerAdapter<>(this);
mPagerAdapter.addFragment(StatusFragment.newInstance(), "列表演示");
mPagerAdapter.addFragment(BrowserFragment.newInstance("https://github.com/getActivity"), "网页演示");
mViewPager.setAdapter(mPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
mViewPager.addOnPageChangeListener(this);
mTabAdapter = new TabAdapter(getAttachActivity());
mTabView.setAdapter(mTabAdapter);
// 给这个 ToolBar 设置顶部内边距才能和 TitleBar 进行对齐
ImmersionBar.setTitleBar(getAttachActivity(), mToolbar);
@ -74,7 +81,9 @@ public final class HomeFragment extends TitleBarFragment<HomeActivity>
@Override
protected void initData() {
mTabAdapter.addItem("列表演示");
mTabAdapter.addItem("网页演示");
mTabAdapter.setOnTabListener(this);
}
@Override
@ -88,6 +97,34 @@ public final class HomeFragment extends TitleBarFragment<HomeActivity>
return mCollapsingToolbarLayout.isScrimsShown();
}
/**
* {@link TabAdapter.OnTabListener}
*/
@Override
public boolean onTabSelected(RecyclerView recyclerView, int position) {
mViewPager.setCurrentItem(position);
return true;
}
/**
* {@link ViewPager.OnPageChangeListener}
*/
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
@Override
public void onPageSelected(int position) {
if (mTabAdapter == null) {
return;
}
mTabAdapter.setSelectedPosition(position);
}
@Override
public void onPageScrollStateChanged(int state) {}
/**
* CollapsingToolbarLayout 渐变回调
*
@ -96,18 +133,18 @@ public final class HomeFragment extends TitleBarFragment<HomeActivity>
@SuppressLint("RestrictedApi")
@Override
public void onScrimsStateChange(XCollapsingToolbarLayout layout, boolean shown) {
if (shown) {
mAddressView.setTextColor(ContextCompat.getColor(getAttachActivity(), R.color.black));
mHintView.setBackgroundResource(R.drawable.home_search_bar_gray_bg);
mHintView.setTextColor(ContextCompat.getColor(getAttachActivity(), R.color.black60));
mSearchView.setSupportImageTintList(ColorStateList.valueOf(getColor(R.color.common_icon_color)));
getStatusBarConfig().statusBarDarkFont(true).init();
} else {
mAddressView.setTextColor(ContextCompat.getColor(getAttachActivity(), R.color.white));
mHintView.setBackgroundResource(R.drawable.home_search_bar_transparent_bg);
mHintView.setTextColor(ContextCompat.getColor(getAttachActivity(), R.color.white60));
mSearchView.setSupportImageTintList(ColorStateList.valueOf(getColor(R.color.white)));
getStatusBarConfig().statusBarDarkFont(false).init();
}
getStatusBarConfig().statusBarDarkFont(shown).init();
mAddressView.setTextColor(ContextCompat.getColor(getAttachActivity(), shown ? R.color.black : R.color.white));
mHintView.setBackgroundResource(shown ? R.drawable.home_search_bar_gray_bg : R.drawable.home_search_bar_transparent_bg);
mHintView.setTextColor(ContextCompat.getColor(getAttachActivity(), shown ? R.color.black60 : R.color.white60));
mSearchView.setSupportImageTintList(ColorStateList.valueOf(getColor(shown ? R.color.common_icon_color : R.color.white)));
}
@Override
public void onDestroy() {
super.onDestroy();
mViewPager.setAdapter(null);
mViewPager.removeOnPageChangeListener(this);
mTabAdapter.setOnTabListener(null);
}
}

View File

@ -1,6 +1,5 @@
package com.hjq.demo.ui.fragment;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
@ -76,8 +75,7 @@ public final class MessageFragment extends TitleBarFragment<HomeActivity> {
mImageView.setVisibility(View.VISIBLE);
GlideApp.with(this)
.load("https://www.baidu.com/img/bd_logo.png")
.transform(new RoundedCorners((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
20, getResources().getDisplayMetrics())))
.transform(new RoundedCorners((int) getResources().getDimension(R.dimen.dp_20)))
.into(mImageView);
} else if (viewId == R.id.btn_message_toast) {
@ -90,15 +88,21 @@ public final class MessageFragment extends TitleBarFragment<HomeActivity> {
} else if (viewId == R.id.btn_message_setting) {
XXPermissions.startApplicationDetails(this);
XXPermissions.startPermissionActivity(this);
} else if (viewId == R.id.btn_message_black) {
getAttachActivity().getStatusBarConfig().statusBarDarkFont(true).init();
getAttachActivity()
.getStatusBarConfig()
.statusBarDarkFont(true)
.init();
} else if (viewId == R.id.btn_message_white) {
getAttachActivity().getStatusBarConfig().statusBarDarkFont(false).init();
getAttachActivity()
.getStatusBarConfig()
.statusBarDarkFont(false)
.init();
} else if (viewId == R.id.btn_message_tab) {

View File

@ -2,6 +2,7 @@ package com.hjq.demo.ui.fragment;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.view.View;
@ -38,23 +39,23 @@ import java.util.List;
* time : 2018/10/18
* desc : 我的 Fragment
*/
public final class MeFragment extends TitleBarFragment<HomeActivity> {
public final class MineFragment extends TitleBarFragment<HomeActivity> {
public static MeFragment newInstance() {
return new MeFragment();
public static MineFragment newInstance() {
return new MineFragment();
}
@Override
protected int getLayoutId() {
return R.layout.me_fragment;
return R.layout.mine_fragment;
}
@Override
protected void initView() {
setOnClickListener(R.id.btn_me_dialog, R.id.btn_me_hint, R.id.btn_me_login, R.id.btn_me_register, R.id.btn_me_forget,
R.id.btn_me_reset, R.id.btn_me_change, R.id.btn_me_personal, R.id.btn_message_setting, R.id.btn_me_about,
R.id.btn_me_guide, R.id.btn_me_browser, R.id.btn_me_image_select, R.id.btn_me_image_preview,
R.id.btn_me_video_select, R.id.btn_me_video_play, R.id.btn_me_crash, R.id.btn_me_pay);
setOnClickListener(R.id.btn_mine_dialog, R.id.btn_mine_hint, R.id.btn_mine_login, R.id.btn_mine_register, R.id.btn_mine_forget,
R.id.btn_mine_reset, R.id.btn_mine_change, R.id.btn_mine_personal, R.id.btn_mine_setting, R.id.btn_mine_about,
R.id.btn_mine_guide, R.id.btn_mine_browser, R.id.btn_mine_image_select, R.id.btn_mine_image_preview,
R.id.btn_mine_video_select, R.id.btn_mine_video_play, R.id.btn_mine_crash, R.id.btn_mine_pay);
}
@Override
@ -66,51 +67,51 @@ public final class MeFragment extends TitleBarFragment<HomeActivity> {
@Override
public void onClick(View view) {
int viewId = view.getId();
if (viewId == R.id.btn_me_dialog) {
if (viewId == R.id.btn_mine_dialog) {
startActivity(DialogActivity.class);
} else if (viewId == R.id.btn_me_hint) {
} else if (viewId == R.id.btn_mine_hint) {
startActivity(StatusActivity.class);
} else if (viewId == R.id.btn_me_login) {
} else if (viewId == R.id.btn_mine_login) {
startActivity(LoginActivity.class);
} else if (viewId == R.id.btn_me_register) {
} else if (viewId == R.id.btn_mine_register) {
startActivity(RegisterActivity.class);
} else if (viewId == R.id.btn_me_forget) {
} else if (viewId == R.id.btn_mine_forget) {
startActivity(PasswordForgetActivity.class);
} else if (viewId == R.id.btn_me_reset) {
} else if (viewId == R.id.btn_mine_reset) {
startActivity(PasswordResetActivity.class);
} else if (viewId == R.id.btn_me_change) {
} else if (viewId == R.id.btn_mine_change) {
startActivity(PhoneResetActivity.class);
} else if (viewId == R.id.btn_me_personal) {
} else if (viewId == R.id.btn_mine_personal) {
startActivity(PersonalDataActivity.class);
} else if (viewId == R.id.btn_message_setting) {
} else if (viewId == R.id.btn_mine_setting) {
startActivity(SettingActivity.class);
} else if (viewId == R.id.btn_me_about) {
} else if (viewId == R.id.btn_mine_about) {
startActivity(AboutActivity.class);
} else if (viewId == R.id.btn_me_guide) {
} else if (viewId == R.id.btn_mine_guide) {
startActivity(GuideActivity.class);
} else if (viewId == R.id.btn_me_browser) {
} else if (viewId == R.id.btn_mine_browser) {
new InputDialog.Builder(getAttachActivity())
.setTitle("跳转到网页")
@ -121,7 +122,7 @@ public final class MeFragment extends TitleBarFragment<HomeActivity> {
.setListener((dialog, content) -> BrowserActivity.start(getAttachActivity(), content))
.show();
} else if (viewId == R.id.btn_me_image_select) {
} else if (viewId == R.id.btn_mine_image_select) {
ImageSelectActivity.start(getAttachActivity(), new ImageSelectActivity.OnPhotoSelectListener() {
@ -136,14 +137,14 @@ public final class MeFragment extends TitleBarFragment<HomeActivity> {
}
});
} else if (viewId == R.id.btn_me_image_preview) {
} else if (viewId == R.id.btn_mine_image_preview) {
ArrayList<String> images = new ArrayList<>();
images.add("https://www.baidu.com/img/bd_logo.png");
images.add("https://avatars1.githubusercontent.com/u/28616817");
ImagePreviewActivity.start(getAttachActivity(), images, images.size() - 1);
} else if (viewId == R.id.btn_me_video_select) {
} else if (viewId == R.id.btn_mine_video_select) {
VideoSelectActivity.start(getAttachActivity(), new VideoSelectActivity.OnVideoSelectListener() {
@ -158,20 +159,23 @@ public final class MeFragment extends TitleBarFragment<HomeActivity> {
}
});
} else if (viewId == R.id.btn_me_video_play) {
} else if (viewId == R.id.btn_mine_video_play) {
new VideoPlayActivity.Builder()
.setVideoTitle("速度与激情特别行动")
.setVideoSource("http://vfx.mtime.cn/Video/2019/06/29/mp4/190629004821240734.mp4")
.setActivityOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
.start(getAttachActivity());
} else if (viewId == R.id.btn_me_crash) {
} else if (viewId == R.id.btn_mine_crash) {
// 上报错误到 Bugly
CrashReport.postCatchedException(new IllegalStateException("are you ok?"));
// 关闭 Bugly 异常捕捉
CrashReport.closeBugly();
throw new IllegalStateException("are you ok?");
} else if (viewId == R.id.btn_me_pay) {
} else if (viewId == R.id.btn_mine_pay) {
new MessageDialog.Builder(getAttachActivity())
.setTitle("捐赠")
@ -180,7 +184,7 @@ public final class MeFragment extends TitleBarFragment<HomeActivity> {
.setCancel(null)
//.setAutoDismiss(false)
.setListener(dialog -> {
BrowserActivity.start(getAttachActivity(), "https://gitee.com/getActivity/Donate");
BrowserActivity.start(getAttachActivity(), "https://github.com/getActivity/Donate");
toast("AndroidProject 因为有你的支持而能够不断更新、完善,非常感谢支持!");
postDelayed(() -> {
try {
@ -193,7 +197,6 @@ public final class MeFragment extends TitleBarFragment<HomeActivity> {
}, 2000);
})
.show();
}
}

View File

@ -73,7 +73,7 @@ public final class StatusFragment extends TitleBarFragment<AppActivity>
*/
private List<String> analogData() {
List<String> data = new ArrayList<>();
for (int i = mAdapter.getItemCount(); i < mAdapter.getItemCount() + 20; i++) {
for (int i = mAdapter.getCount(); i < mAdapter.getCount() + 20; i++) {
data.add("我是第" + i + "条目");
}
return data;
@ -110,7 +110,7 @@ public final class StatusFragment extends TitleBarFragment<AppActivity>
mAdapter.addData(analogData());
mRefreshLayout.finishLoadMore();
mAdapter.setLastPage(mAdapter.getItemCount() >= 100);
mAdapter.setLastPage(mAdapter.getCount() >= 100);
mRefreshLayout.setNoMoreData(mAdapter.isLastPage());
}, 1000);
}

View File

@ -8,6 +8,7 @@ import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.base.BaseAdapter;
@ -34,6 +35,7 @@ public final class ListPopup {
implements BaseAdapter.OnItemClickListener {
@SuppressWarnings("rawtypes")
@Nullable
private OnListener mListener;
private boolean mAutoDismiss = true;
@ -53,7 +55,7 @@ public final class ListPopup {
new ArrowDrawable.Builder(context)
.setArrowOrientation(Gravity.TOP)
.setArrowGravity(Gravity.CENTER)
.setShadowSize((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()))
.setShadowSize((int) getResources().getDimension(R.dimen.dp_10))
.setBackgroundColor(0xFFFFFFFF)
.apply(recyclerView);
}
@ -112,9 +114,10 @@ public final class ListPopup {
dismiss();
}
if (mListener != null) {
mListener.onSelected(getPopupWindow(), position, mAdapter.getItem(position));
if (mListener == null) {
return;
}
mListener.onSelected(getPopupWindow(), position, mAdapter.getItem(position));
}
}
@ -138,16 +141,17 @@ public final class ListPopup {
super(new TextView(getContext()));
mTextView = (TextView) getItemView();
mTextView.setTextColor(getColor(R.color.black50));
mTextView.setTextSize(16);
mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.sp_16));
}
@Override
public void onBindView(int position) {
mTextView.setText(getItem(position).toString());
mTextView.setPaddingRelative((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12, getResources().getDisplayMetrics()),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, position == 0 ? 12 : 0, getResources().getDisplayMetrics()),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12, getResources().getDisplayMetrics()),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()));
mTextView.setPaddingRelative((int) getResources().getDimension(R.dimen.dp_12),
(position == 0 ? (int) getResources().getDimension(R.dimen.dp_12) : 0),
(int) getResources().getDimension(R.dimen.dp_12),
(int) getResources().getDimension(R.dimen.dp_10));
}
}
}

View File

@ -9,6 +9,7 @@ import android.content.Intent;
import android.net.Uri;
import android.net.http.SslError;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.webkit.GeolocationPermissions;
import android.webkit.JsPromptResult;
@ -34,13 +35,16 @@ import com.hjq.base.action.ActivityAction;
import com.hjq.demo.R;
import com.hjq.demo.other.AppConfig;
import com.hjq.demo.other.PermissionCallback;
import com.hjq.demo.ui.dialog.HintDialog;
import com.hjq.demo.ui.activity.ImageSelectActivity;
import com.hjq.demo.ui.activity.VideoSelectActivity;
import com.hjq.demo.ui.dialog.InputDialog;
import com.hjq.demo.ui.dialog.MessageDialog;
import com.hjq.demo.ui.dialog.TipsDialog;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
import com.hjq.widget.layout.NestedScrollWebView;
import java.io.File;
import java.util.List;
import timber.log.Timber;
@ -128,10 +132,10 @@ public final class BrowserView extends NestedScrollWebView
public String getUrl() {
String originalUrl = super.getOriginalUrl();
// 避免开始时同时加载两个地址而导致的崩溃
if (originalUrl != null) {
return originalUrl;
if (originalUrl == null) {
return super.getUrl();
}
return super.getUrl();
return originalUrl;
}
/**
@ -152,7 +156,7 @@ public final class BrowserView extends NestedScrollWebView
onResume();
resumeTimers();
break;
case ON_PAUSE:
case ON_STOP:
onPause();
pauseTimers();
break;
@ -219,6 +223,7 @@ public final class BrowserView extends NestedScrollWebView
return;
}
// 如何处理应用中的 WebView SSL 错误处理程序提醒https://support.google.com/faqs/answer/7071387?hl=zh-Hans
new MessageDialog.Builder(context)
.setMessage(R.string.common_web_ssl_error_title)
.setConfirm(R.string.common_web_ssl_error_allow)
@ -277,7 +282,7 @@ public final class BrowserView extends NestedScrollWebView
Timber.i("WebView shouldOverrideUrlLoading%s", url);
String scheme = Uri.parse(url).getScheme();
if (scheme == null) {
return true;
return false;
}
switch (scheme) {
// 如果这是跳链接操作
@ -292,7 +297,6 @@ public final class BrowserView extends NestedScrollWebView
default:
break;
}
// 已经处理该链接请求
return true;
}
@ -341,8 +345,8 @@ public final class BrowserView extends NestedScrollWebView
return false;
}
new HintDialog.Builder(activity)
.setIcon(HintDialog.ICON_WARNING)
new TipsDialog.Builder(activity)
.setIcon(TipsDialog.ICON_WARNING)
.setMessage(message)
.setCancelable(false)
.addOnDismissListener(dialog -> result.confirm())
@ -458,7 +462,6 @@ public final class BrowserView extends NestedScrollWebView
* @param callback 文件选择回调
* @param params 文件选择参数
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> callback, FileChooserParams params) {
Activity activity = mWebView.getActivity();
@ -468,12 +471,12 @@ public final class BrowserView extends NestedScrollWebView
}
XXPermissions.with(activity)
.permission(Permission.MANAGE_EXTERNAL_STORAGE)
.permission(Permission.Group.STORAGE)
.request(new PermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (all) {
openSystemFileChooser((BaseActivity) activity, callback, params);
openSystemFileChooser((BaseActivity) activity, params, callback);
}
}
@ -489,15 +492,59 @@ public final class BrowserView extends NestedScrollWebView
/**
* 打开系统文件选择器
*/
private void openSystemFileChooser(BaseActivity activity, ValueCallback<Uri[]> callback, FileChooserParams params) {
private static void openSystemFileChooser(BaseActivity activity, FileChooserParams params, ValueCallback<Uri[]> callback) {
Intent intent = params.createIntent();
String[] mimeTypes = params.getAcceptTypes();
if (mimeTypes != null && mimeTypes.length > 0 && mimeTypes[0] != null && !"".equals(mimeTypes[0])) {
boolean multipleSelect = params.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
if (mimeTypes != null && mimeTypes.length > 0 && !TextUtils.isEmpty(mimeTypes[0])) {
// 要过滤的文件类型
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
if (mimeTypes.length == 1) {
switch (mimeTypes[0]) {
case "image/*":
ImageSelectActivity.start(activity, multipleSelect ? Integer.MAX_VALUE : 1, new ImageSelectActivity.OnPhotoSelectListener() {
@Override
public void onSelected(List<String> data) {
Uri[] uri = new Uri[data.size()];
for (int i = 0; i < data.size(); i++) {
uri[i] = Uri.fromFile(new File(data.get(i)));
}
callback.onReceiveValue(uri);
}
@Override
public void onCancel() {
callback.onReceiveValue(null);
}
});
return;
case "video/*":
VideoSelectActivity.start(activity, multipleSelect ? Integer.MAX_VALUE : 1, new VideoSelectActivity.OnVideoSelectListener() {
@Override
public void onSelected(List<VideoSelectActivity.VideoBean> data) {
Uri[] uri = new Uri[data.size()];
for (int i = 0; i < data.size(); i++) {
uri[i] = Uri.fromFile(new File(data.get(i).getVideoPath()));
}
callback.onReceiveValue(uri);
}
@Override
public void onCancel() {
callback.onReceiveValue(null);
}
});
return;
default:
break;
}
}
}
// 是否是多选模式
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, params.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multipleSelect);
activity.startActivityForResult(Intent.createChooser(intent, params.getTitle()), (resultCode, data) -> {
Uri[] uris = null;
if (resultCode == Activity.RESULT_OK && data != null) {

View File

@ -5,11 +5,12 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import com.hjq.demo.R;
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/AndroidProject
@ -23,9 +24,9 @@ public final class PasswordView extends View {
private final Paint mPointPaint;
/** 单个密码框的宽度 */
private int mItemWidth = 44;
private final int mItemWidth;
/** 单个密码框的高度 */
private int mItemHeight = 41;
private final int mItemHeight;
/** 中心黑点的半径大小 */
private static final int POINT_RADIUS = 15;
@ -57,8 +58,8 @@ public final class PasswordView extends View {
public PasswordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mItemWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mItemWidth, getResources().getDisplayMetrics());
mItemHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mItemHeight, getResources().getDisplayMetrics());
mItemWidth = (int) getResources().getDimension(R.dimen.dp_44);
mItemHeight = (int) getResources().getDimension(R.dimen.dp_41);
mPaint = new Paint();
// 设置抗锯齿

View File

@ -1,8 +1,8 @@
package com.hjq.demo.widget;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
@ -34,7 +34,9 @@ import androidx.lifecycle.LifecycleOwner;
import com.airbnb.lottie.LottieAnimationView;
import com.hjq.base.action.ActivityAction;
import com.hjq.demo.R;
import com.hjq.demo.ui.dialog.MessageDialog;
import com.hjq.widget.layout.SimpleLayout;
import com.hjq.widget.view.PlayButton;
import java.io.File;
import java.util.Formatter;
@ -52,7 +54,8 @@ public final class PlayerView extends SimpleLayout
View.OnClickListener, ActivityAction,
MediaPlayer.OnPreparedListener,
MediaPlayer.OnInfoListener,
MediaPlayer.OnCompletionListener {
MediaPlayer.OnCompletionListener,
MediaPlayer.OnErrorListener {
/** 刷新间隔 */
private static final int REFRESH_TIME = 1000;
@ -60,6 +63,8 @@ public final class PlayerView extends SimpleLayout
private static final int CONTROLLER_TIME = 3000;
/** 提示对话框隐藏间隔 */
private static final int DIALOG_TIME = 500;
/** 动画执行时间 */
private static final int ANIM_TIME = 500;
private final ViewGroup mTopLayout;
private final TextView mTitleView;
@ -71,17 +76,22 @@ public final class PlayerView extends SimpleLayout
private final SeekBar mProgressView;
private final VideoView mVideoView;
private final ImageView mControlView;
private final PlayButton mControlView;
private final ImageView mLockView;
private ViewGroup mMessageLayout;
private final LottieAnimationView mLottieView;
private final TextView mMessageView;
/** 视频宽度 */
private int mVideoWidth;
/** 视频高度 */
private int mVideoHeight;
/** 锁定面板 */
private boolean mLockMode;
/** 显示面板 */
private boolean mControllerShow = true;
private boolean mControllerShow = false;
/** 触摸按下的 X 坐标 */
private float mViewDownX;
@ -92,10 +102,11 @@ public final class PlayerView extends SimpleLayout
/** 当前播放进度 */
private int mCurrentProgress;
/** 返回监听器 */
private onPlayListener mListener;
@Nullable
private OnPlayListener mListener;
/** 音量管理器 */
private AudioManager mAudioManager;
private final AudioManager mAudioManager;
/** 最大音量值 */
private int mMaxVoice;
/** 当前音量值 */
@ -152,9 +163,9 @@ public final class PlayerView extends SimpleLayout
mVideoView.setOnPreparedListener(this);
mVideoView.setOnCompletionListener(this);
mVideoView.setOnInfoListener(this);
mVideoView.setOnErrorListener(this);
mAudioManager = ContextCompat.getSystemService(getContext(), AudioManager.class);
postDelayed(mHideControllerRunnable, CONTROLLER_TIME);
}
/**
@ -217,7 +228,7 @@ public final class PlayerView extends SimpleLayout
*/
public void start() {
mVideoView.start();
mControlView.setImageResource(R.drawable.video_play_pause_ic);
mControlView.play();
// 延迟隐藏控制面板
removeCallbacks(mHideControllerRunnable);
postDelayed(mHideControllerRunnable, CONTROLLER_TIME);
@ -228,7 +239,7 @@ public final class PlayerView extends SimpleLayout
*/
public void pause() {
mVideoView.pause();
mControlView.setImageResource(R.drawable.video_play_start_ic);
mControlView.pause();
// 延迟隐藏控制面板
removeCallbacks(mHideControllerRunnable);
postDelayed(mHideControllerRunnable, CONTROLLER_TIME);
@ -309,7 +320,7 @@ public final class PlayerView extends SimpleLayout
/**
* 设置返回监听
*/
public void setOnPlayListener(onPlayListener listener) {
public void setOnPlayListener(@Nullable OnPlayListener listener) {
mListener = listener;
mLeftView.setVisibility(mListener != null ? VISIBLE : INVISIBLE);
}
@ -323,16 +334,44 @@ public final class PlayerView extends SimpleLayout
}
mControllerShow = true;
ObjectAnimator.ofFloat(mTopLayout, "translationY", - mTopLayout.getHeight(), 0).start();
ObjectAnimator.ofFloat(mBottomLayout, "translationY", mBottomLayout.getHeight(), 0).start();
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(500);
animator.addUpdateListener(animation -> {
ValueAnimator topAnimator = ValueAnimator.ofInt(-mTopLayout.getHeight(), 0);
topAnimator.setDuration(ANIM_TIME);
topAnimator.addUpdateListener(animation -> {
int translationY = (int) animation.getAnimatedValue();
mTopLayout.setTranslationY(translationY);
if (translationY != -mTopLayout.getHeight()) {
return;
}
if (mTopLayout.getVisibility() == INVISIBLE) {
mTopLayout.setVisibility(VISIBLE);
}
});
topAnimator.start();
ValueAnimator bottomAnimator = ValueAnimator.ofInt(mBottomLayout.getHeight(), 0);
bottomAnimator.setDuration(ANIM_TIME);
bottomAnimator.addUpdateListener(animation -> {
int translationY = (int) animation.getAnimatedValue();
mBottomLayout.setTranslationY(translationY);
if (translationY != mBottomLayout.getHeight()) {
return;
}
if (mBottomLayout.getVisibility() == INVISIBLE) {
mBottomLayout.setVisibility(VISIBLE);
}
});
bottomAnimator.start();
ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0f, 1f);
alphaAnimator.setDuration(ANIM_TIME);
alphaAnimator.addUpdateListener(animation -> {
float alpha = (float) animation.getAnimatedValue();
mLockView.setAlpha(alpha);
mControlView.setAlpha(alpha);
if ((int) alpha != 1) {
if (alpha != 0) {
return;
}
@ -343,7 +382,7 @@ public final class PlayerView extends SimpleLayout
mControlView.setVisibility(VISIBLE);
}
});
animator.start();
alphaAnimator.start();
}
/**
@ -355,16 +394,44 @@ public final class PlayerView extends SimpleLayout
}
mControllerShow = false;
ObjectAnimator.ofFloat(mTopLayout, "translationY", 0, - mTopLayout.getHeight()).start();
ObjectAnimator.ofFloat(mBottomLayout, "translationY", 0, mBottomLayout.getHeight()).start();
ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
animator.setDuration(500);
animator.addUpdateListener(animation -> {
ValueAnimator topAnimator = ValueAnimator.ofInt(0, -mTopLayout.getHeight());
topAnimator.setDuration(ANIM_TIME);
topAnimator.addUpdateListener(animation -> {
int translationY = (int) animation.getAnimatedValue();
mTopLayout.setTranslationY(translationY);
if (translationY != -mTopLayout.getHeight()) {
return;
}
if (mTopLayout.getVisibility() == VISIBLE) {
mTopLayout.setVisibility(INVISIBLE);
}
});
topAnimator.start();
ValueAnimator bottomAnimator = ValueAnimator.ofInt(0, mBottomLayout.getHeight());
bottomAnimator.setDuration(ANIM_TIME);
bottomAnimator.addUpdateListener(animation -> {
int translationY = (int) animation.getAnimatedValue();
mBottomLayout.setTranslationY(translationY);
if (translationY != mBottomLayout.getHeight()) {
return;
}
if (mBottomLayout.getVisibility() == VISIBLE) {
mBottomLayout.setVisibility(INVISIBLE);
}
});
bottomAnimator.start();
ValueAnimator alphaAnimator = ValueAnimator.ofFloat(1f, 0f);
alphaAnimator.setDuration(ANIM_TIME);
alphaAnimator.addUpdateListener(animation -> {
float alpha = (float) animation.getAnimatedValue();
mLockView.setAlpha(alpha);
mControlView.setAlpha(alpha);
if (alpha != 0f) {
if (alpha != 0) {
return;
}
@ -375,7 +442,7 @@ public final class PlayerView extends SimpleLayout
mControlView.setVisibility(INVISIBLE);
}
});
animator.start();
alphaAnimator.start();
}
public void onResume() {
@ -397,54 +464,6 @@ public final class PlayerView extends SimpleLayout
removeAllViews();
}
/**
* {@link MediaPlayer.OnPreparedListener}
*/
@Override
public void onPrepared(MediaPlayer player) {
mPlayTime.setText(conversionTime(0));
mTotalTime.setText(conversionTime(player.getDuration()));
mProgressView.setMax(mVideoView.getDuration());
// 获取视频的宽高
int videoWidth = player.getVideoWidth();
int videoHeight = player.getVideoHeight();
// VideoView 的宽高
int viewWidth = getWidth();
int viewHeight = getHeight();
// 基于比例调整大小
if (videoWidth * viewHeight < viewWidth * videoHeight) {
// 视频宽度过大进行纠正
viewWidth = viewHeight * videoWidth / videoHeight;
} else if (videoWidth * viewHeight > viewWidth * videoHeight) {
// 视频高度过大进行纠正
viewHeight = viewWidth * videoHeight / videoWidth;
}
// 重新设置 VideoView 的宽高
ViewGroup.LayoutParams params = mVideoView.getLayoutParams();
params.width = viewWidth;
params.height = viewHeight;
mVideoView.setLayoutParams(params);
if (mListener != null) {
mListener.onPlayStart(this);
}
postDelayed(mRefreshRunnable, REFRESH_TIME / 2);
}
/**
* {@link MediaPlayer.OnCompletionListener}
*/
@Override
public void onCompletion(MediaPlayer player) {
pause();
if (mListener != null) {
mListener.onPlayEnd(this);
}
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
// 这里解释一下 onWindowVisibilityChanged 方法调用的时机
@ -452,10 +471,11 @@ public final class PlayerView extends SimpleLayout
// 从后台返回到前台先调用 onWindowVisibilityChanged(View.INVISIBLE) 后调用 onWindowVisibilityChanged(View.VISIBLE)
super.onWindowVisibilityChanged(visibility);
// 这里修复了 Activity 从后台返回到前台时 VideoView 从头开始播放的问题
if (visibility == VISIBLE) {
mVideoView.seekTo(mCurrentProgress);
mProgressView.setProgress(mCurrentProgress);
if (visibility != VISIBLE) {
return;
}
mVideoView.seekTo(mCurrentProgress);
mProgressView.setProgress(mCurrentProgress);
}
/**
@ -494,6 +514,59 @@ public final class PlayerView extends SimpleLayout
setProgress(seekBar.getProgress());
}
/**
* {@link MediaPlayer.OnPreparedListener}
*/
@Override
public void onPrepared(MediaPlayer player) {
mPlayTime.setText(conversionTime(0));
mTotalTime.setText(conversionTime(player.getDuration()));
mProgressView.setMax(mVideoView.getDuration());
// 获取视频的宽高
mVideoWidth = player.getVideoWidth();
mVideoHeight = player.getVideoHeight();
// VideoView 的宽高
int viewWidth = getWidth();
int viewHeight = getHeight();
// 基于比例调整大小
if (mVideoWidth * viewHeight < viewWidth * mVideoHeight) {
// 视频宽度过大进行纠正
viewWidth = viewHeight * mVideoWidth / mVideoHeight;
} else if (mVideoWidth * viewHeight > viewWidth * mVideoHeight) {
// 视频高度过大进行纠正
viewHeight = viewWidth * mVideoHeight / mVideoWidth;
}
// 重新设置 VideoView 的宽高
ViewGroup.LayoutParams params = mVideoView.getLayoutParams();
params.width = viewWidth;
params.height = viewHeight;
mVideoView.setLayoutParams(params);
post(mShowControllerRunnable);
postDelayed(mRefreshRunnable, REFRESH_TIME / 2);
if (mListener == null) {
return;
}
mListener.onPlayStart(this);
}
/**
* {@link MediaPlayer.OnCompletionListener}
*/
@Override
public void onCompletion(MediaPlayer player) {
pause();
if (mListener == null) {
return;
}
mListener.onPlayEnd(this);
}
/**
* {@link MediaPlayer.OnInfoListener}
*/
@ -519,6 +592,35 @@ public final class PlayerView extends SimpleLayout
return false;
}
/**
* {@link MediaPlayer.OnErrorListener}
*/
@Override
public boolean onError(MediaPlayer player, int what, int extra) {
Activity activity = getActivity();
if (activity == null) {
return false;
}
String message;
if (what == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
message = activity.getString(R.string.common_video_error_not_support);
} else {
message = activity.getString(R.string.common_video_error_unknown);
}
message += "\n" + String.format(activity.getString(R.string.common_video_error_supplement), what, extra);
new MessageDialog.Builder(getActivity())
.setMessage(message)
.setConfirm(R.string.common_confirm)
.setCancel(null)
.setCancelable(false)
.setListener(dialog -> onCompletion(player))
.show();
return true;
}
/**
* {@link View.OnClickListener}
*/
@ -526,27 +628,34 @@ public final class PlayerView extends SimpleLayout
@Override
public void onClick(View view) {
if (view == this) {
// 先移除之前发送的
removeCallbacks(mShowControllerRunnable);
removeCallbacks(mHideControllerRunnable);
if (mControllerShow) {
// 隐藏控制面板
post(mHideControllerRunnable);
} else {
// 显示控制面板
post(mShowControllerRunnable);
postDelayed(mHideControllerRunnable, CONTROLLER_TIME);
return;
}
return;
}
// 显示控制面板
post(mShowControllerRunnable);
postDelayed(mHideControllerRunnable, CONTROLLER_TIME);
if (view == mLeftView && mListener != null) {
} else if (view == mLeftView) {
if (mListener == null) {
return;
}
mListener.onClickBack(this);
return;
}
if (view == mControlView && mControlView.getVisibility() == VISIBLE) {
} else if (view == mControlView) {
if (mControlView.getVisibility() != VISIBLE) {
return;
}
if (isPlaying()) {
pause();
} else {
@ -560,24 +669,33 @@ public final class PlayerView extends SimpleLayout
post(mShowControllerRunnable);
}
postDelayed(mHideControllerRunnable, CONTROLLER_TIME);
if (mListener != null) {
mListener.onClickPlay(this);
if (mListener == null) {
return;
}
return;
}
mListener.onClickPlay(this);
} else if (view == mLockView) {
if (view == mLockView) {
if (mLockMode) {
unlock();
} else {
lock();
}
if (mListener != null) {
mListener.onClickLock(this);
if (mListener == null) {
return;
}
mListener.onClickLock(this);
}
}
public int getVideoWidth() {
return mVideoWidth;
}
public int getVideoHeight() {
return mVideoHeight;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
@ -596,7 +714,10 @@ public final class PlayerView extends SimpleLayout
// 如果当前亮度是默认的那么就获取系统当前的屏幕亮度
if (mCurrentBrightness == WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE) {
try {
mCurrentBrightness = Settings.System.getInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS) / 255f;
// 这里需要注意Settings.System.SCREEN_BRIGHTNESS 获取到的值在小米手机上面会超过 255
mCurrentBrightness = Math.min(Settings.System.getInt(
getContext().getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS), 255) / 255f;
} catch (Settings.SettingNotFoundException ignored) {
mCurrentBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_OFF;
}
@ -629,11 +750,12 @@ public final class PlayerView extends SimpleLayout
int progress = getProgress() + second * 1000;
if (progress >= 0 && progress <= getDuration()) {
mAdjustSecond = second;
mLottieView.setImageResource(mAdjustSecond < 0 ? R.drawable.video_schedule_rewind_ic : R.drawable.video_schedule_forward_ic);
mLottieView.setImageResource(mAdjustSecond < 0 ?
R.drawable.video_schedule_rewind_ic :
R.drawable.video_schedule_forward_ic);
mMessageView.setText(String.format("%s s", Math.abs(mAdjustSecond)));
post(mShowMessageRunnable);
}
break;
}
@ -749,10 +871,12 @@ public final class PlayerView extends SimpleLayout
mBottomLayout.setVisibility(GONE);
}
}
if (mListener != null) {
mListener.onPlayProgress(PlayerView.this);
}
postDelayed(this, REFRESH_TIME);
if (mListener == null) {
return;
}
mListener.onPlayProgress(PlayerView.this);
}
};
@ -812,7 +936,7 @@ public final class PlayerView extends SimpleLayout
/**
* 点击返回监听器
*/
public interface onPlayListener {
public interface OnPlayListener {
/**
* 点击了返回按钮可在此处处理返回事件

View File

@ -5,6 +5,7 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
@ -34,6 +35,10 @@ public final class StatusLayout extends FrameLayout {
private LottieAnimationView mLottieView;
/** 提示文本 */
private TextView mTextView;
/** 重试按钮 */
private TextView mRetryView;
/** 重试监听 */
private OnRetryListener mListener;
public StatusLayout(@NonNull Context context) {
this(context, null);
@ -44,15 +49,7 @@ public final class StatusLayout extends FrameLayout {
}
public StatusLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public StatusLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setClickable(true);
setFocusable(true);
setFocusableInTouchMode(true);
super(context, attrs, defStyleAttr);
}
/**
@ -68,6 +65,7 @@ public final class StatusLayout extends FrameLayout {
if (isShow()) {
return;
}
mRetryView.setVisibility(mListener == null ? View.INVISIBLE : View.VISIBLE);
// 显示布局
mMainLayout.setVisibility(VISIBLE);
}
@ -147,23 +145,53 @@ public final class StatusLayout extends FrameLayout {
mLottieView = mMainLayout.findViewById(R.id.iv_status_icon);
mTextView = mMainLayout.findViewById(R.id.iv_status_text);
mRetryView = mMainLayout.findViewById(R.id.iv_status_retry);
if (mMainLayout.getBackground() == null) {
// 默认使用 windowBackground 作为背景
TypedArray typedArray = getContext().obtainStyledAttributes(new int[]{android.R.attr.windowBackground});
mMainLayout.setBackground(typedArray.getDrawable(0));
mMainLayout.setClickable(true);
typedArray.recycle();
}
mRetryView.setOnClickListener(mClickWrapper);
addView(mMainLayout);
}
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
/**
* 设置重试监听器
*/
public void setOnRetryListener(OnRetryListener listener) {
mListener = listener;
if (isShow()) {
mMainLayout.setOnClickListener(l);
} else {
super.setOnClickListener(l);
mRetryView.setVisibility(mListener == null ? View.INVISIBLE : View.VISIBLE);
}
}
/**
* 点击事件包装类
*/
private final OnClickListener mClickWrapper = new OnClickListener() {
@Override
public void onClick(View v) {
if (mListener == null) {
return;
}
mListener.onRetry(StatusLayout.this);
}
};
/**
* 重试监听器
*/
public interface OnRetryListener {
/**
* 点击了重试
*/
void onRetry(StatusLayout layout);
}
}

Some files were not shown because too many files have changed in this diff Show More