文章目录
实现效果
目的就是方便的将多种格式的列表在一个adapter中使用
使用版本
- androidx扩展包下的recyclerview 1.2以上版本新增了一个MergeAdapter的api,
- An RecyclerView.Adapter implementation that presents the contents of multiple adapters in sequence. 按顺序显示多个适配器内容
- 样例
MyAdapter adapter1 = ...;
AnotherAdapter adapter2 = ...;
MergeAdapter merged = new MergeAdapter(adapter1, adapter2);
recyclerView.setAdapter(mergedAdapter);
- 我们在kotlin中来使用它,并且顺便在简单的MVVM架构中结合一下jetpack组件中databinding,livdata以及retrofit2.6和协程,相关依赖如下:(retrofit从2.6开始支持协程)
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha02'
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
implementation "com.squareup.okhttp3:okhttp:4.2.0"
implementation "com.squareup.retrofit2:retrofit:2.6.1"
implementation "com.squareup.okhttp3:logging-interceptor:4.2.0"
implementation "com.squareup.retrofit2:converter-gson:2.6.1"
RecyclerView.Adapter
- adapter的简单封装
package com.example.mergeadapterapp.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
/**
* @Author yangtianfu
* @CreateTime 2020/4/2 16:50
* @Describe recycleView统配adapter
*/
abstract class BaseAdapter<T>(var data: List<T> = listOf()):RecyclerView.Adapter<BaseViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder{
return BaseViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent?.context), viewType, parent, false))
}
override fun getItemCount(): Int {
return data.size
}
fun refreshData(newData: List<T>) {
this.data = newData
this.notifyDataSetChanged()
}
// 增加点击和长按事件
interface OnItemClickListener {
fun onItemClick(view: View, position: Int)
// fun onItemLongClick(view: View,position: Int): Boolean
}
}
package com.example.mergeadapterapp.adapter
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.RecyclerView
/**
* @Author yangtianfu
* @CreateTime 2020/4/2 16:56
* @Describe recycleView封装ViewHolder
*/
open class BaseViewHolder(var dataBinding: ViewDataBinding):RecyclerView.ViewHolder(dataBinding.root) {
}
- 我们搞两个不一样的adapter,分别有不同的item样式文件
- 第一个adapter
package com.example.mergeadapterapp.adapter
import androidx.databinding.ViewDataBinding
import com.example.mergeadapterapp.BR
/**
* @Author yangtianfu
* @CreateTime 2020/4/2 16:59
* @Describe recycleView通用adapter
*/
class MyAdapter<T> constructor(layout:Int) : BaseAdapter<T>() {
var layout:Int = layout
private lateinit var onItemClickListener: OnItemClickListener
fun setOnItemClickListener(listener: OnItemClickListener) {
this.onItemClickListener = listener
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
var binding : ViewDataBinding = holder.dataBinding
binding.setVariable(BR.myData,data.get(position))
holder.itemView.setOnClickListener {
onItemClickListener.onItemClick(holder.itemView,position)
}
}
override fun getItemViewType(position: Int): Int {
return this.layout
}
}
- 第二个adapter
package com.example.mergeadapterapp.adapter
import androidx.databinding.ViewDataBinding
import com.example.mergeadapterapp.BR
/**
* @Author yangtianfu
* @CreateTime 2020/4/2 16:59
* @Describe recycleView通用adapter
*/
class MyAdapter1<T> constructor(layout:Int) : BaseAdapter<T>() {
var layout:Int = layout
private lateinit var onItemClickListener: OnItemClickListener
fun setOnItemClickListener(listener: OnItemClickListener) {
this.onItemClickListener = listener
}
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
var binding : ViewDataBinding = holder.dataBinding
binding.setVariable(BR.myData1,data.get(position))
holder.itemView.setOnClickListener {
onItemClickListener.onItemClick(holder.itemView,position)
}
}
override fun getItemViewType(position: Int): Int {
return this.layout
}
}
获取本地数据
- ①通过kotlin中的顶层函数提供全局获取数据方法
package com.example.mergeadapterapp.data
import com.example.mergeadapterapp.domain.News
import com.example.mergeadapterapp.domain.TitleBean
import java.util.Random
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.TimeUnit
//顶层声明,私有当前文件可见,避免误调,也可以放全局常量
//同一个模块中全局可见
//顶层声明中的方法全局可用,可以直接调用
internal fun createNewsList() = mutableListOf<News>().apply {
for (i in 1..8) {
add(News("News $i", "第二个adapter的item内容"))
}
}
internal fun createTitleList() = mutableListOf<TitleBean>().apply {
for (i in 1..10) {
add(TitleBean("title $i", "第一个adapter的item内容"))
}
}
- UI层获取数据的方式:
// 将顶层函数返回的数据交给livedata
myViewModel.listTitleBean.value = createTitleList()
myViewModel.listNews.value = createNewsList()
- ②通过类似网络获取数据的方式在viewmodel中mock数据,举例如下
var listTitleBean = MutableLiveData<List<TitleBean>>()
var listNews = MutableLiveData<List<News>>()
//第一个adapter数据
fun getTitleData(){
var data = listOf(
TitleBean("TitleBean1","TitleBean1 第一个adapter"),
TitleBean("TitleBean2","TitleBean2 第一个adapter"),
TitleBean("TitleBean3","TitleBean3 第一个adapter"),
TitleBean("TitleBean4","TitleBean4 第一个adapter"),
TitleBean("TitleBean5","TitleBean5 第一个adapter"),
TitleBean("TitleBean6","TitleBean6 第一个adapter"),
TitleBean("TitleBean7","TitleBean7 第一个adapter")
)
//将数据交给livedata
listTitleBean.value = data
}
//第二个adapter数据
fun getNewsData(){
var data = listOf(
News("NewsBean1","NewsBean1 第二个adapter"),
News("NewsBean2","NewsBean2 第二个adapter"),
News("NewsBean3","NewsBean3 第二个adapter"),
News("NewsBean4","NewsBean4 第二个adapter"),
News("NewsBean5","NewsBean5 第二个adapter"),
News("NewsBean6","NewsBean6 第二个adapter"),
News("NewsBean7","NewsBean7 第二个adapter")
)
listNews.value = data
}
- 完整ViewModel:
package com.example.mergeadapterapp.vm
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.mergeadapterapp.domain.News
import com.example.mergeadapterapp.domain.TitleBean
import com.example.myapp.bean.Article
import com.example.myapp.bean.WBean
import com.wjx.android.wanandroidmvvm.base.https.ApiService
import com.wjx.android.wanandroidmvvm.base.https.RetrofitFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* @Author yangtianfu
* @CreateTime 2020/4/6 15:55
* @Describe vm获取并提供数据
*/
class MyViewModel:ViewModel() {
var listTitleBean = MutableLiveData<List<TitleBean>>()
var listNews = MutableLiveData<List<News>>()
//第一个adapter数据
fun getTitleData(){
var data = listOf(
TitleBean("TitleBean1","TitleBean1 第一个adapter"),
TitleBean("TitleBean2","TitleBean2 第一个adapter"),
TitleBean("TitleBean3","TitleBean3 第一个adapter"),
TitleBean("TitleBean4","TitleBean4 第一个adapter"),
TitleBean("TitleBean5","TitleBean5 第一个adapter"),
TitleBean("TitleBean6","TitleBean6 第一个adapter"),
TitleBean("TitleBean7","TitleBean7 第一个adapter")
)
listTitleBean.value = data
}
//第二个adapter数据
fun getNewsData(){
var data = listOf(
News("NewsBean1","NewsBean1 第二个adapter"),
News("NewsBean2","NewsBean2 第二个adapter"),
News("NewsBean3","NewsBean3 第二个adapter"),
News("NewsBean4","NewsBean4 第二个adapter"),
News("NewsBean5","NewsBean5 第二个adapter"),
News("NewsBean6","NewsBean6 第二个adapter"),
News("NewsBean7","NewsBean7 第二个adapter")
)
listNews.value = data
}
//网络数据
// vm持有数据层引用,并利用livedata赋值更新UI
private val _articleListData = MutableLiveData<List<Article>>()
//保证外部只能观察此数据,不同通过setValue修改 model调用articleListData拿到网络请求数据交个观察者,但是不能修改
val articleListData: LiveData<List<Article>> = _articleListData
private val _errorMsg = MutableLiveData<String?>()
val errorMsg: LiveData<String?> = _errorMsg
fun fetch(page:Int){
viewModelScope.launch {
var result = RetrofitFactory.instance.getService(ApiService::class.java).getArticleList(page)
//请求到的数据用livedata包裹
_articleListData.value = result.data.datas
}
}
}
recycleview合并显示多个adapter
- 通过MergeAdapter(mAdapter1,mAdapter2)将多个adapter交给recycleview顺序显示
private fun initRecyclerView() {
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
mAdapter1 = MyAdapter(R.layout.item_topics_header)
mAdapter2 = MyAdapter1(R.layout.item_news)
val mergeAdapter = MergeAdapter(mAdapter1,mAdapter2)
binding.recyclerView.adapter = mergeAdapter
binding.recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
mAdapter1?.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
showSnackbar("点击mAdapter1 - ${position}")
}
})
mAdapter2?.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
showSnackbar("点击mAdapter2 - ${position}")
}
})
myViewModel.listTitleBean.observe(this, Observer {
mAdapter1!!.refreshData(it)
mAdapter1?.notifyDataSetChanged()
})
myViewModel.listNews.observe(this, Observer {
mAdapter2!!.refreshData(it)
mAdapter2?.notifyDataSetChanged()
})
通过协程显示请求网络数据
- api定义
@GET("article/list/{page}/json")
suspend fun getArticleList(@Path("page") page: Int = 0): Result<PageEntity<Article>>
- viewmodel中调用api请求网络
//网络数据
// vm持有数据层引用,并利用livedata赋值更新UI
private val _articleListData = MutableLiveData<List<Article>>()
//保证外部只能观察此数据,不同通过setValue修改 model调用articleListData拿到网络请求数据交个观察者,但是不能修改
val articleListData: LiveData<List<Article>> = _articleListData
private val _errorMsg = MutableLiveData<String?>()
val errorMsg: LiveData<String?> = _errorMsg
fun fetch(page:Int){
viewModelScope.launch {
var result = RetrofitFactory.instance.getService(ApiService::class.java).getArticleList(page)
//请求到的数据用livedata包裹
_articleListData.value = result.data.datas
}
}
- 监听网络数据更新UI
- 数据和recycleview绑定这里就省略了,有兴趣可以点击阅读原文看demo
/**
* 监听网络请求数据
*/
private fun observerNetData() {
//观察文章列表数据
myViewModel.articleListData.observe(this, Observer { list ->
//articleListData 的值改变时触发此监听
Toast.makeText(this,"网络数据请求成功:${list.toString()}",Toast.LENGTH_SHORT).show()
})
}
- 效果图
- activity中调用
package com.example.mergeadapterapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.MergeAdapter
import com.example.mergeadapterapp.adapter.*
import com.example.mergeadapterapp.data.createNewsList
import com.example.mergeadapterapp.data.createTitleList
import com.example.mergeadapterapp.databinding.ActivityMainBinding
import com.example.mergeadapterapp.domain.News
import com.example.mergeadapterapp.domain.TitleBean
import com.example.mergeadapterapp.vm.MyViewModel
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*
/**
* @Author yangtianfu
* @CreateTime 2020/4/6 12:07
* @Describe
*/
class MainActivity : AppCompatActivity() {
lateinit var myViewModel: MyViewModel
private var mAdapter1 : MyAdapter<TitleBean>? =null
private var mAdapter2 : MyAdapter1<News>? =null
lateinit var binding:ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
// 本地数据获取方式一
// myViewModel.getTitleData()
// myViewModel.getNewsData()
// 本地数据获取方式二
myViewModel.listTitleBean.value = createTitleList()
myViewModel.listNews.value = createNewsList()
initRecyclerView()
// ViewModel获取网络数据 协程请求网络数据
myViewModel.fetch(1)
observerNetData()
}
/**
* 监听网络请求数据
*/
private fun observerNetData() {
//观察文章列表数据
myViewModel.articleListData.observe(this, Observer { list ->
//articleListData 的值改变时触发此监听
Toast.makeText(this,"网络数据请求成功:${list.toString()}",Toast.LENGTH_SHORT).show()
})
}
private fun initRecyclerView() {
val layoutManager = LinearLayoutManager(this)
binding.recyclerView.layoutManager = layoutManager
mAdapter1 = MyAdapter(R.layout.item_topics_header)
mAdapter2 = MyAdapter1(R.layout.item_news)
val mergeAdapter = MergeAdapter(mAdapter1,mAdapter2)
binding.recyclerView.adapter = mergeAdapter
binding.recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
mAdapter1?.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
showSnackbar("点击mAdapter1 - ${position}")
}
})
mAdapter2?.setOnItemClickListener(object : BaseAdapter.OnItemClickListener {
override fun onItemClick(view: View, position: Int) {
showSnackbar("点击mAdapter2 - ${position}")
}
})
myViewModel.listTitleBean.observe(this, Observer {
mAdapter1!!.refreshData(it)
mAdapter1?.notifyDataSetChanged()
})
myViewModel.listNews.observe(this, Observer {
mAdapter2!!.refreshData(it)
mAdapter2?.notifyDataSetChanged()
})
}
private fun showSnackbar(message: String) = Snackbar.make(window.decorView, message, Snackbar.LENGTH_SHORT).show()
}
retrofit封装类
package com.wjx.android.wanandroidmvvm.base.https
import android.util.Log
import com.google.gson.Gson
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.lang.StringBuilder
import java.util.concurrent.TimeUnit
/**
* @Author yangtianfu
* @CreateTime 2020/4/1 21:17
* @Describe retrofit封装类
*/
class RetrofitFactory private constructor() {
private val retrofit: Retrofit
init {
val gson = Gson().newBuilder()
.setLenient()
.serializeNulls()
.create()
retrofit = Retrofit.Builder()
.baseUrl("https://www.wanandroid.com/")
.client(initOkhttpClient())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
companion object {
val instance: RetrofitFactory by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
RetrofitFactory()
}
}
private fun initOkhttpClient(): OkHttpClient {
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.addInterceptor(initLogInterceptor())
.build()
return okHttpClient
}
/*
* 日志拦截器
* */
private fun initLogInterceptor(): HttpLoggingInterceptor {
val interceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
Log.i("Retrofit", message)
}
})
interceptor.level = HttpLoggingInterceptor.Level.BODY
return interceptor
}
/*
* 具体服务实例化
* */
fun <T> getService(service: Class<T>): T {
return retrofit.create(service)
}
}
- api接口
package com.wjx.android.wanandroidmvvm.base.https
import com.example.myapp.bean.Article
import com.example.myapp.bean.Result
import com.example.myapp.bean.BaseResp
import com.example.myapp.bean.PageEntity
import com.example.myapp.bean.WBean
import retrofit2.http.GET
import retrofit2.http.Path
/**
* @Author yangtianfu
* @CreateTime 2020/3/31 21:04
* @Describe retrofit 使用协程定义api
*/
interface ApiService {
/**
* 使用协程进行网络请求
*/
@GET("article/top/json/")
suspend fun getTopArticle(): BaseResp<List<WBean>>
@GET("article/list/{page}/json")
suspend fun getArticleList(@Path("page") page: Int = 0): Result<PageEntity<Article>>
}