UI给了空数据页的图,但是下一个模块又想换style,某些特殊的界面又要特殊定制,好烦!
ToolBar的样式都差不多,但是具体到项目中又时时有所调整,碎片化十分严重,怎么办?
BaseActivity 和 BaseFragment 已经膨胀得快炸掉了,还要往里面加东西?
所以我们需要一种可以灵活配置的手段,进可以全局配置,统一使用,退可以具体到某个Activity,进行个性化定制,并且除了全局和Activity,我们还需要更细致的层级,比如位于全局和Activity之间的Module等等,它们之间遵循某个规律,高权限可以覆盖低权限,越细致的配置越要做更多的事情,而如果仅仅是使用全局配置,就要令其尽可能少做事情,权限越大,责任越大。
用interface做配置,Lifecycle做切面,ContentProvider做初始化
上述一系列的问题都可以用这套组合拳解决部分问题;
1、对于要配置的属性抽象成interface,给具体的页面实现,当然一些非必要配置的属性可以利用Java8/Kotlin做默认实现;
2、然后在Application.ActivityLifecycleCallbacks
和FragmentManager.FragmentLifecycleCallbacks
中获取到具体某个页面要配置的东西,做具体的配置,我们所说的多级配置的控制也是在这里去实现;
3、最后注册一个ContentProvider,在onCreate
中获取到application,然后把上面配置好的Application.ActivityLifecycleCallbacks
注册进去,而FragmentManager.FragmentLifecycleCallbacks
则是在Application.ActivityLifecycleCallbacks
的onCreate
中注册。
说完理论我们来举个例子吧,比如ToolBar:
1、我们配置三个接口,默认是ToolBarUI是三段式的:
/**
* 提供给单个Class做特殊配置,最大程度实现灵活配置
*/
interface IInitToolBar {
fun initToolBar(toolBar: IToolBar?)
}
/**
* 控制单个Class是否初始化的开关
*/
interface IIsInitToolBar {
fun initToolBar():Boolean
}
/**
* ToolBar的具体属性抽象
*/
interface IToolBar {
fun leftText(): Int? = null
fun rightText(): Int? = null
fun leftIcon(): Int? = null
fun rightIcon(): Int? = null
fun titleText(): Int? = null
/**
* 单位:像素
*/
fun drawablePadding():Int?=null
/**
* 返回true代表被消费
*/
fun onClickLeft(view: View) = false
/**
* 返回true代表被消费
*/
fun onClickRight(view: View) = false
/**
* 初始化并且隐藏
*/
fun hideToolBar() = false
}
2、提供具体的Application.ActivityLifecycleCallbacks
和FragmentManager.FragmentLifecycleCallbacks
实现:
class ToolBarActivityLifecycleCallbacksImpl :
DefaultActivityLifecycleCallbacks {
companion object {
private val handler = Handler(Looper.getMainLooper())
}
override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {
activity?.let {
handler.post {
/**
* 可能会用到toolBar,所以丢到队尾执行,确保initView完成
*/
ToolBarManager.initToolBar(it as? IToolBar)
}
}
(activity as? FragmentActivity)?.supportFragmentManager?.registerFragmentLifecycleCallbacks(
ToolBarFragmentLifecycleCallbacksImpl(),
true
)
}
}
class ToolBarFragmentLifecycleCallbacksImpl : FragmentManager.FragmentLifecycleCallbacks() {
companion object {
private val handler = Handler(Looper.getMainLooper())
}
override fun onFragmentActivityCreated(
fm: FragmentManager,
f: Fragment,
savedInstanceState: Bundle?
) {
handler.post {
/**
* 可能会用到toolBar,所以丢到队尾执行,确保initView完成
*/
ToolBarManager.initToolBar(f as? IToolBar)
}
}
}
3、用ContentProvider初始化可以把初始化工作交给lib实现,不需要在业务工程中手动注册:
class InitProvider : ContentProvider() {
override fun onCreate(): Boolean {
(context?.applicationContext as Application).apply {
registerActivityLifecycleCallbacks(ToolBarActivityLifecycleCallbacksImpl())
}
return true
}
...
三级控制+两级配置(级别越大,权限越高)
不难发现,上述配置的具体工作是交由ToolBarManager
来完成,文章标题所谓的多级配置也是在这里完成的,我们看一下具体实现:
override fun initToolBar(toolBar: IToolBar?) {
//一级控制,全局控制
var isInitToolBar = defaultIsToolBar
//二级控制,Class控制
if (toolBar is IIsInitToolBar) {
isInitToolBar = toolBar.initToolBar()
}
/**
* 三级控制,Object控制
*/
isInitToolBar = when (toolBar) {
is Activity -> {
toolBar.intent.getBooleanExtra(EXTRA_INIT_TOOLBAR, isInitToolBar).apply {
//用完就遗弃,避免脏数据
toolBar.intent.removeExtra(EXTRA_INIT_TOOLBAR)
}
}
is Fragment -> {
toolBar.arguments?.getBoolean(EXTRA_INIT_TOOLBAR, isInitToolBar)?.apply {
//用完就遗弃,避免脏数据
toolBar.arguments?.remove(EXTRA_INIT_TOOLBAR)
} ?: isInitToolBar
}
else -> {
isInitToolBar
}
}
if (isInitToolBar) {
if (toolBar is IInitToolBar) {
//二级控制,class控制
toolBar.initToolBar(toolBar)
} else {
//一级控制,全局
defaultInitToolBar.initToolBar(toolBar)
}
}
}
ToolBarManager
是个单例,defaultIsToolBar
就是可以全局更改的字段,它代表权限最低的全局开关;
IIsInitToolBar
只做一件事,那就是开关,它代表权限更高的Class开关;
通过intent
/arguments
来传递的是最高权限的Object开关,不过用完记得移除脏数据。
具体配置的时候,如果该页面的UI符合三段式UI,那么就沿用全局配置的defaultInitToolBar
,否则也可以具体界面实现IInitToolBar
,做特殊配置。
根据包名的Module级别
除了上述的3+2之外,我们也可以通过全局Map去缓存一些配置,可以用包名前缀做Key,从而实现按照不同的Module配置不同的样式,也可以通过注解的手段优化开发体验等等。
最后我也写了个demo,包括沉浸式配置、空数据页错误页配置、Loading配置、ToolBar配置,当然,目前还未完善,计划写完用在项目中,待项目完结再发个开源吧。