补一补前面偷懒的博客(1/4)
只是我的总结的文章不当心被你找到啦~
若是感兴趣的话项目地址在文末java
MVP是一种设计模式(框架),由于其出色的解耦功能普遍地用于Android工程中,它将应用程序分为Model-View-Presenter,各司其职,简称MVPgit
一次简单的更新视图的基本流程 github
有的版本的MVP可能选择将数据处理放入Presenter中,而后model只有一个setter/getter的相似JavaBean的做用,可是我以为这样处理使得Presenter变得很臃肿,因此我选择将逻辑处理放入Model。两种方式均可以√设计模式
Contract并无什么很通用个框架,由于每一个视图和每个model的工做各不相同,这里给出的是上图中的的范例网络
class DetailContract{
interface DetailView{
fun onChangeText(String)
}
interface DetailModel {
fun getNowText(callBack: GetTextCallBack)
}
interface GetTextCallBack{
fun onSuccess(str:String)
fun onFail(info:String)
}
}
复制代码
Model也没有什么很通用的框架,这里给出的是上图中的范例并发
class SampleModel: DetailContract.DetailModel{
override getNowText(callBack: GetTextCallBack){
val str = ...
//以上是获取String的操做
if(str!=""){
callBack.onSuccess(str)
}else{
callBake.onFail("获取失败")
}
}
}
复制代码
这里的具体Model类实现了Contract契约类中的接口,方便咱们Presenter进行调用框架
View在Android中通常包括两种,一种是Activity,一种是Fragment,这里只给出Activity的封装,Fragment相似,须要处理一些生命周期的问题。异步
Activity:ide
abstract class BaseActivity<V,T:BasePresenter<V>>:Activity(){
val TAG:String = javaClass.simpleName
protected lateinit var mPresenter: T
lateinit var mContext: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mContext = this
//初始化Presenter
mPresenter = createPresenter()
//将Presenter和View绑定
mPresenter.attachView(this as V)
//初始化布局
initView(savedInstanceState)
}
/** * 应该由子类进行实现的初始化view的方法 */
abstract fun initView(savedInstanceState: Bundle?)
/** * 建立对应的Presenter */
abstract fun createPresenter():T
//解除绑定
override fun onDestroy() {
super.onDestroy()
mPresenter.detachView()
}
}
复制代码
BaseActivity是一个抽象类,全部加入MVP模式的Activity都应该继承这个抽象类。 泛型V表明的是视图(即本身),T则是对应的Presenter。View层持有对应Presenter的引用,用来发送消息。布局
abstract class BasePresenter<T> {
//View接口类型的弱引用,防止所持有的view已经被销毁,可是该presenter仍然持有,致使内存的泄露
protected lateinit var mViewRef:Reference<T>
//绑定View引用
fun attachView(view:T){
mViewRef = SoftReference<T>(view)
}
//获取当前绑定的View引用
protected fun getView(): T? {
return mViewRef.get()
}
//是否已绑定View
fun isViewAttached(): Boolean {
return mViewRef != null&&mViewRef.get()!=null
}
//解除引用
fun detachView(){
if (mViewRef != null){
mViewRef.clear()
}
}
}
复制代码
BasePresenter是一个抽象类,全部加入MVP模式的Presenter都应该继承该抽象类。 Presenter持有View层的一个弱引用,同时包括4个和弱引用有关的方法,分别是绑定View的引用,获取当前View的引用,断定是否已绑定了View,解除View的引用。
在具体的Presenter中还拥有一个对应Model对象。也就是Presenter同时持有View和Model,这样才能够作到信息的转发功能
传入的是Contract中的View接口类型是由于可使得Presenter只经过接口向view传输传输信息。而不是一个具体的类型。
以上就是一些经常使用的框架,下面咱们用实战来继续加深理解:
该范例选自红岩移动开发部的中期考核,内容为一个音乐App。仅仅分析播放页面(由于我就作了两个页面😭)
主页 | 播放页 |
---|---|
![]() |
![]() |
主要功能就是播放播放音乐以及歌词的滚动 咱们先来看看结构:
我以为首先应该编写的是这一层,它用来规范咱们View和Model的具体行为: DetailMusicContract:
class DetailMusicContract{
interface DetailView{
fun showDetailMusic(name:String,author:String,imageUrl:String)
fun showLyric(viewList:ArrayList<View>)
fun showToast(message:String)
fun changeLyricPosition(position:Int)
fun changeNowTimeTextView(time:String)
fun changeSeekBarPosition(position:Int)
}
interface DetailModel {
fun getNowMusic(callBack: GetNowMusicCallBack)
fun getLyric(context:Context,callBack: GetLyricCallBack)
}
interface GetNowMusicCallBack{
fun onSuccess(music: MyMusic)
fun onFail(info:String)
}
interface GetLyricCallBack{
fun onSuccess(viewList: ArrayList<View>)
fun onFail(info:String)
}
}
复制代码
在interface DetailView
中定义了6个方法
showDetailMuisc
用来显示当前歌曲的名字,做者,以及图片showLyric
用来显示歌词(初始化ViewPager和Adapter)changeLyricPosition
用来更改当前歌词的位置(即实现歌词轮播)changNowTimeTextView
用来更改当前歌曲的播放时间changeSeekBarPosition
用来更变滑动条的进度在interface DetailModel
中定义了两个方法
getNowMusic
用于从管理音乐播放的Service(服务)中获取当前播放的音乐getLyric
用于得到当前播放的放音乐的歌词Tips:不少状况下,Model的方法是后面才加的,觉得你可能一开始不知道Model须要哪些方法
BaseActivity以前已经展现过,实际上的BaseActivity会增长一些关于服务绑定的东西,不在本篇范畴以内
DetailMusicActivity:
class DetailMusicActivity : BaseActivity<DetailMusicContract.DetailView, DetailMusicPresenter>(),
DetailMusicContract.DetailView,
MyMusicPlayerManager.OnStartPlay,
MyMusicPlayerManager.StartNextMusic,
View.OnClickListener{
override fun initView(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_detail)
iv_detail_play.setOnClickListener(this)
iv_detail_previous.setOnClickListener(this)
iv_detail_next.setOnClickListener(this)
iv_detail_back.setOnClickListener(this)
//音乐准备完毕的回调
MyMusicPlayerManager.instance.setOnStartPlay(this)
MyMusicPlayerManager.instance.setStartNextMusic(this)
}
//实现绑定成功后的音乐数据
override fun onService(name: ComponentName?, service: IBinder?) {
Toast.makeText(this,"绑定成功",Toast.LENGTH_SHORT).show()
changeNowMusic()
}
override fun createPresenter(): DetailMusicPresenter {
return DetailMusicPresenter()
}
override fun showDetailMusic(name: String, author: String, imageUrl: String) {
tv_detail_name.text = name
tv_detail_author.text = author
ImageLoader.with(this)
.from(imageUrl)
.disposeWith(CutToCircle())
.cacheWith(DoubleCacheUtils.getInstance())
.into(iv_detail_music)
}
//改变音乐的时候必要操做,注意,这里能够进行一些歌词尚未获取可是已经能够进行的操做
override fun changeNowMusic() {
Log.d("刷新音乐","")
mPresenter.getNowMusic()
mPresenter.getLyric(this)
mPresenter.startToChangeTextView()
mPresenter.startToChangeSeekBar()
sb_detail.max = MyMusicPlayerManager.instance.musicDuration()
sb_detail.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
var isTouch = false
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (isTouch){
val position = seekBar!!.progress
MyMusicPlayerManager.instance.musicSeekTo(position)
mPresenter.pause()
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
isTouch = true
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
isTouch = false
MyMusicPlayerManager.instance.play()
}
})
}
//触发显示歌词的回调,注意,这里应该放只有获取到了歌词以后才能够作出的ui操做
override fun showLyric(viewList:ArrayList<View>) {
Log.d("歌词显示回调","成功")
runOnUiThread {
val adapter = MyViewPagerAdapter(viewList)
mb_lyric.init()
mb_lyric.setScrollTime(1500)
mb_lyric.setAdapter(adapter,this)
mb_lyric.setTransformer(CustomTransformer())
mPresenter.startToChangeLyric()
}
}
override fun onNextMusic() {
mPresenter.playNext()
}
override fun showToast(message:String) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
}
override fun changeLyricPosition(position: Int) {
runOnUiThread {
mb_lyric.changeTo(position)
}
}
override fun changeNowTimeTextView(time: String) {
runOnUiThread{
tv_detail_now.text = time
}
}
override fun changeSeekBarPosition(position: Int) {
runOnUiThread {
sb_detail.progress = position
}
}
//点击事件的集中处理
override fun onClick(v: View?) {
when{
v!!.id == iv_detail_play.id -> {
if (MyMusicPlayerManager.instance.isPlaying()){
mPresenter.pause()
}else{
mPresenter.play()
}
}
v.id == iv_detail_previous.id -> {
mPresenter.playPrevious()
}
v.id == iv_detail_next.id -> {
mPresenter.playNext()
}
v.id == iv_detail_back.id -> {
this.finish()
}
}
}
/** * 生命周期相关 */
override fun onDestroy() {
super.onDestroy()
mPresenter.cancelTimer()
}
}
复制代码
看起来代码可能有点长,缘由是这个页面相对来讲有点复杂,可是整个View的结构很清晰。
override fun initView
MyMusicPlayerManager.instance.setOnStartPlay(this) MyMusicPlayerManager.instance.setStartNextMusic(this)
这两个方法,因为音乐播放器须要从网络上异步加载音乐播放数据,因此须要设置一个音乐准备播放的回调接口以及播放完毕后切换到下一首的回调接口他们对应的方法为override fun changeNowMusic()
当前的音乐发生改变时(上一首下一首)的触发的回调 override fun onNextMusic()
当播放完毕后切换到下一首的回调接口override fun onService
changeNowMusic()
来进行音乐界面的初始化操做override fun createPresenter
override fun showDetailMusic
override fun changeNowMusic
MyMusicPlayerManager.OnStartPlay
接口中定义的方法,具体内容为显示当前正在播放的歌曲的全部信息,里面向Presenter发送了四个消息,getNowMusic
用于请求显示当前的音乐、getLyric
用于请求歌词、startToChangeTextView
用于请求开始不断更新当前播放时间、startToChangeSeekBar
用于请求开始不断更新SeekBar的进度。以后就是设置一些SeekBar的监听来实现对音乐进度的控制override fun showLyric
override fun onNextMusic
MyMusicPlayerManager.StartNextMusic
接口中的方法,上文已经说起,在音乐自动播放完后出发的回调,即播放下一首歌 接下来的一些方法就不用多说了,showToast、changeLyricPosition、changeNowTimeTextView、changeSeekBarPosition、还有集中处理的onClick控件点击监听View层总结
说了这么多,实际上View层的做用简而言之就是 输入 输出
根据用户的操做向Presenter发送请求、提供各式各样的接口来给Presenter和音乐服务进行回调
DetailMusicPresenter:
class DetailMusicPresenter : BasePresenter<DetailMusicContract.DetailView>(){
private var lyricTimer:Timer = Timer()
private var textViewTimer:Timer = Timer()
private var seekBarTimer:Timer = Timer()
private val detailMusicModel = DetailMusicModel()
//获取目前播放的音乐的回调
fun getNowMusic(){
detailMusicModel.getNowMusic(object :DetailMusicContract.GetNowMusicCallBack{
override fun onSuccess(music: MyMusic) {
mViewRef.get()!!.showDetailMusic(music.name,music.author,music.imageUrl)
}
override fun onFail(info: String) {
mViewRef.get()!!.showToast(info)
}
})
}
fun getLyric(context: Context){
detailMusicModel.getLyric(context,object :DetailMusicContract.GetLyricCallBack{
override fun onSuccess(viewList:ArrayList<View>) {
mViewRef.get()!!.showLyric(viewList)
}
override fun onFail(info: String) {
mViewRef.get()!!.showToast(info)
}
})
}
fun startToChangeLyric(){
lyricTimer = Timer()
lyricTimer.schedule(object : TimerTask() {
override fun run() {
mViewRef.get()!!.changeLyricPosition(MyMusicPlayerManager.instance.getNowLyricPosition())
}
}
,0,100)
}
fun startToChangeTextView(){
textViewTimer = Timer()
textViewTimer.schedule(object : TimerTask(){
override fun run() {
mViewRef.get()!!.changeNowTimeTextView(MyMusicPlayerManager.instance.nowTimeInMin())
}
},0,100)
}
fun startToChangeSeekBar(){
seekBarTimer = Timer()
seekBarTimer.schedule(object :TimerTask(){
override fun run() {
mViewRef.get()!!.changeSeekBarPosition(MyMusicPlayerManager.instance.musicCurrent())
}
},0,100)
}
//音乐控制
fun play(){
MyMusicPlayerManager.instance.play()
}
fun pause(){
MyMusicPlayerManager.instance.pause()
}
fun playPrevious(){
cancelTimer()
MyMusicPlayerManager.instance.playPrevious()
}
fun playNext(){
cancelTimer()
MyMusicPlayerManager.instance.playNext()
}
fun cancelTimer(){
lyricTimer.cancel()
textViewTimer.cancel()
seekBarTimer.cancel()
}
}
复制代码
由于Presenter的功能就是转发,因此代码不长,并且结构清晰
getNowMusic
showDetailMusic
方法来通知View层更新,若是失败那就调用以前也在Contract中定义好的showToast
方法来显示Toast信息提醒用户fun getLyric
fun startToChangeLyric
、fun startToChangeTextView
、fun startToChangeSeekBar
一样是在View层中定义的,实现方式几乎一致,开启一个新的TimerTask,定时获取当前歌词应该在的position、当前已经播放时间、当前SeekBar应该在的进度,而后回调View层对应的方法来更新fun cancelTimer
DetailMusic:
class DetailMusicModel: DetailMusicContract.DetailModel {
override fun getNowMusic(callBack: DetailMusicContract.GetNowMusicCallBack){
val music = MyMusicPlayerManager.instance.nowMusic()
callBack.onSuccess(music)
}
override fun getLyric(context:Context,callBack: DetailMusicContract.GetLyricCallBack) {
val music = MyMusicPlayerManager.instance.nowMusic()
val request = Request.Builder("http://elf.egos.hosigus.com/music/lyric?id=${music.id}")
.setMethod("GET").build()
NetUtil.getInstance().execute(request,object :Callback{
override fun onResponse(response: String?) {
val mainJson = JSONObject(response)
val str = mainJson.getJSONObject("lrc").getString("lyric")
val lyric = Lyric(str!!)
MyMusicPlayerManager.instance.nowMusic().lyric = lyric
val viewList = ArrayList<View>()
for (i in 0 until lyric.arrayList.size){
val view = LayoutInflater.from(context).inflate(R.layout.item_lyric,null)
view.findViewById<TextView>(R.id.tv_item_lyric).text=lyric.arrayList[i]
viewList.add(view)
}
callBack.onSuccess(viewList)
}
override fun onFailed(t: Throwable?) {
}
})
}
}
复制代码
Model类主要用来收集数据并提供给Presenter
override fun getNowMusic
override fun getLyric
本文只针对MVP的结构进行了分析,一些其余的内容,如音乐播放器,自定义歌词View等并无涉及,若是感兴趣的话能够访问GitHub源码地址
抱歉拉低了掘金的文章质量。。。 本人技术有限,仍然在学习当中,若是有什么不对的地方但愿大佬们指正!!! 写的初衷也只是来总结罢了,并无想过会有多少人看hhhhhh🤣