3、改造MVP--让数据改变来通知UI更新

原则:

  • 减少/不依赖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进行监听就好了,也不用去实现不需要的方法

 然而,对于异步线程的问题以及其他的问题仍需要进一步去解决

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值