Kotlin项目实战之手机影音---悦单条目实现及BaseListFragment抽取

悦单条目自定义及界面适配:

阐述:

距离上一次https://www.cnblogs.com/webor2006/p/13193142.html这块的学习已经相隔快半年了。。基本也把它快要遗忘在脑海里了,好在之前还有备忘可以复习一下,准备继续前行,不能放弃。上一次已经实现了首页这个TAB页面了,这次照理按顺序应该来实现MV这个TAB了:

但是这里从最后一个悦单Tab开始实现:

为什么呢?因为它里面长得跟首页Tab差不多,这里先来看一下它的预览效果:

由于比较简单,这里就快速来实现一下,顺便过一遍上一次实现的首页Tab当时构建的逻辑。

设置布局:

由于它的布局跟首页Tab是一模一样的,所以可以直接复用:

但是很显然这样写不太好,fragment_home是HomeFragment的嘛,所以有必要改一个名称,改成通用一点的:

准备列表Item布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="5dp"
    app:cardElevation="5dp"
    app:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:padding="10dp">

        <ImageView
            android:id="@+id/img_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@mipmap/default_splash_bg"
            android:scaleType="centerCrop" />

        <ImageView
            android:id="@+id/img_author_image"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_alignParentBottom="true"
            android:layout_margin="10dp"
            tools:src="@color/colorAccent" />

        <TextView
            android:id="@+id/tv_publishtime"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="10dp"
            android:layout_toRightOf="@id/img_author_image"
            android:maxLines="1"
            android:textColor="#fff"
            android:textSize="20sp"
            tools:text="2020-12-20" />

        <TextView
            android:id="@+id/tv_author_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/tv_publishtime"
            android:layout_toRightOf="@id/img_author_image"
            android:maxLines="1"
            android:textColor="#fff"
            android:textSize="20sp"
            tools:text="张三" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/tv_author_name"
            android:layout_toRightOf="@id/img_author_image"
            android:maxLines="1"
            android:textColor="#fff"
            android:textSize="20sp"
            tools:text="test" />
    </RelativeLayout>
</androidx.cardview.widget.CardView>

其预览效果长这样:

其中标红的是一个平常写布局的小小建议:为了能看到布局的效果不要直接将假的数据写到View当中了【比如TextView中的android:text】,而要使用tools标签来进行预览数据的模拟【比如TextView中的tools:text】,这样可以避免在线上可能会看到一些不想看到的假数据了。

准备YueDanItemView来加载item布局:

如之前首页Tab的实现,对于每个条目都会将其封装成一个View对象,避免在Adapter中出现有一堆设置数据的细节,增加代码可维护性,也是通常的技法,不多说:

package com.kotlin.musicplayer.ui.widget

import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.RelativeLayout
import com.kotlin.musicplayer.R

/**
 * 悦单界面每个条目的自定义view
 */
class YueDanItemView : RelativeLayout {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    init {
        View.inflate(context, R.layout.item_yuedan, this)
    }
}

其中这里挪一下包:

这样看起来包体结构更合理一点。

准备Adpater:

它的写法跟HomeAdpater类似,先来回忆一下之前首页Tab的Adapter写法:

package com.kotlin.musicplayer.adapter

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.itheima.player.model.bean.HomeItemBean
import com.kotlin.musicplayer.widget.HomeItemView
import com.kotlin.musicplayer.widget.LoadMoreView

/**
 * 首页列表Adapter
 */
class HomeAdapter : RecyclerView.Adapter<HomeAdapter.HomeHolder>() {
    private var list = ArrayList<HomeItemBean>()

    fun setData(list: List<HomeItemBean>?) {
        list?.let {
            this.list.clear()
            this.list.addAll(it)
            notifyDataSetChanged()
        }
    }

    fun loadMoreData(list: List<HomeItemBean>?) {
        list?.let {
            this.list.addAll(it)
            notifyDataSetChanged()
        }
    }

    class HomeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeHolder {
        if (viewType == 1) {
            //最后一条
            return HomeHolder(LoadMoreView(parent?.context))
        } else {
            //普通条目
            return HomeHolder(HomeItemView(parent?.context))
        }
    }

    override fun getItemViewType(position: Int): Int {
        if (position == list.size) {
            //最后一条,则显示加载更多
            return 1
        } else {
            //普通条目
            return 0
        }
    }

    override fun getItemCount(): Int {
        return list.size + 1
    }

    override fun onBindViewHolder(holder: HomeHolder, position: Int) {
        //如果是最后一条 不需要刷新view
        if (position == list.size) return
        val data = list.get(position)
        val itemView = holder.itemView as HomeItemView
        itemView.setData(data)
    }
}

那不简单,“拷贝”再改吧改吧【注意:这个拷贝也足以看出代码的问题了,也就是逻辑相同但是实现还得重头来一次,这些就是之后代码重构的关键】,这里Adapter中先写死,先让其条目能正常显示出来既可:

package com.kotlin.musicplayer.adapter

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.kotlin.musicplayer.widget.YueDanItemView

/**
 * 悦单列表Adapter
 */
class YuedanAdapter : RecyclerView.Adapter<YuedanAdapter.YuedanHolder>() {

    class YuedanHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): YuedanHolder {
        return YuedanHolder(YueDanItemView(parent?.context))
    }

    override fun getItemCount(): Int {
        return 20
    }

    override fun onBindViewHolder(holder: YuedanHolder, position: Int) {
    }
}

绑定Adapter,看看初步效果:

运行:

加载悦单列表数据刷新列表:

接下来则来绑定真实的列表数据,其代码编写还是按之前的MVP风格来。

定义V层:

关于它里面方法的定义在之后再来完善,先建个空壳。

定义P层:

如之前首页Tab一样,也是需要定义一个接口,一个具体实现:

 

里面先空实现,待之后再慢慢填充:

package com.kotlin.musicplayer.presenter.`interface`

interface YueDanPresenter {
}
package com.kotlin.musicplayer.presenter.impl

import com.kotlin.musicplayer.presenter.`interface`.YueDanPresenter
import com.kotlin.musicplayer.view.YueDanView
import java.lang.ref.WeakReference

class YueDanPresenterImpl(val yueDanView: WeakReference<YueDanView>) : YueDanPresenter {

}

封装网络请求:

还是参照之前首页Tab网络请求的封装:

其中发送请求得借助NetManager这个管理类,而具体每个模块则需要定义一个具体的Request,如HomeRequest,这里先来回忆一下当时HomeRequest里面是如何写的:

package com.kotlin.musicplayer.net

import com.kotlin.musicplayer.model.HomeBean
import com.kotlin.musicplayer.utils.URLProviderUtils

class HomeRequest(type: Int, offset: Int, responseHandler: ResponseHandler<HomeBean>) :
    MRequest<HomeBean>(type, URLProviderUtils.getHomeUrl(offset, 5), responseHandler) {
}

所以对照着也建议一个悦单相关的:

 

YueDanBean.kt:

package com.kotlin.musicplayer.model

/**
 * 悦单列表bean
 */
data class YueDanBean(
        val songlist: List<YueDanItemBean>
)

data class YueDanItemBean(
        val albums_total: String,
        val aliasname: String,
        val area: String,
        val artist_id: String,
        val avatar_big: String,
        val avatar_middle: String,
        val avatar_mini: String,
        val avatar_s1000: String,
        val avatar_s180: String,
        val avatar_s500: String,
        val avatar_small: String,
        val birth: String,
        val bloodtype: String,
        val company: String,
        val constellation: String,
        val country: String,
        val del_status: String,
        val firstchar: String,
        val gender: String,
        val intro: String,
        val name: String,
        val piao_id: String,
        val songs_total: String,
        val source: String,
        val stature: String,
        val ting_uid: String,
        val translatename: String,
        val url: String,
        val weight: String,
        val title: String,
        val author: String,
        val pic_small: String,
        val album_500_500: String,
        val hot: String,
        val publishtime: String
)

YueDanRequest.kt:

package com.kotlin.musicplayer.net

import com.kotlin.musicplayer.model.YueDanBean
import com.kotlin.musicplayer.utils.URLProviderUtils


/**
 * 悦单界面网络请求request
 */
class YueDanRequest(type: Int, offset: Int, handler: ResponseHandler<YueDanBean>) :
    MRequest<YueDanBean>(type, URLProviderUtils.getYueDanUrl(offset, 20), handler) {
}

其中URLProviderUtils.getYueDanUrl()为:

    /**
     * 获取悦单列表的url
     */
    public static String getYueDanUrl(int offset, int size) {
        String url = "http://tingapi.ting.baidu.com/v1/restserver/ting?from=android&version=5.6.5.0&method=baidu.ting.artist.getSongList&format=json&order=2&tinguid=7988&artistid=7988"
                + "&offset=" + offset
                + "&limits=" + size;
        Log.i("Main_url", url);
        return url;
    }

【说明】:上面的URL是网上自己找的,说不定哪天就不能用了,可以根据需要上网自己搜源~~

YueDanFragment.initData()中发起网络请求:

如之前首页Tab一样,发起网络请求是这样写的:

所以依葫芦画瓢:

此时回到P层定义一下,先定义接口,其里面的方法跟HomeView的一模一样【注意:这块肯定未来要封装的,不然这种来回copy的方式太low了,也不太好维护】:

copy到YueDanPresenter中:

此时就可以回到具体的p类中来实现了:

package com.kotlin.musicplayer.presenter.impl

import com.kotlin.musicplayer.model.YueDanBean
import com.kotlin.musicplayer.net.ResponseHandler
import com.kotlin.musicplayer.net.YueDanRequest
import com.kotlin.musicplayer.presenter.`interface`.YueDanPresenter
import com.kotlin.musicplayer.ui.fragment.YueDanFragment
import java.lang.ref.WeakReference

class YueDanPresenterImpl(val yueDanView: WeakReference<YueDanFragment>) : YueDanPresenter,
    ResponseHandler<YueDanBean> {
    override fun loadDatas() {
        YueDanRequest(YueDanPresenter.TYPE_INIT_OR_REFRESH, 0, this).excute()
    }

    override fun loadMoreDatas(offset: Int) {
        TODO("Not yet implemented")
    }

    override fun onError(type: Int, msg: String?) {
        TODO("Not yet implemented")
    }

    override fun onSuccessed(type: Int, result: YueDanBean) {
        TODO("Not yet implemented")
    }

}

其中请求成功与失败的回调肯定得要调用V层的方法,但是呢目前咱们的YueDanView里面方法还没有定义,所以这里也来完善一下,完善方式来是校仿HomeView的copy大法:

package com.kotlin.musicplayer.view

import com.kotlin.musicplayer.model.YueDanBean

interface YueDanView {
    /**
     * 获取数据失败
     */
    fun onError(message: String?)

    /**
     * 初始化数据或者刷新数据成功
     */
    fun loadSuccessed(reponse: YueDanBean?)

    /**
     * 加载更多成功
     */
    fun loadMore(response: YueDanBean?)
}

此时就可以回到P层中请求回调中调用V中的方法了:

此时回到Fragment中就可以发起网络请求了:

处理数据加载成功刷新Adapter:

此时就需要给YuedanAdapter中增加刷新数据的方法,如下:

悦单条目view初始化:

有了数据之后,接下来则对列表条目进行数据初始化,直接贴代码:

运行看一下:

不过目前头像不是圆角的,所以接下来处理一下,关于Picasso的圆角处理可以参考大佬的文章https://blog.csdn.net/growing_tree/article/details/106618191,其实上跟Glide差不多,先添加依赖:

implementation 'jp.wasabeef:picasso-transformations:2.1.2'

然后添加一个transform配置既可:

运行:

悦单界面下拉刷新和上拉加载更多:

下拉刷新:

这块也是跟首页Tab逻辑类似。

运行看一下:

上拉加载更多:

这块跟首页Tab逻辑也一样~~

修改Adapter:

为了有一个分页loading的效果,所以对于整个列表项而言得多出一项,所以先来将getCount()加1:

然后需要定义getItemType()了,如下:

    override fun getItemViewType(position: Int): Int {
        if (position == list.size) {
            //最后一条,则显示加载更多
            return 1
        } else {
            //普通条目
            return 0
        }
    }

接下来对应的得来修改onCreateViewHolder()了,得根据列表类型绑不同的View:

最后再来修改一下onBindViewHolder():

package com.kotlin.musicplayer.adapter

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.kotlin.musicplayer.model.YueDanItemBean
import com.kotlin.musicplayer.widget.HomeItemView
import com.kotlin.musicplayer.widget.LoadMoreView
import com.kotlin.musicplayer.widget.YueDanItemView

/**
 * 悦单列表Adapter
 */
class YuedanAdapter : RecyclerView.Adapter<YuedanAdapter.YuedanHolder>() {

    private var list = ArrayList<YueDanItemBean>()

    fun setData(list: List<YueDanItemBean>?) {
        list?.let {
            this.list.clear()
            this.list.addAll(it)
            notifyDataSetChanged()
        }
    }

    class YuedanHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): YuedanHolder {
        if (viewType == 1) {
            //最后一条
            return YuedanHolder(LoadMoreView(parent?.context))
        } else {
            //普通条目
            return YuedanHolder(YueDanItemView(parent?.context))
        }
    }

    override fun getItemCount(): Int {
        return list.size + 1
    }

    override fun getItemViewType(position: Int): Int {
        if (position == list.size) {
            //最后一条,则显示加载更多
            return 1
        } else {
            //普通条目
            return 0
        }
    }

    override fun onBindViewHolder(holder: YuedanHolder, position: Int) {
        //如果是最后一条 不需要刷新view
        if (position == list.size) return
        val yueDanItemBean = list.get(position)
        val yueDanItemView = holder?.itemView as YueDanItemView
        yueDanItemView.setData(yueDanItemBean)
    }
}

设置滑动监听:

这里有一个体现Kotlin比较人性的地方需要提示一下,就是它的智能类型转换,啥意思?

YueDanPresenterImpl.loadMoreDatas()实现:

接下来则回到Fragment中来处理加载更多:

然后在Adapter中来处理加载更多:

下面来运行看一下:

抽取BaseListFragment:

在上面的实现中,一直在啰嗦提醒一句话:“跟首页tab的功能差不多”, 如果在实际项目的开发中经常会看到跟某某界面的功能相似,对于有代码追求的开发者来说此时抽取Base就变得很有必要了,一是可以大大加快开发效率,二是也能避免大量copy造成的一些出错,最重要的一点是心情爽呀,谁愿意同样的功能重复性的写N遍呢?所以接下来咱们针对已经实现的首页和悦单Tab进行一个base提取,也为之后的MV的tab实现打好一个非常好的基础。

基类抽取思路:

抽取点:

接下来先来捋一下需要抽取的点,其实也就是看首页和悦单两者之间哪些是有共同性的,这里从2个层面来进行。

View:

  • 列表显示
  • 下拉刷新
  • 上拉加载

data:

  • 初始化数据
  • 刷新数据
  • 加载更多数据

抽取方法:

最笨的抽取方法就是先新建一个Base,然后将首页或者悦单的全面代码拷至里面,然后再一点点将共同的功能保留,不共同的则由具体子类来实现既可。 

抽取view以及presenter和adapter的基类:

新建Base基类:

copy具体页面的实现到base:

这里以HomeFragment为例将它的所有实现拷进来:

挼出不通用的点:

接下来要抽取的核心就是将不通用的点让子类来实现,其实咱们这页面比较简单很容易就挼出来了,而对于复杂页面可能需要花些时间的,不过都不难,下面先来挼一挼:

HomeView:

它应该是一个通用的才行,可以看一下HomeView里面定义的内容:

其实抽取一个BaseView就可以了,这三个方法应该是每个列表页面都需要的,其中HomeBean就可以用一个泛型来定义。

HomeAdapter:

而它里面也得将一些具体的类型给泛化掉:

HomePresenterImpl:

这些出现了具体的地方全得通用化,其实也就应该是要抽取对应的Base出来才行,其实总的来说是需要这样抽取:

所以下面开始来抽取一下。

实现BaseListFragment的抽取:

将HomeView的内容抽取到BaseView中:

但是呢,这里的HomeBean很显然得用泛型了,因为这里面是通用的行为:

此时咱们的HomeView和YueDanView就可以继承至它了:

 

目前由于这俩tab模块的行为跟BaseView一样,所以继承之后里面就是空空的了,但是!!!未来如果有模块有新的行为则可以自行扩展。

将HomePresenter的内容提取到BaseListPresenter中:

package com.kotlin.musicplayer.base


/**
 * 所有下拉刷新和上拉加载更多列表界面presenter的基类
 */
interface BaseListPresenter {
    companion object {
        val TYPE_INIT_OR_REFRESH = 1
        val TYPE_LOAD_MORE = 2
    }

    fun loadDatas()
    fun loadMoreDatas(offset: Int)
}

其中还可以扩展一个方法,如下:

关于它这块的具体实现之后再说,先扩展一下。此时就可以把HomePresenter和YueDanPresenter继承至它了:

修复具体Presenter的报错:

由于将原Presenter定义的都抽取到BaseListPresenter,所以对于HomePresenterImpl和YueDanPresenterImpl就会报错了,下面来解除一下:

HomePresenterImpl:

YueDanPresenterImpl:

这个是同样的,直接贴出来:

package com.kotlin.musicplayer.presenter.impl

import com.kotlin.musicplayer.base.BaseListPresenter
import com.kotlin.musicplayer.model.YueDanBean
import com.kotlin.musicplayer.net.ResponseHandler
import com.kotlin.musicplayer.net.YueDanRequest
import com.kotlin.musicplayer.presenter.`interface`.YueDanPresenter
import com.kotlin.musicplayer.view.YueDanView
import java.lang.ref.WeakReference

class YueDanPresenterImpl(val yueDanView: WeakReference<YueDanView>) : YueDanPresenter,
    ResponseHandler<YueDanBean> {
    override fun loadDatas() {
        YueDanRequest(BaseListPresenter.TYPE_INIT_OR_REFRESH, 0, this).excute()
    }

    override fun loadMoreDatas(offset: Int) {
        YueDanRequest(BaseListPresenter.TYPE_LOAD_MORE, offset, this).excute()
    }

    override fun onError(type: Int, msg: String?) {
        yueDanView.get()?.onError(msg)
    }

    override fun onSuccessed(type: Int, result: YueDanBean) {
        when (type) {
            BaseListPresenter.TYPE_INIT_OR_REFRESH -> yueDanView.get()?.loadSuccessed(result)
            BaseListPresenter.TYPE_LOAD_MORE -> yueDanView.get()?.onLoadMore(result)
        }
    }

    override fun destoryView() {
        //TODO
    }

}

将HomeAdapter的内容提取到BaseListAdapter中:

新建BaseListAdapter:

将HomeAdapter的内容拷到BaseListAdapter当中:

package com.kotlin.musicplayer.base

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.kotlin.musicplayer.adapter.HomeAdapter
import com.kotlin.musicplayer.model.HomeItemBean
import com.kotlin.musicplayer.widget.HomeItemView
import com.kotlin.musicplayer.widget.LoadMoreView

/**
 * 所有下拉刷新和上拉加载更多列表界面adapter基类
 */
class BaseListAdapter : RecyclerView.Adapter<HomeAdapter.HomeHolder>() {
    private var list = ArrayList<HomeItemBean>()

    fun setData(list: List<HomeItemBean>?) {
        list?.let {
            this.list.clear()
            this.list.addAll(it)
            notifyDataSetChanged()
        }
    }

    fun loadMoreData(list: List<HomeItemBean>?) {
        list?.let {
            this.list.addAll(it)
            notifyDataSetChanged()
        }
    }

    class HomeHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HomeAdapter.HomeHolder {
        if (viewType == 1) {
            //最后一条
            return HomeAdapter.HomeHolder(LoadMoreView(parent?.context))
        } else {
            //普通条目
            return HomeAdapter.HomeHolder(HomeItemView(parent?.context))
        }
    }

    override fun getItemViewType(position: Int): Int {
        if (position == list.size) {
            //最后一条,则显示加载更多
            return 1
        } else {
            //普通条目
            return 0
        }
    }

    override fun getItemCount(): Int {
        return list.size + 1
    }

    override fun onBindViewHolder(holder: HomeAdapter.HomeHolder, position: Int) {
        //如果是最后一条 不需要刷新view
        if (position == list.size) return
        val data = list.get(position)
        val itemView = holder.itemView as HomeItemView
        itemView.setData(data)
    }
}

通用化改造:

1、将HomeHolder改为通用的:

2、将HomeItemBean泛型化:

3、 将HomeItemView泛型化:

4、将setData抽象化:

5、将onCreateViewHolder()中的创建HomeItemView抽象化:

让HomeAdapter、YuedanAdapter继承至BaseListAdapter:

有了抽象Adapter的封装之后,具体子类实现就超级清爽了,下面来改造一下:

package com.kotlin.musicplayer.adapter

import android.content.Context
import com.kotlin.musicplayer.base.BaseListAdapter
import com.kotlin.musicplayer.model.HomeItemBean
import com.kotlin.musicplayer.widget.HomeItemView

/**
 * 首页列表Adapter
 */
class HomeAdapter : BaseListAdapter<HomeItemBean, HomeItemView>() {
    override fun refreshItemView(itemView: HomeItemView, data: HomeItemBean) {
        itemView.setData(data)
    }

    override fun getItemView(context: Context?): HomeItemView {
        return HomeItemView(context)
    }

}
package com.kotlin.musicplayer.adapter

import android.content.Context
import com.kotlin.musicplayer.base.BaseListAdapter
import com.kotlin.musicplayer.model.YueDanItemBean
import com.kotlin.musicplayer.widget.YueDanItemView

/**
 * 悦单列表Adapter
 */
class YuedanAdapter : BaseListAdapter<YueDanItemBean, YueDanItemView>() {
    override fun refreshItemView(itemView: YueDanItemView, data: YueDanItemBean) {
        itemView.setData(data)
    }

    override fun getItemView(context: Context?): YueDanItemView {
        return YueDanItemView(context)
    }

}

BaseListFragment通用化改造:

接下来则可以回头来改造咱们之前所创建的BaseListFragment。

将HomeView通用化:

将loadSuccessed()、onLoadMore()处理数据回调泛型化:

 

而每个数据中获取列表的方式可能是不一样的,所以交由子类来处理:

将HomeAdapter通用化:

而它的创建也由子类来实现:

将HomePresenterImpl通用化:

同样的也是交由子类来实现:

package com.kotlin.musicplayer.base

import android.graphics.Color
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.kotlin.musicplayer.R
import kotlinx.android.synthetic.main.fragment_list.*

/**
 * 所有具有下拉刷新和上拉加载更多列表界面的基类
 * HomeView->BaseView
 * Presenter->BaseListPresenter
 * Adapter->BaseListAdapter
 */
abstract class BaseListFragment<RESPONSE, ITEMBEAN, ITEMVIEW : View> : BaseFragment(),
    BaseView<RESPONSE> {

    val adapter by lazy { getSpecialAdapter() }
    val homePresenterImpl by lazy { getSpecialPresenter() }

    override fun initView(): View? {
        return View.inflate(context, R.layout.fragment_list, null)
    }

    override fun initListeners() {
        super.initListeners()
        recyclerView.layoutManager = LinearLayoutManager(context)
        recyclerView.adapter = adapter
        //监听列表滑动
        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    //是否最后一条已经显示
                    val layoutManager = recyclerView.layoutManager
                    if (layoutManager is LinearLayoutManager) {
                        //由于RecycleView还有其它样式的列表,所以这里只有下拉列表类型才处理分页
                        val manager: LinearLayoutManager = layoutManager
                        val lastPosition = manager.findLastVisibleItemPosition()
                        if (lastPosition == adapter.itemCount - 1) {
                            //开始加载更多数据
                            homePresenterImpl.loadMoreDatas(adapter.itemCount - 1);
                        }
                    }
                }
            }
        })
        lay_refresh.setColorSchemeColors(Color.RED, Color.YELLOW, Color.BLUE)
        lay_refresh.setOnRefreshListener {
            homePresenterImpl.loadDatas()
        }
    }

    override fun initData() {
        super.initData()
        homePresenterImpl.loadDatas()
    }

    override fun onError(message: String?) {
        showToast("获取数据出错")
        lay_refresh.isRefreshing = false
    }

    override fun loadSuccessed(response: RESPONSE?) {
        lay_refresh.isRefreshing = false
        adapter.setData(getList(response))
    }

    override fun onLoadMore(response: RESPONSE?) {
        lay_refresh.isRefreshing = false
        adapter.loadMoreData(getList(response))
    }

    /**
     * 获取适配器adapter
     */
    abstract fun getSpecialAdapter(): BaseListAdapter<ITEMBEAN, ITEMVIEW>

    /**
     * 获取presenter
     */
    abstract fun getSpecialPresenter(): BaseListPresenter

    /**
     * 从返回结果中获取列表数据集合
     */
    abstract fun getList(response: RESPONSE?): List<ITEMBEAN>?
}

至此整个BaseListFragment就已经抽取完了。

让HomeFragemnt、YueDanAdapter继承至BaseListFragment:

同样是有了抽象之后,子类的实现就变得异常的舒适简单了。

package com.kotlin.musicplayer.ui.fragment

import com.kotlin.musicplayer.adapter.HomeAdapter
import com.kotlin.musicplayer.base.BaseListAdapter
import com.kotlin.musicplayer.base.BaseListFragment
import com.kotlin.musicplayer.base.BaseListPresenter
import com.kotlin.musicplayer.model.HomeBean
import com.kotlin.musicplayer.model.HomeItemBean
import com.kotlin.musicplayer.presenter.impl.HomePresenterImpl
import com.kotlin.musicplayer.widget.HomeItemView
import java.lang.ref.WeakReference

/**
 * 首页
 */
class HomeFragment : BaseListFragment<HomeBean, HomeItemBean, HomeItemView>() {
    override fun getSpecialAdapter(): BaseListAdapter<HomeItemBean, HomeItemView> {
        return HomeAdapter()
    }

    override fun getSpecialPresenter(): BaseListPresenter {
        return HomePresenterImpl(WeakReference(this))
    }

    override fun getList(response: HomeBean?): List<HomeItemBean>? {
        return response?.songlist
    }
}

其中HomePresenterImpl中的这块需要改一下:

因为基类抽象化了嘛,同样的对于YueDanFragment也一样:

package com.kotlin.musicplayer.ui.fragment

import com.kotlin.musicplayer.adapter.YuedanAdapter
import com.kotlin.musicplayer.base.BaseListAdapter
import com.kotlin.musicplayer.base.BaseListFragment
import com.kotlin.musicplayer.base.BaseListPresenter
import com.kotlin.musicplayer.model.YueDanBean
import com.kotlin.musicplayer.model.YueDanItemBean
import com.kotlin.musicplayer.presenter.impl.YueDanPresenterImpl
import com.kotlin.musicplayer.widget.YueDanItemView
import java.lang.ref.WeakReference

/**
 * 悦单
 */
class YueDanFragment : BaseListFragment<YueDanBean, YueDanItemBean, YueDanItemView>() {
    override fun getSpecialAdapter(): BaseListAdapter<YueDanItemBean, YueDanItemView> {
        return YuedanAdapter()
    }

    override fun getSpecialPresenter(): BaseListPresenter {
        return YueDanPresenterImpl(WeakReference(this))
    }

    override fun getList(response: YueDanBean?): List<YueDanItemBean>? {
        return response?.songlist
    }
}

最后挂接destroy()方法:

最后还有一个小细节需要处理:

这块还没调用,虽说目前都是空实现,但是有可能在之后会在销毁做一些处理嘛,所以:

至此整个抽取工作完成,之后再实现类似列表的页面就可以秒秒钟搞定了,而且还好维护~~最后温馨提示:任何重构都得细测,这是对软件的一种尊重~~不过这里仅是为了学习,能正常运行就成。

原文地址:https://www.cnblogs.com/webor2006/p/13375555.html