RecycleView的MergeAdapter

163 阅读5分钟

文章目录

实现效果

在这里插入图片描述

目的就是方便的将多种格式的列表在一个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>>

}

GitHub地址