• 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()方法:

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

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

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

  • 相关阅读:
    JSONObject的问题- 在用JSONObject传参到controller接收为空白和JSONArray添加json后转string不正确
    SpringContextHolder使用报错:applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder
    MQ报错Waiting for workers to finish.Stopping container from aborted consumer.Successfully waited for workers to finish.
    nacos的docker启动
    问题总结
    ubuntu docker中文乱码问题
    你该用HTTP2了
    Redis哨兵(Sentinel)模式快速入门
    Redis主从复制的原理
    Redis持久化的原理及优化
  • 原文地址:https://www.cnblogs.com/webor2006/p/13375555.html
Copyright © 2020-2023  润新知