補一補前面偷懶的博客(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相關的就不涉及。我以爲首先應該編寫的是這一層,它用來規範咱們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🤣