zoukankan      html  css  js  c++  java
  • 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()方法:

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

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

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

  • 相关阅读:
    149. Max Points on a Line(js)
    148. Sort List(js)
    147. Insertion Sort List(js)
    146. LRU Cache(js)
    145. Binary Tree Postorder Traversal(js)
    144. Binary Tree Preorder Traversal(js)
    143. Reorder List(js)
    142. Linked List Cycle II(js)
    141. Linked List Cycle(js)
    140. Word Break II(js)
  • 原文地址:https://www.cnblogs.com/webor2006/p/13375555.html
Copyright © 2011-2022 走看看