修改启动项LauncherActivity在AndroidManifest.xml中的注册配置,修改 theme 主题为 NoActionBar,因为我们仅把此对象作为启动入口项,不需要任何 UI。只在里面做一点逻辑业务而已,比如申请权限,跳转到业务activity等等。配置如下:
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
其中 action为View是 IDE 提示至少需要一个,关于 action intent 请点击查阅。
在 activities 这个包下面新建一个业务上的 Activity。在 android stuido 中右击 activities然后选择 New--Activity--Empty Activity,取名 LoginActivity。需要勾选Generate Layout File,Language为 Kotlin。兼容 AppCompact 是否勾选不是必需的,因为我们会让 LoginActivity继承CoreActivity,而这个BaseActivity 是兼容AppCompact 的。注册配置如下:
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/SwipeBackActivityTheme"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize">
其中,windowSoftInputMode配置键盘弹出策略,请参考《windowSoftInputMode属性解析》,configChanges配置 UI调整由哪几个事件触发,比如 orientation 屏幕旋转,且配置了此项后,activity 的生命周期不再经历 destroy再 re-new 一个新的实例而给程序员带来要恢复 UI 及数据等比较高深的工作。此时会触发如下回调,您可以 override它实现自己的需求:
/**
* 屏幕旋转等 UI 改变时不销毁对象实例,而是会回调此方法
* android:configChanges="keyboardHidden|orientation|screenSize"
*/
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
}
当然,如果你没有配置此项导致 activity在屏幕旋转等操作中被 destroy 又 re-new,此时也不必太担心,因为 CoreActivity 底层的 MvRx MVVM库已经做好了恢复数据状态(restore data state)及 UI 的工作(当然您没有用 mvrx的 data state 记录 数据仍然需要自己管理,否则也会消失)。这个需要您熟悉 android 组件的生命周期各个回调函数的知识才能处理得好,请查阅mvrx有关acitivity封装的源代码。另外配置的SwipeBackActivityTheme会在后面提及,用来处理启用swipeback带来的问题。
由于我们的架构是采用单 Activity+多个 Fragment 的形式(activity 尽量少,可以是多个),尽量只用Activity来承载 Fragments业务模块并大量复用 Fragments 来形成业务模块跳转流程,所以,在 LogInActivity的 xml的布局中我们基本上只需要配置一个FrameLayout就可以用来安顿 Fragment 的展示了。
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activities.LogInActivity">
android:id="@+id/mContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
id 配置为 mContainer,并且作为以下方法的返回值,是集成的 Fragmatation 库的方法中用到的值,多个 fragments栈管理用到的容器值:
override fun getContextViewId(): Int = R.id.mContainer
上面mContainer的类类型是我们封装的WindowInsetsFrameLayout,主要是为了解决fitsSystemWindows = true 无效的现象,而fitsSystemWindows = true是为了启用沉浸式状态栏,如果失效,我们只能在第一个加载的 Fragment 中看到 header 加宽,后续显示的Fragment 的 header 和 status bar 重叠。解决方案请参阅《Android Fragment 布局使用 fitsSystemWindows = true 无效解决方案》。android:fitsSystemWindows="true"这个配置项我们会在具体的 Fragment 的 xml 布局中用到。
在 Activity 上面打上 ARouter的路由注解如:
@Route(path = RouterPath.activity_login)
实现路由和组件类的绑定,您需要先脑补 ARouter 的相关知识,如编译期生成代码技术APT,此处只带领如何使用。在您后续编写 epoxy 需要的View 组件时,也需要先编译下才可使用,此处也用到了APT。
在一个 Activity 中加载首个 Fragment时,您都必须在第一次调用loadRootFragmentView方法加载第一个业务 Fragment,且要保证getContextViewId的返回值是一个正整数。
有关权限申请的部分,示例操作如下:
先在AndroidManifest.xml里面配置您的程序需要的权限列表(如上代码段),然后可采用以下代码申请并处理用户的选择操作:
/**
* 打开 app第一个 activity时要发起权限请求,常用在启动页
*/
private fun requestPermissions() {
//获取未经同意的权限列表,过滤掉已经经过用户同意的,无需用户再次确认
val unGrantedPerms =
PermissionUtils.getPermissions().filter { !PermissionUtils.isGranted(it) }.toTypedArray()
if (unGrantedPerms.isEmpty()) {
return
}
val launchPerms = RxPermissions(this)
.requestEach(
*unGrantedPerms
)
.bindToLifecycle(this)
.subscribe { permission ->
when {
permission.granted -> {
}
permission.shouldShowRequestPermissionRationale -> {
}
else -> {
}
}
}
}
此处逻辑是先过滤过用户已经授权的列表,留下未授权的再次请求权限。用到了 androidUtilCode库和 rxPermisson2库的操作,rxJava2流式代码风格以及 bindToLifeCycle 解决可能出现的内存泄露问题。rxPermisson2不是必须的,您可以采用androidUtilCode中有关权限申请的方法。
通过QMUI 的集成实现全局 theme 的修改。和解决集成 Swipeback 功能时的坑。修改 res/styles.xml 中原来的 AppTheme parent继承parent="QMUI.Compat.NoActionBar",一般可以照抄 QMUI 里的 demo 代码再自己修改以实现全局主题的替换。
。。。
。。。
true
在需要滑动退出的 Activity 中需要在 AndroidManifest.xml修改绑定的Theme 为上面的SwipeBackActivityTheme,其实是在 AppTheme 的基础上添加了背景透明android:windowIsTranslucent = true,不然滑动时背景黑屏(Fragmatation库在 Github的 ReadMe 中有说明)。
在创建的业务Fragment 中,继承 CoreFragment 后,在通过注解注册 Arouter 的 url 后,我们需要实例化recycleView和将其与 epoxyController 绑定才实现 date state 与 epoxy 的响应,另外如果当前 Fragment 需要实现 swipeback,还要关联attachToSwipeBack方法:
@Route(path = RouterPath.fragment_account_login)
class AccountLogInFragment : CoreFragment() {
private val viewModel by fragmentViewModel(AccountLogInViewModel::class)
// @JvmField
// @Autowired(name = MvRx.KEY_ARG)
// var params: Account? = null
private val params: Account by args()
override fun getContextViewId(): Int = R.layout.fragment_account_login
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_account_login, container, false).apply {
recycleViewInstance = findViewById(R.id.mAccountLoginRecycleView)
recycleViewInstance.setController(epoxyControllerInstance)
}
//配置当前fragment与滑动退出功能进行视图关联
return attachToSwipeBack(view)
}
}
值得注意的是,在 onCreateView 生命周期方法中,生成的view尚未返回给系统,故无法使用 kotlin 的 android 扩展功能直接把 id 当作成员变量操作,在 apply 方法中,我们仍然需要使用 findViewById 方法。 getContextViewId 为当前 fragment 的 xml RId,调用者传入的参数我们用mvRx 的 args()作为by 委托可提取出参数对象,关键点在于 key都为 MvRx.KEY_ARG.如果您要使用 ARouter 的参数自动注入的功能,类似于 spring的@Autowired注解,在 Kotlin类中还需要配上@JvmField。
如果你在 Fragment 中集成了 attachToSwipeBack(view)但是不想启用滑动退出,请调用setSwipeBackEnable(false),另外就是您得解决一些内存泄露的问题,比如:
RxToast.error(context?.applicationContext!!, it.message ?: "", Toast.LENGTH_SHORT, true).show()
context 采用applicationContext而不是当前 activity,主要是 Toast 在显示时如果已经退出了 activity,由于引用关系不会 release 此对象的实例内存,故产生 memory leak,基本在在代码里除了有关 layout 的,都可以使用applicationContext。
在 componets 中封装了一些基本的 loading UI,暂时基于 QMUI 的小组件,您可 replece这些公用的组件。