The article directories

    • Implementation effect
    • Use version
    • RecyclerView.Adapter
    • Obtaining local data
    • Recycleview merge display multiple adapters
    • Display request network data through coroutines
    • Retrofit wrapper class

Implementation effect

The goal is to make it easy to use lists in multiple formats in one Adapter

Use version

  • A MergeAdapter API has been added to recyclerView 1.2 and above.
  • An RecyclerView.Adapter implementation that presents the contents of multiple adapters in sequence. Displays the contents of multiple adapters in sequence
  • The sample
MyAdapter adapter1 = ... ; AnotherAdapter adapter2 = ... ; MergeAdapter merged = new MergeAdapter(adapter1, adapter2); recyclerView.setAdapter(mergedAdapter);Copy the code
  • We use it in kotlin, and incidentally combine the jetpack component databinding,livdata, and retrofit2.6 and coroutines in a simple MVVM architecture, with the following dependencies :(retrofit supports coroutines since 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." "Copy the code

RecyclerView.Adapter

  • Simple encapsulation of 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 BaseAdapter */ 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: The List < T >) {this. Data = newData enclosing notifyDataSetChanged ()} / / increase the click and long press event interface OnItemClickListener {fun onItemClick(view: View, position: Int) // fun onItemLongClick(view: View,position: Int): Boolean } }Copy the code
package com.example.mergeadapterapp.adapter import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.RecyclerView /** * @Author yangtianfu * @CreateTime 2020/4/2 16:56 * @Describe RecycleView/Open class BaseViewHolder(var dataBinding: ViewDataBinding):RecyclerView.ViewHolder(dataBinding.root) { }Copy the code
  • We have two different Adapters, each with a different Item style file
  • The first adapter
package com.example.mergeadapterapp.adapter import androidx.databinding.ViewDataBinding import Com. Example. Mergeadapterapp. BR / * * * @ Author yangtianfu * @ CreateTime 2020/4/2 for * @ the Describe recycleView universal 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 } }Copy the code
  • The second adapter
package com.example.mergeadapterapp.adapter import androidx.databinding.ViewDataBinding import Com. Example. Mergeadapterapp. BR / * * * @ Author yangtianfu * @ CreateTime 2020/4/2 for * @ the Describe recycleView universal 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 } }Copy the code

Obtaining local data

  • ① Provide a global method to get data through the top-level function in 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 the Java. Util. Concurrent. TimeUnit statement / / top, private current file can be seen, avoid misadjustment, also can put the global constants global visible / / / / the same module in statements of top method globally available, Internal fun createNewsList() = mutableListOf<News>(). Apply {for (I in 1.. 8) { add(News("News $i", }} internal fun createTitleList() = mutableListOf<TitleBean>().apply {for (I in 1.. 10) {add(TitleBean("title $I ", "first adapter item ")}}Copy the code
  • How the UI layer gets data:
/ / the top-level () function returns the data to the livedata myViewModel. ListTitleBean. Value = createTitleList () myViewModel. ListNews. Value = createNewsList()Copy the code
  • ② Mock data in the viewModel in a net-like way, as shown in the following example
Var listTitleBean = MutableLiveData<List<TitleBean>>() var listNews = MutableLiveData<List<News>>( GetTitleData (){var data = listOf(TitleBean("TitleBean1","TitleBean1 first adapter"), TitleBean("TitleBean2","TitleBean2 first Adapter "), TitleBean("TitleBean3","TitleBean3 first adapter"), TitleBean("TitleBean4","TitleBean4 first Adapter "), TitleBean("TitleBean5","TitleBean5 first Adapter "), TitleBean("TitleBean6","TitleBean6 first adapter"), TitleBean("TitleBean7","TitleBean7 first Adapter ")) // Pass data to liveData listtitleBean.value = data} // Second Adapter data fun GetNewsData (){var data = listOf(News("NewsBean1"," NewsBean2 2nd adapter"), News("NewsBean4","NewsBean4 2nd adapter"), News("NewsBean4 2nd adapter"), News("NewsBean6","NewsBean6 second adapter"), News("NewsBean6 second adapter"), News("NewsBean7","NewsBean7 second adapter") listnews. value = data}Copy the code
  • Complete the 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 obtains and provides data */ class MyViewModel:ViewModel() {var ListTitleBean = MutableLiveData<List<TitleBean>>() var listNews = MutableLiveData<List<News>>( GetTitleData (){var data = listOf(TitleBean("TitleBean1","TitleBean1 first adapter"), TitleBean("TitleBean2","TitleBean2 first Adapter "), TitleBean("TitleBean3","TitleBean3 first adapter"), TitleBean("TitleBean4","TitleBean4 first Adapter "), TitleBean("TitleBean5","TitleBean5 first Adapter "), TitleBean("TitleBean6","TitleBean6 first adapter"), TitleBean("TitleBean7","TitleBean7 first Adapter ") listTitleBean.value = data} // Second Adapter data fun getNewsData(){var Data = listOf(News("NewsBean1","NewsBean1 2nd adapter"), News("NewsBean2","NewsBean2 2nd adapter"), News("NewsBean4","NewsBean4 2nd adapter"), News("NewsBean4 2nd adapter"), News("NewsBean6","NewsBean6 second adapter"), News("NewsBean6 second adapter"), News("NewsBean7","NewsBean7 second adapter") listnews. value = data} Private val _articleListData = MutableLiveData<List<Article>>() Call articleListData to get the network request data to the observer, but do not modify 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) / / request to the data with livedata _articleListData.value = result.data.datas } } }Copy the code

Recycleview merge display multiple adapters

  • Through MergeAdapter(mAdapter1,mAdapter2) to a plurality of adapter to recycleView sequence display
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 (" click mAdapter2 - ${position} ")}}) myViewModel. ListTitleBean. Observe (this, the Observer {mAdapter1!!!!! .refreshData(it) mAdapter1? .notifyDataSetChanged() }) myViewModel.listNews.observe(this, Observer { mAdapter2!! .refreshData(it) mAdapter2? .notifyDataSetChanged() })Copy the code

Display request network data through coroutines

  • The API defined
 @GET("article/list/{page}/json")
    suspend fun getArticleList(@Path("page") page: Int = 0): Result<PageEntity<Article>>
Copy the code
  • Call API in viewModel to request network
Private val _articleListData = MutableLiveData<List<Article>>() Call articleListData to get the network request data to the observer, but do not modify 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) / / request to the data with livedata _articleListData.value = result.data.datas } }Copy the code
  • Monitor network data to update the UI
  • Recycleview binding data and recycleView binding is omitted here. If you are interested, you can click to read the original text to see the demo
/ * * * * to monitor network request/private fun observerNetData () {/ / observe article list data myViewModel. ArticleListData. Observe (this, Observer {list -> // If the value of articleListData changes, this listener is triggered toast. makeText(this," Network data request success: ${list.toString()}",Toast.LENGTH_SHORT).show() }) }Copy the code
  • rendering

  • The activity of the call
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 () / / local data access method ii myViewModel. ListTitleBean. Value = createTitleList () myViewModel. ListNews. Value = Myviewmodel.fetch (1) observerNetData()} /** * Listen for network request data * / private fun observerNetData () {/ / observe article list data myViewModel. ArticleListData. Observe (this, Observer {list -> // If the value of articleListData changes, this listener is triggered toast. makeText(this," Network data request success: ${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 (" click mAdapter2 - ${position} ")}}) myViewModel. ListTitleBean. Observe (this, the 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() }Copy the code

Retrofit wrapper class

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 * */ 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} /* * Log interceptor * */ 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} / specific service instantiation * * * * / fun < T > getService(service: Class<T>): T { return retrofit.create(service) } }Copy the code
  • API interface
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 using coroutine definition API */ interface ApiService {/** ** Network request using coroutine */ @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>> }Copy the code

Making the address