原则:
- 减少/不依赖View
- 数据驱动UI,在数据变化时,UI自动进行更新
目的:
- View不与Presenter直接关联
- 确保数据在主线程更新UI
- 感知View的生命周期
- 在可见的生命周期范围内数据更新则更新UI,否则不更新UI
- View不可见时,暂停某些不必要的操作
这里不需要再在Presenter中注册View,也不需要在Presenter中回调View的方法了
测试代码:
逻辑层接口:用来执行播放状态,以及切换歌曲的方法
package com.example.jetpackbysob.datanotifyuiupdatetest
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 16:29
*/
interface IPlayerPresenter {
/**
* 根据状态进行播放或则暂停
*/
fun doPlayOrPause()
/**
* 播放上一首
*/
fun playPre()
/**
* 播放下一首
*/
fun playNext()
}
P层:PlayerPresenter
package com.example.jetpackbysob.datanotifyuiupdatetest
import com.example.jetpackbysob.datanotifyuiupdatetest.domain.MusicBean
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 11:14
*
* 这里测试改写通过数据改变通知UI
*
* 相关数据:
* 当前播放的数据
* 当前的播放状态
*
*/
class PlayerPresenter private constructor() : IPlayerPresenter {
companion object {
val instance by lazy {
PlayerPresenter()
}
}
// 播放的model对象
private val mPlayerModel by lazy {
PlayerModel()
}
// 音乐播放器,播放音乐
private val mMusicPlayer by lazy {
MusicPlayer()
}
// 播放状态的枚举类
enum class PlayState {
NONE, LOADING, PLAYING, PAUSE
}
// 让外部监听到mCurrentPlayState,和mCurrentMusic的变化
var mCurrentPlayState = DataListenerContainer<PlayState>()
var mCurrentMusic = DataListenerContainer<MusicBean>() // 当前播放的音乐
init {
mCurrentPlayState.value = PlayState.NONE
}
override fun doPlayOrPause() {
println("cfx ${mCurrentMusic.value}")
println("cfx ${mCurrentPlayState.value}")
if (mCurrentMusic.value == null) {
// 去获取一首歌曲
mCurrentMusic.value = mPlayerModel.getMusicById("卡农")
}
// 开始播放音乐
mMusicPlayer.play(mCurrentMusic.value)
if (mCurrentPlayState.value != PlayState.PLAYING) {
// 开始播放音乐
handlerPlayingState()
} else {
// 暂停播放
handlerPauseState()
}
}
// 处理通知UI进行暂停音乐
private fun handlerPauseState() {
println("cfx handlerPauseState ${mCurrentPlayState.value}")
mCurrentPlayState.value = PlayState.NONE
println("cfx handlerPauseState ${mCurrentPlayState.value}")
}
// 处理进行通知界面播放音乐
private fun handlerPlayingState() {
println("cfx handlerPlayingState ${mCurrentPlayState.value}")
mCurrentPlayState.value = PlayState.NONE
println("cfx handlerPlayingState ${mCurrentPlayState.value}")
}
/**
* 播放上一首
*/
override fun playPre() {
// 1.拿到上一首歌曲 --> 变更UI,包括通知标题和封面
mCurrentMusic.value = mPlayerModel.getMusicById("星空")
// 2.设置给播放器
// 3.等待播放的回调通知
mCurrentPlayState.value = PlayState.PLAYING
}
/**
* 播放下一首歌曲
*/
override fun playNext() {
// 1.拿到下一首歌曲 --> 变更UI,包括通知标题和封面
mCurrentMusic.value = mPlayerModel.getMusicById("心太乱")
// 2.设置给播放器
// 3.等待播放的回调通知
mCurrentPlayState.value = PlayState.PLAYING
}
}
M层:
用来播放音乐
package com.example.jetpackbysob.datanotifyuiupdatetest
import com.example.jetpackbysob.datanotifyuiupdatetest.domain.MusicBean
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 22:37
*
* 用来播放音乐
*/
class MusicPlayer {
// 播放音乐
fun play(mCurrentMusic: MusicBean?) {
}
}
获取音乐歌曲对象
package com.example.jetpackbysob.datanotifyuiupdatetest
import com.example.jetpackbysob.datanotifyuiupdatetest.domain.MusicBean
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 22:25
*/
class PlayerModel {
// 获取一首歌
fun getMusicById(id: String): MusicBean {
return MusicBean(
name = "歌曲名:$id",
cover = "歌曲封面:$id 封面",
url = "https://www.baidu.com"
)
}
}
音乐实体类
package com.example.jetpackbysob.datanotifyuiupdatetest.domain
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 22:17
*
* 歌曲的实体类对象
*/
data class MusicBean(
val name: String,
val cover: String,
val url: String
) {
}
V层:PlayerActivity、FlowPlayControllerActivity
package com.example.jetpackbysob.datanotifyuiupdatetest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import com.example.jetpackbysob.R
import kotlinx.android.synthetic.main.activity_player.*
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 16:22
*
* 这里测试改写通过数据改变通知UI
*/
class PlayerActivity : AppCompatActivity() {
private val mPlayerPresenter by lazy {
PlayerPresenter.instance
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
// 增加对数据的监听
initDataListener()
initListener()
}
/**
* 控件设置点击事件
*/
private fun initListener() {
play_or_pause.setOnClickListener {
// 调用presenter层进行播放或者暂停
mPlayerPresenter.doPlayOrPause()
}
// 播放上一首
play_pre.setOnClickListener {
mPlayerPresenter.playPre()
}
// 播放下一首
play_next.setOnClickListener {
mPlayerPresenter.playNext()
}
}
// 对数据进行监听
private fun initDataListener() {
// 监听当前的音乐数据变化
mPlayerPresenter.mCurrentMusic.addListener {
// 音乐内容发生变化了, 更新UI
play_title.text = it?.name
Toast.makeText(this, "封面变化了--> ${it?.cover}", Toast.LENGTH_SHORT).show()
}
// 监听当前的播放状态的数据变化
mPlayerPresenter.mCurrentPlayState.addListener {
// 播放状态发生变化了,更新UI
println("cfx------------ $it")
when (it) {
PlayerPresenter.PlayState.PLAYING -> {
play_or_pause.text = "暂停"
}
PlayerPresenter.PlayState.PAUSE -> {
play_or_pause.text = "播放"
}
}
}
}
override fun onDestroy() {
super.onDestroy()
}
}
package com.example.jetpackbysob.datanotifyuiupdatetest
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.jetpackbysob.R
import kotlinx.android.synthetic.main.activity_flow_play_controller.*
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 18:08
*
* 该activity表示虚浮窗,只用来控制歌曲播放/暂停
* 用来演示MVP架构的缺点
*/
class FlowPlayControllerActivity : AppCompatActivity(){
private val mPlayerPresenter by lazy {
PlayerPresenter.instance
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_flow_play_controller)
initListener()
initDataListener()
}
/**
* 创建对数据的监听
*/
private fun initDataListener() {
/**
* 这里因为是悬浮窗,只需要对播放状态的数据进行监听
* 对于当前的播放的音乐数据并不关系,所以不需要监听
* 因此,也不用像MVP架构中,没用到的方法也要去实现
* 对数据的监听,减少了代码的臃肿性
*/
mPlayerPresenter.mCurrentPlayState.addListener {
when(it) {
PlayerPresenter.PlayState.PLAYING -> {
play_or_pause.text = "暂停"
}
PlayerPresenter.PlayState.PAUSE -> {
play_or_pause.text = "播放"
}
}
}
}
private fun initListener() {
play_or_pause.setOnClickListener {
mPlayerPresenter.doPlayOrPause()
}
}
override fun onDestroy() {
super.onDestroy()
}
}
这里增加数据容器:DataListenerContainer,监听数据的变化
package com.example.jetpackbysob.datanotifyuiupdatetest
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 22:51
*
* 数据容器:监听数据的变化
* 这个类中添加对数据进行监听的功能
*/
class DataListenerContainer<T> {
private val mBlocks = arrayListOf<(T?) -> Unit>()
// 数据
var value: T? = null
/**
* 当设置数据时,即表明数据发生变化
* 就进行通知更新
*/
set(value: T?) {
mBlocks.forEach { it.invoke(value) }
}
// 将匿名函数添加到数据容器中去
fun addListener(block: (T?) -> Unit) {
if (!mBlocks.contains(block)) {
mBlocks.add(block)
}
}
}
在逻辑层中定义需要监听的数据,当数据的value值改变时,会调用DataListenerContainer的set方法,进而会遍历集合mBlocks.forEach { it.invoke(value)方法
而在View层只需要通过Presenter获取到对应的数据,例如mCurrentMusic、mCurrentPlayState,传入相应的匿名方法,然后进行监听回调即可,也不需要将view注册给Presenter,也不需要实现相应的接口方法,十分的便捷
对于前文中说到MVP架构中缺点之一,对于不需要的方法,也必须去实现,十分冗余,而采用数据通知UI的方法,对于悬浮窗功能中,只需要监听播放状态的数据,那么在activity中只对于mCurrentPlayState进行监听就好了,也不用去实现不需要的方法
然而,对于异步线程的问题以及其他的问题仍需要进一步去解决