悦单条目自定义及界面适配:
阐述:
距离上一次https://www.cnblogs.com/webor2006/p/13193142.html这块的学习已经相隔快半年了。。基本也把它快要遗忘在脑海里了,好在之前还有备忘可以复习一下,准备继续前行,不能放弃。上一次已经实现了首页这个TAB页面了,这次照理按顺序应该来实现MV这个TAB了:
![](https://i-blog.csdnimg.cn/blog_migrate/3fa0e12f5bf763a01a87748cd4a174d0.png)
但是这里从最后一个悦单Tab开始实现:
![](https://i-blog.csdnimg.cn/blog_migrate/cdfc4094c0f60bec9610bb031d17e4ef.png)
为什么呢?因为它里面长得跟首页Tab差不多,这里先来看一下它的预览效果:
![](https://i-blog.csdnimg.cn/blog_migrate/eb73865a8376c484202cd2ef116cd594.png)
由于比较简单,这里就快速来实现一下,顺便过一遍上一次实现的首页Tab当时构建的逻辑。
设置布局:
由于它的布局跟首页Tab是一模一样的,所以可以直接复用:
![](https://i-blog.csdnimg.cn/blog_migrate/aa9342b36731e65549d896f2c4ccc183.png)
但是很显然这样写不太好,fragment_home是HomeFragment的嘛,所以有必要改一个名称,改成通用一点的:
![](https://i-blog.csdnimg.cn/blog_migrate/73eedda3306a98b9efa7207bebd5011e.png)
准备列表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>
其预览效果长这样:
![](https://i-blog.csdnimg.cn/blog_migrate/5c1648b900d612c8ef5d6297662de141.png)
其中标红的是一个平常写布局的小小建议:为了能看到布局的效果不要直接将假的数据写到View当中了【比如TextView中的android:text】,而要使用tools标签来进行预览数据的模拟【比如TextView中的tools:text】,这样可以避免在线上可能会看到一些不想看到的假数据了。
准备YueDanItemView来加载item布局:
如之前首页Tab的实现,对于每个条目都会将其封装成一个View对象,避免在Adapter中出现有一堆设置数据的细节,增加代码可维护性,也是通常的技法,不多说:
![](https://i-blog.csdnimg.cn/blog_migrate/24c0383b522f50e204d326d31632a193.png)
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)
}
}
其中这里挪一下包:
![](https://i-blog.csdnimg.cn/blog_migrate/ded973e85b9a5c9b0e71a1c151264ff8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6709eb04034970bdaddd949d3850fca3.png)
这样看起来包体结构更合理一点。
准备Adpater:
它的写法跟HomeAdpater类似,先来回忆一下之前首页Tab的Adapter写法:
![](https://i-blog.csdnimg.cn/blog_migrate/a34a5499ba24f76a00e1b28a4fb54973.png)
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中先写死,先让其条目能正常显示出来既可:
![](https://i-blog.csdnimg.cn/blog_migrate/7b66d8fe0e93783bca81c3a70e035e1c.png)
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,看看初步效果:
![](https://i-blog.csdnimg.cn/blog_migrate/d55b25701911799c8f01169c55dc5a74.png)
运行:
![](https://i-blog.csdnimg.cn/blog_migrate/5b7ec4b127cba6c123cbced954e45d91.gif)
加载悦单列表数据刷新列表:
接下来则来绑定真实的列表数据,其代码编写还是按之前的MVP风格来。
定义V层:
![](https://i-blog.csdnimg.cn/blog_migrate/13433f47b5884d8294a43fb74ac4123f.png)
关于它里面方法的定义在之后再来完善,先建个空壳。
定义P层:
如之前首页Tab一样,也是需要定义一个接口,一个具体实现:
![](https://i-blog.csdnimg.cn/blog_migrate/8f8357d66efeaa7d15543939496482d2.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2c8cf67253500482c05743d082e9ed56.png)
里面先空实现,待之后再慢慢填充:
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网络请求的封装:
![](https://i-blog.csdnimg.cn/blog_migrate/fcbf5cbfebe63bae68fa8d4b5eb50468.png)
其中发送请求得借助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) {
}
所以对照着也建议一个悦单相关的:
![](https://i-blog.csdnimg.cn/blog_migrate/c15805d8c4e1160f8904dd81602a683b.png)
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一样,发起网络请求是这样写的:
![](https://i-blog.csdnimg.cn/blog_migrate/1f634e5f5ef1ad0917d4010323e5391c.png)
所以依葫芦画瓢:
![](https://i-blog.csdnimg.cn/blog_migrate/857d34b3cdba2478a4faca5fc3b3a944.png)
此时回到P层定义一下,先定义接口,其里面的方法跟HomeView的一模一样【注意:这块肯定未来要封装的,不然这种来回copy的方式太low了,也不太好维护】:
![](https://i-blog.csdnimg.cn/blog_migrate/6a9b04872a4511c904439e68ad916558.png)
copy到YueDanPresenter中:
![](https://i-blog.csdnimg.cn/blog_migrate/d1e2deaf488b2007f362e70120e005bb.png)
此时就可以回到具体的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大法:
![](https://i-blog.csdnimg.cn/blog_migrate/dd2d60ae11445dbe9b9b66b583b4c187.png)
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中的方法了:
![](https://i-blog.csdnimg.cn/blog_migrate/5331ce7607b3d65be89ebb068c51e3f0.png)
此时回到Fragment中就可以发起网络请求了:
![](https://i-blog.csdnimg.cn/blog_migrate/e8214c073173898155ab2094e48adfce.png)
处理数据加载成功刷新Adapter:
此时就需要给YuedanAdapter中增加刷新数据的方法,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/1c181ea5c2f3c73039def688ef20b207.png)
![](https://i-blog.csdnimg.cn/blog_migrate/63f3f802f843f78a016648230d066f84.png)
悦单条目view初始化:
有了数据之后,接下来则对列表条目进行数据初始化,直接贴代码:
![](https://i-blog.csdnimg.cn/blog_migrate/b3be19f5ca55ae00cc1c98b03207abc5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c08bf16f55b51ad72dba1b0edc61efb4.png)
运行看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/1a35f9f9c8fd484f0b0b1cc5ac0d8bb4.gif)
不过目前头像不是圆角的,所以接下来处理一下,关于Picasso的圆角处理可以参考大佬的文章https://blog.csdn.net/growing_tree/article/details/106618191,其实上跟Glide差不多,先添加依赖:
implementation 'jp.wasabeef:picasso-transformations:2.1.2'
![](https://i-blog.csdnimg.cn/blog_migrate/9d8565016a3b07068eee0cba8ef64ea1.png)
然后添加一个transform配置既可:
![](https://i-blog.csdnimg.cn/blog_migrate/7822351b6732bd9cc24951f68c20e285.png)
运行:
![](https://i-blog.csdnimg.cn/blog_migrate/16e6edd5c463512b13fa0f0d1fcc8644.png)
悦单界面下拉刷新和上拉加载更多:
下拉刷新:
这块也是跟首页Tab逻辑类似。
![](https://i-blog.csdnimg.cn/blog_migrate/26b7cd50d4e306f95d30132532eb9041.png)
运行看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/98ee3755248a388b8f69c3af89573f20.gif)
上拉加载更多:
这块跟首页Tab逻辑也一样~~
修改Adapter:
为了有一个分页loading的效果,所以对于整个列表项而言得多出一项,所以先来将getCount()加1:
![](https://i-blog.csdnimg.cn/blog_migrate/664bcd8fa578683aae168700fa0b9c41.png)
然后需要定义getItemType()了,如下:
override fun getItemViewType(position: Int): Int {
if (position == list.size) {
//最后一条,则显示加载更多
return 1
} else {
//普通条目
return 0
}
}
接下来对应的得来修改onCreateViewHolder()了,得根据列表类型绑不同的View:
![](https://i-blog.csdnimg.cn/blog_migrate/7c777606efa17a98129865269e67ea21.png)
最后再来修改一下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)
}
}
设置滑动监听:
![](https://i-blog.csdnimg.cn/blog_migrate/2795d5700d50f0cd8679f726c841c1cb.png)
这里有一个体现Kotlin比较人性的地方需要提示一下,就是它的智能类型转换,啥意思?
![](https://i-blog.csdnimg.cn/blog_migrate/b6008426b75442f061a358da2d3e0044.png)
YueDanPresenterImpl.loadMoreDatas()实现:
![](https://i-blog.csdnimg.cn/blog_migrate/e3bb673d65f72e0120e135fcea51334c.png)
接下来则回到Fragment中来处理加载更多:
![](https://i-blog.csdnimg.cn/blog_migrate/aa412135dc7093f486c58dff727de377.png)
然后在Adapter中来处理加载更多:
![](https://i-blog.csdnimg.cn/blog_migrate/fba2c8e6a5a5a508137f62cc7531e140.png)
下面来运行看一下:
![](https://i-blog.csdnimg.cn/blog_migrate/113ac8fc08e56f527c80c4b8bf256155.gif)
抽取BaseListFragment:
在上面的实现中,一直在啰嗦提醒一句话:“跟首页tab的功能差不多”, 如果在实际项目的开发中经常会看到跟某某界面的功能相似,对于有代码追求的开发者来说此时抽取Base就变得很有必要了,一是可以大大加快开发效率,二是也能避免大量copy造成的一些出错,最重要的一点是心情爽呀,谁愿意同样的功能重复性的写N遍呢?所以接下来咱们针对已经实现的首页和悦单Tab进行一个base提取,也为之后的MV的tab实现打好一个非常好的基础。
基类抽取思路:
抽取点:
接下来先来捋一下需要抽取的点,其实也就是看首页和悦单两者之间哪些是有共同性的,这里从2个层面来进行。
View:
- 列表显示
- 下拉刷新
- 上拉加载
data:
- 初始化数据
- 刷新数据
- 加载更多数据
抽取方法:
最笨的抽取方法就是先新建一个Base,然后将首页或者悦单的全面代码拷至里面,然后再一点点将共同的功能保留,不共同的则由具体子类来实现既可。
抽取view以及presenter和adapter的基类:
新建Base基类:
![](https://i-blog.csdnimg.cn/blog_migrate/74b0259a5052b28ae5943e14167a06d3.png)
copy具体页面的实现到base:
这里以HomeFragment为例将它的所有实现拷进来:
![](https://i-blog.csdnimg.cn/blog_migrate/1db77ebf60c5b39ed5d3ecbd22932958.png)
挼出不通用的点:
接下来要抽取的核心就是将不通用的点让子类来实现,其实咱们这页面比较简单很容易就挼出来了,而对于复杂页面可能需要花些时间的,不过都不难,下面先来挼一挼:
HomeView:
![](https://i-blog.csdnimg.cn/blog_migrate/15b55d6e44362cee2c7d97dbf070f826.png)
它应该是一个通用的才行,可以看一下HomeView里面定义的内容:
![](https://i-blog.csdnimg.cn/blog_migrate/1530b50431b101c0bd737a0090f5fc59.png)
其实抽取一个BaseView就可以了,这三个方法应该是每个列表页面都需要的,其中HomeBean就可以用一个泛型来定义。
HomeAdapter:
![](https://i-blog.csdnimg.cn/blog_migrate/0d56a3cb0f1327059236613de048e9ea.png)
而它里面也得将一些具体的类型给泛化掉:
![](https://i-blog.csdnimg.cn/blog_migrate/97e0e4f9c0820d56ddbe76aa785aed91.png)
HomePresenterImpl:
![](https://i-blog.csdnimg.cn/blog_migrate/c05c04d3c01692f181fc710f72684d4e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/eadf5a840c884692355829e0dd3d8989.png)
这些出现了具体的地方全得通用化,其实也就应该是要抽取对应的Base出来才行,其实总的来说是需要这样抽取:
![](https://i-blog.csdnimg.cn/blog_migrate/3cb9cf022374adc2a23a316db28163bb.png)
所以下面开始来抽取一下。
实现BaseListFragment的抽取:
将HomeView的内容抽取到BaseView中:
![](https://i-blog.csdnimg.cn/blog_migrate/7e8461ce7018d34268bebf3ff7272545.png)
但是呢,这里的HomeBean很显然得用泛型了,因为这里面是通用的行为:
![](https://i-blog.csdnimg.cn/blog_migrate/a521ba41bf9f830609dc123252a2f05e.png)
此时咱们的HomeView和YueDanView就可以继承至它了:
![](https://i-blog.csdnimg.cn/blog_migrate/d638f27588cd4e8572d201bf77d28e45.png)
![](https://i-blog.csdnimg.cn/blog_migrate/595d947db466a18e3c581b784d8736d0.png)
目前由于这俩tab模块的行为跟BaseView一样,所以继承之后里面就是空空的了,但是!!!未来如果有模块有新的行为则可以自行扩展。
将HomePresenter的内容提取到BaseListPresenter中:
![](https://i-blog.csdnimg.cn/blog_migrate/7b48195324b7a0d30c8001ec759f7833.png)
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)
}
其中还可以扩展一个方法,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/60a739ce9ec956e047fa213a087bd436.png)
关于它这块的具体实现之后再说,先扩展一下。此时就可以把HomePresenter和YueDanPresenter继承至它了:
![](https://i-blog.csdnimg.cn/blog_migrate/b864e071c1331a5ef793181842a73c73.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9c13034e57246b7db7a334ab4e8b5a7a.png)
修复具体Presenter的报错:
由于将原Presenter定义的都抽取到BaseListPresenter,所以对于HomePresenterImpl和YueDanPresenterImpl就会报错了,下面来解除一下:
HomePresenterImpl:
![](https://i-blog.csdnimg.cn/blog_migrate/cc160553b47cca0a82b1957505629884.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d565de2aad80e37a0a7f6a5b45e39bf6.png)
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:
![](https://i-blog.csdnimg.cn/blog_migrate/d1fae4072aba4e213f58b2232dfec1f5.png)
将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改为通用的:
![](https://i-blog.csdnimg.cn/blog_migrate/b27f6169ea508f3d47a1cf49ab9ebf80.png)
2、将HomeItemBean泛型化:
![](https://i-blog.csdnimg.cn/blog_migrate/44e95f58592804e9b8410ed240cbbc7a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/c2ddf1c9d9f0acfab8666aee914f652d.png)
3、 将HomeItemView泛型化:
![](https://i-blog.csdnimg.cn/blog_migrate/59e08cb9caecd9d3feefaeb5d7f3ba0f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e5e95bac756f651daf3c1ede7a97f48c.png)
4、将setData抽象化:
![](https://i-blog.csdnimg.cn/blog_migrate/9416e8872354630d049e1ce5d2c742ee.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6b09d4ba174eac2675040c49e742353a.png)
5、将onCreateViewHolder()中的创建HomeItemView抽象化:
![](https://i-blog.csdnimg.cn/blog_migrate/6cc5f7cb4604c05cbabb285ac9c76a88.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e65252c7b142e43f271e0f1241789692.png)
让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通用化:
![](https://i-blog.csdnimg.cn/blog_migrate/3323d5b1d8638316e25052505c4235b9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9948e114d3196040e148f71acdb9c095.png)
将loadSuccessed()、onLoadMore()处理数据回调泛型化:
![](https://i-blog.csdnimg.cn/blog_migrate/b939ccaa1097e82ac64bf55fc65dceec.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e2ed3926c0568a9a42aa364b25e564b6.png)
而每个数据中获取列表的方式可能是不一样的,所以交由子类来处理:
![](https://i-blog.csdnimg.cn/blog_migrate/061f52422cc708bdf31bb3ec5da7a2da.png)
将HomeAdapter通用化:
![](https://i-blog.csdnimg.cn/blog_migrate/2f0c6c6aa458385627d0ee6787e8ec16.png)
而它的创建也由子类来实现:
![](https://i-blog.csdnimg.cn/blog_migrate/7f6027a22a7a83cb636f1c5c037c3c3c.png)
将HomePresenterImpl通用化:
![](https://i-blog.csdnimg.cn/blog_migrate/8b70e07ab8eefadc6205ca6820010376.png)
同样的也是交由子类来实现:
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中的这块需要改一下:
![](https://i-blog.csdnimg.cn/blog_migrate/d30e6c2063841c262df7bbe17197ca51.png)
因为基类抽象化了嘛,同样的对于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
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/d81980bb56e039cabd8d82e408c1c6c9.png)
最后挂接destroy()方法:
最后还有一个小细节需要处理:
![](https://i-blog.csdnimg.cn/blog_migrate/56b0da9627c30d29c0855ca8613442b1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/61e34400662d93df8d95e86726cea1cd.png)
这块还没调用,虽说目前都是空实现,但是有可能在之后会在销毁做一些处理嘛,所以:
![](https://i-blog.csdnimg.cn/blog_migrate/319807c508ea63bfdaa32f21431b8cf1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2e7649abdaefda32f830147cc94d50e4.png)
至此整个抽取工作完成,之后再实现类似列表的页面就可以秒秒钟搞定了,而且还好维护~~最后温馨提示:任何重构都得细测,这是对软件的一种尊重~~不过这里仅是为了学习,能正常运行就成。