前言
小红点提示,可以说是现在app都会有的一个功能,比如微信的消息界面,微博的我的界面。一般来说,一个小红点不会单独存在,可能会和其他红点之间有所关联,构成红点系统。
红点系统可以看做是树形结构,红点系统设计遵循以下原则:
- 子节点上的红点变化,需要通知其父节点更新
- 父节点上红点数量,是其所有子节点红点数量之和
- 父节点上红点清除,要将其所有子节点红点清除(数量置为0)
之前我们小红点是这么处理的
当收到红点消息时,通过EventBus方式通知对应的红点view更新,同时需要发送event通知其父节点,父节点收到通知需要计算它所有子节点红点数量之和,然后再更新自身,如果该父节点还有父节点,需要再发送一个event......
可以看出,这是一个非常糟糕的设计,可以说毫无设计。这在红点数量少的时候,感觉不到问题,但是随着版本不断迭代,需要的红点越来越多,就会发现它是多么的糟糕。代码中存在大量的event来处理红点,收到一个红点消息,可能要发好几个event,代码逻辑混乱,增加删除红点复杂,如果不熟悉业务逻辑,很容易漏掉相关代码。
后来我是这么设计的
基于观察者模式,设计下面两个类
1.红点处理类
- 维护自身的key,父节点key,子节点key(如果有的话)
- 处理tcp推过来的消息,通知观察者更新视图,并通知父节点更新
- 清除红点消息,并通知父节点更新,同时清除子节点红点
2.红点中心类
- 注册红点处理类
- 注册观察者(向红点处理类注册观察者)
- 接收tcp发送的红点信息,然后分发给对应红点处理类
- 接收清除红点信息,然后分发给对应的红点处理类
这样设计的好处是,低耦合,代码逻辑清晰,增加删除红点方便,符合数据驱动设计思想,每次有红点消息变化时,只要更新对应的红点数据就可以了。
LiveData
后来接触了LiveData,觉得代码可以更简单一点,LiveData本身就是观察者模式,还有生命周期处理等其他好处,用它来处理红点应该会很合适。每个红点对应一个LiveData,如果该红点是一个父节点,则使用MediatorLiveData
官网上是这么描述的
MediatorLiveData
是LiveData
的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发MediatorLiveData
对象的观察者。
这样我们可以写一个全局的ViewModel,里面存放这些红点对应的LiveData,红点view所在的界面,observer这些LiveData,当收到红点消息时,直接LiveData.setValue就行,这样代码逻辑上会很简单。
写了一个小demo
ViewModel
class MainViewModel : ViewModel() {
val systemNum = MutableLiveData<Int>(0)
val friendNum = MutableLiveData<Int>(0)
val otherNum = MutableLiveData<Int>(0)
val totalNum = MediatorLiveData<Int>()
init {
totalNum.addSource(systemNum){
totalNum.value = systemNum.value!! + friendNum.value!! + otherNum.value!!
}
totalNum.addSource(friendNum){
totalNum.value = systemNum.value!! + friendNum.value!! + otherNum.value!!
}
totalNum.addSource(otherNum){
totalNum.value = systemNum.value!! + friendNum.value!! + otherNum.value!!
}
}
fun clear(){
systemNum.value = 0
friendNum.value = 0
otherNum.value = 0
}
}
Fragment
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private lateinit var viewModel: MainViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
viewModel.systemNum.observe(viewLifecycleOwner){
view.findViewById<TextView>(R.id.tv_message_system).text = it.toString()
}
viewModel.friendNum.observe(viewLifecycleOwner){
view.findViewById<TextView>(R.id.tv_message_friend).text = it.toString()
}
viewModel.otherNum.observe(viewLifecycleOwner){
view.findViewById<TextView>(R.id.tv_message_other).text = it.toString()
}
viewModel.totalNum.observe(viewLifecycleOwner){
view.findViewById<TextView>(R.id.tv_message_all).text = it.toString()
}
view.findViewById<Button>(R.id.bt_add_system).setOnClickListener {
//系统消息加
viewModel.systemNum.value = viewModel.systemNum.value!!+1
}
view.findViewById<Button>(R.id.bt_reduce_system).setOnClickListener {
//系统消息减
if (viewModel.systemNum.value!! > 0){
viewModel.systemNum.value = viewModel.systemNum.value!!-1
}
}
view.findViewById<Button>(R.id.bt_add_friend).setOnClickListener {
//好友消息加
viewModel.friendNum.value = viewModel.friendNum.value!!+1
}
view.findViewById<Button>(R.id.bt_reduce_friend).setOnClickListener {
//好友消息减
if (viewModel.friendNum.value!! > 0){
viewModel.friendNum.value = viewModel.friendNum.value!!-1
}
}
view.findViewById<Button>(R.id.bt_add_other).setOnClickListener {
//其他消息加
viewModel.otherNum.value = viewModel.otherNum.value!!+1
}
view.findViewById<Button>(R.id.bt_reduce_other).setOnClickListener {
//其他消息减
if (viewModel.otherNum.value!! > 0){
viewModel.otherNum.value = viewModel.otherNum.value!!-1
}
}
view.findViewById<Button>(R.id.bt_clear).setOnClickListener {
viewModel.clear()
}
}
这只是一个简单的demo,后期可以参照MediatorLiveData对liveData封装,MediatorLiveData内部维护一个LiveData列表,我们可以添加一个clear方法,清除其子节点红点数量,这样只要调用LiveData.clear即可以清除其所有子节点红点。