Encapsulating databinding allows you to write less than 10000 lines of code

A program delayed by photography 2021-05-03 08:37:07
encapsulating databinding allows write lines


banners_twitter.png

Preface of this chapter

This article is about kotlin coroutines It's expanded when it comes to , If the kotlin coroutines Those who are interested can read through the link below 、

Extended series

I'm just an ordinary developer , The design is not necessarily reasonable , You can absorb the essence of the article by yourself , Dross removal .

Now we can start to do some basic packaging work , At the same time app Of bulid.gradle Open in file dataBinding Use

android {
buildFeatures {
dataBinding = true
}
// Omit ...
}
 Copy code 

be based on DataBinding Encapsulation

Let's first create a simple layout file activity_main.xml. To save time , At the same time, we also create a fragment_main.xml Keep the same layout .

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<Button
android:id="@+id/btn"
android:layout_width="100dp"
android:layout_height="50dp"
android:gravity="center"
android:text="Hello World"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
 Copy code 

We are using DataBinding When initializing the layout , We usually like to use the following ways , stay Activity in :

class MainActivity : AppCompatActivity() {
private lateinit var mBinding:ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
}
}
 Copy code 

stay Fragment in :

class HomeFragment:Fragment() {
private lateinit var mBinding:FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_main,container,false)
return mBinding.root
}
}
 Copy code 

In this case , For every one created activity and Fragment It needs to be rewritten . So we create a BaseActivity Abstraction , Then use generics to pass in what we need ViewDataBinding object VB, And then get it by constructing or abstracting methods LayoutRes resources

abstract class BaseActivity<VB : ViewDataBinding>(@LayoutRes resId:Int) : AppCompatActivity() {
lateinit var mBinding:VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView<VB>(this,resId)
}
//...
}
// perhaps 
abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity() {
lateinit var mBinding:VB
@LayoutRes abstract fun getLayoutId():Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView<VB>(this,getLayoutId())
}
}
 Copy code 

Is this time after we have dealt with the above , It will be much more convenient for us to use it again . Maybe some people who have encapsulated it will think , What are you talking about , I'll do all of that , There's no idea . I want to say : Don't try so hard , We're not talking about it , After all, everything needs prelude .

image.png

Although after the above abstraction , We have reduced some steps . But I still think it's a bit of trouble , Because we still need to send one by hand through the outside LayoutRes Resources can be used . Want to refine the reduced code again , Then we have to see ActivityMainBinding The implementation of the .

We're opening up DataBinding When , By using layout Of activity_main.xml Layout ,DataBinding At compile time, it will be in our project automatically app/build/generated/data_binding_base_class_source_out/packname/databinding Create a ActivityMainBinding class , Let's look at its implementation :

public abstract class ActivityMainBinding extends ViewDataBinding {
@NonNull
public final Button btn;
protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount,
TextView tv) {
super(_bindingComponent, _root, _localFieldCount);
this.btn = btn;
this.recyclerView = recyclerView;
}
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot) {
return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup root, boolean attachToRoot, @Nullable Object component) {
return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, root, attachToRoot, component);
}
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, DataBindingUtil.getDefaultComponent());
}
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
@Nullable Object component) {
return ViewDataBinding.<ActivityMainBinding>inflateInternal(inflater, R.layout.activity_main, null, false, component);
}
// Omit ...
}
 Copy code 

We can see in the ActivityMainBinding Among them are 4 individual inflate Method , At the same time, all of them will use our layout file directly activity_main.xml Loading . So we want to further simplify the usage on the basis of the above , We have to go through the mechanism of reflection , Get it from ActivityMainBinding Medium inflate Method , Use the corresponding inflate Method to load our layout . The code is as follows :

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater):VB{
val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java)
return inflate.invoke(null, inflater) as VB
}
 Copy code 

Let's define an extension method first , By way of reflection , From our Class Get the generic class we want in ViewBinding, then invoke call ViewBinding Of inflate Method . Then we create an interface for BaseActivity Subclass for UI Initialize the binding operation .

interface BaseBinding<VB : ViewDataBinding> {
fun VB.initBinding()
}
 Copy code 
abstract class BaseActivity<VB : ViewDataBinding> : AppCompatActivity(), BaseBinding<VB> {
internal val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
getViewBinding(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(mBinding.root)
mBinding.initBinding()
}
}
 Copy code 

Now we can inherit BaseActivity Achieve our Activity

class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun ActivityMainBinding.initBinding() {
Log.d("MainActivity","btn :${btn.text}")
}
}
 Copy code 
D/MainActivity: btn :Hello World
 Copy code 

Now, is our code concise 、 It's so refreshing . In this way, we not only save a lot of time in writing repetitive code , It also makes our code more reasonable 、 beautiful .

image.png

and Activity There are some differences , because Fragment When you create a layout, you need to pass in ViewGroup, So let's make a little change .

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup?):VB{
val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
return inflate.invoke(null, inflater, container, false) as VB
}
 Copy code 

Maybe in some circumstances, some people are creating Fragment You need to put attachToRoot Set to true, It's good to expand one by yourself at this time , We won't show it here . In the same way, let's abstract another one BaseFragment

abstract class BaseFragment<VB : ViewDataBinding>: Fragment(),BaseBinding<VB> {
protected lateinit var mBinding:VB
private set
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding = getViewBinding(inflater,container)
return mBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.initBinding()
}
}
 Copy code 
class HomeFragment:BaseFragment<FragmentMainBinding>() {
override fun FragmentMainBinding.initBinding() {
Log.d("HomeFragment","btn :${btn.text}")
}
}
 Copy code 

It's a lot easier to see here , Not only is the amount of code reduced , It's also improved a lot , At the same time, it adds XX Technology Group It's time to fish and blow water .

image.png

Of course, we can't just be satisfied with this , In the process of coding, there is also a lot of repetitive work is our Adapter, Let's use it to be long RecyclerView.Adapter For example , Suppose we create the simplest HomeAdapter

class HomeAdapter(private val data: List<String>? = null) : RecyclerView.Adapter<HomeAdapter.BindingViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
val mBinding = DataBindingUtil.inflate<ItemHomeBinding>(LayoutInflater.from(parent.context), R.layout.item_home ,parent, false)
return BindingViewHolder(mBinding)
}
override fun onBindViewHolder(holder: BindingViewHolder, position: Int) {
holder.binding.tv.text = data?.get(position) ?: ""
// Other bindings ...
holder.binding.executePendingBindings()
}
fun setData(){
// The refresh data ...
}
override fun getItemCount(): Int {
return data?.size ?: 0
}
class BindingViewHolder constructor(val binding: ItemHomeBinding) : RecyclerView.ViewHolder(binding.root)
}
 Copy code 

This is the simplest one Adapter, We all need a lot of code , If you add item Of click Words of events , What we have to do Work becomes more . So if we want to solve such a problem now , Where are we going to deal with :

  1. Unified Adapter Initialization work .
  2. simplify onBindViewHolder Use .
  3. Remove the need to create repeatedly every time ViewHolder.
  4. We set up Item How to monitor events .
  5. Unified Adapter Data refresh for .

First, we need to modify the extension we defined earlier getViewBinding, Because we don't know this getViewBinding Which class is it used for , This class defines several generics . So we add a default value of 0 Of position Parameter instead of the previously written dead 0, In this way, let the caller set VB:ViewBinding The order of the positions :

inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater,position:Int = 0):VB{
val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java)
return inflate.invoke(null, inflater) as VB
}
inline fun <VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup?,position:Int = 0):VB{
val vbClass = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstance<Class<VB>>()
val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
return inflate.invoke(null, inflater, container, false) as VB
}
 Copy code 

Create our BaseAdapter, Post the full code first :

abstract class BaseAdapter<T, VB : ViewDataBinding> : RecyclerView.Adapter<BaseAdapter.BindViewHolder<VB>>() {
private var mData: List<T> = mutableListOf()
fun setData(data: List<T>?) {
data?.let {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return mData.size
}
override fun getNewListSize(): Int {
return it.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldData: T = mData[oldItemPosition]
val newData: T = it[newItemPosition]
return this@BaseAdapter.areItemsTheSame(oldData, newData)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldData: T = mData[oldItemPosition]
val newData: T = it[newItemPosition]
return this@BaseAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition)
}
})
mData = data
result.dispatchUpdatesTo(this)
} ?: let {
mData = mutableListOf()
notifyItemRangeChanged(0, mData.size)
}
}
fun addData(data: List<T>?, position: Int? = null) {
if (!data.isNullOrEmpty()) {
with(LinkedList(mData)){
position?.let {
val startPosition = when {
it < 0 -> 0
it >= size -> size
else -> it
}
addAll(startPosition, data)
}?: addAll(data)
setData(this)
}
}
}
protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItem == newItem
}
protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem == newItem
}
fun getData(): List<T> {
return mData
}
fun getItem(position: Int): T {
return mData[position]
}
fun getActualPosition(data: T): Int {
return mData.indexOf(data)
}
override fun getItemCount(): Int {
return mData.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder<VB> {
return with(getViewBinding<VB>(LayoutInflater.from(parent.context), parent,1)) {
setListener()
BindViewHolder(this)
}
}
override fun onBindViewHolder(holder: BindViewHolder<VB>, position: Int) {
with(holder.binding){
onBindViewHolder(getItem(position), position)
executePendingBindings()
}
}
open fun VB.setListener() {}
abstract fun VB.onBindViewHolder(bean: T, position: Int)
class BindViewHolder<M : ViewDataBinding>(var binding: M) :
RecyclerView.ViewHolder(binding.root)
}
 Copy code 

Let's ignore this first BaseAdapter The definition of , Now we will HomeAdapter It's changed to :

class HomeAdapter : BaseAdapter<String, ItemHomeBinding>() {
override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) {
tv.text = bean
}
}
 Copy code 

We are Activity When used in :

class MainActivity : BaseActivity<ActivityMainBinding>() {
lateinit var homeAdapter:HomeAdapter
override fun ActivityMainBinding.initBinding() {
homeAdapter = HomeAdapter()
with(recyclerView){
layoutManager = LinearLayoutManager(this@MainActivity).apply {
orientation = RecyclerView.VERTICAL
}
adapter = homeAdapter
}
homeAdapter.setData(listOf("a","b","c","d","e","f"))
}
}
 Copy code 

Now our adapter Does the code in become super concise , I believe that now even if you write 100 individual Adapter Not afraid of .

image.png

Now let's dismantle it step by step BaseAdapter. We see BaseAdapter Need to 2 Generics ,T Is the data type of the entity we need to display ,VB It's our layout binding ViewDataBinding.

abstract class BaseAdapter<T, VB : ViewDataBinding> 
: RecyclerView.Adapter<BaseAdapter.BindViewHolder<VB>>() {
//...
}
 Copy code 

You can see from below that we are passing through BaseAdapter Realization onCreateViewHolder To deal with it Item Initialization of layout , We call here getViewBinding When position What's coming in is 1, It corresponds to us VB In the order of :

 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolder<VB> {
return with(getViewBinding<VB>(LayoutInflater.from(parent.context), parent,1)) {
setListener()
BindViewHolder(this)
}
}
 Copy code 

At the same time, we created an inner class BindViewHolder To unify ViewHolder Creation of .

 class BindViewHolder<M : ViewDataBinding>(var binding: M) :
RecyclerView.ViewHolder(binding.root)
 Copy code 

We call an empty implementation while initializing the layout setListener Method . Why do we use open Instead of adopting abstract To define . It's because we're not everyone Adapter All need to be set Item The monitoring event of , So we put setListener Just as an optional item .

 open fun VB.setListener() {}
 Copy code 

image.png

After initialization , We need to do layout binding , But because of the different Adapter The interface of , The binding that needs to be handled is different , So we are realizing onBindViewHolder At the same time , By calling an internally created abstract method VB.onBindViewHolder Leave our binding processing to subclasses .

 override fun onBindViewHolder(holder: BindViewHolder<VB>, position: Int) {
with(holder.binding){
onBindViewHolder(getItem(position), position)
executePendingBindings()
}
}
 Copy code 

At the same time VB.onBindViewHolder Parameters are converted to actual data bean And the corresponding position position.

 abstract fun VB.onBindViewHolder(bean: T, position: Int)
 Copy code 

up to now , We are BaseAdapter We've dealt with :

  1. Unified Adapter Initialization work .
  2. simplify onBindViewHolder Use .
  3. Remove the need to create repeatedly every time ViewHolder.
  4. We set up Item How to monitor events .

Now let's look at how to unify Adapter Data refresh for . You can see that we are BaseAdapter Created a private data set mData, stay mData What we need to display is the generics in T Data type of .

private var mData: List<T> = mutableListOf()
 Copy code 

At the same time, we added a setData Method , In this method we use DiffUtil Compare and refresh our data ., If the DiffUtil If you are not familiar with it, you can check its method .

 fun setData(data: List<T>?) {
data?.let {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return mData.size
}
override fun getNewListSize(): Int {
return it.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldData: T = mData[oldItemPosition]
val newData: T = it[newItemPosition]
return this@BaseAdapter.areItemsTheSame(oldData, newData)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldData: T = mData[oldItemPosition]
val newData: T = it[newItemPosition]
return this@BaseAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition)
}
})
mData = data
result.dispatchUpdatesTo(this)
} ?: let {
mData = mutableListOf()
notifyItemRangeChanged(0, mData.size)
}
}
 Copy code 

Here we need to pay attention to ,DiffUtil.Callback Medium areItemsTheSame and areContentsTheSame2 A way to compare data , It's actually through us in BaseAdapter In the definition of 2 individual open Method areItemContentsTheSame,areItemsTheSame.

 protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItem == newItem
}
protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem == newItem
}
 Copy code 

Why do you define it like this . Although in BaseAdapter This is achieved in 2 A way , Because we don't know whether subclasses need to change the way of comparison when they are implemented . For example, I'm using areItemsTheSame When , Generic T If generics T Not a basic data type , Generally, you just need to compare generics T The only one in key Can . Now suppose generics T Is a data entity class User:

 data class User(val id:Int,val name:String)
 Copy code 

So we're copying in subclasses areItemsTheSame Method time , Can be realized in our apapter Use the following :

 protected open fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
 Copy code 

Careful may notice that we have a definition of getActualPosition Method , Why not getPosition. This is because in some places, for convenience , We are onBindViewHolder At this time position preserved , Or when the listener is set position.

If we insert or reduce a few pieces of data based on the previous data , But also because we use DiffUtil The way to refresh , Because it existed before bean The data of the company has not changed , It's just that the position has changed , therefore onBindViewHolder Not execute , At this time, we directly use position There will be the problem of the wrong position when you are in the car , Or the problem of crossing the border . For example, use :

interface ItemClickListener<T> {
fun onItemClick(view: View,position:Int, data: T){}
fun onItemClick(view: View, data: T)
}
 Copy code 

We are ItemClickListener Defined 2 A way , We use the position Of onItemClick Method to demonstrate :

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="bean"
type="String" />
<variable
name="position"
type="int" />
<variable
name="itemClickListener"
type="com.carman.kotlin.coroutine.interf.ItemClickListener" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="match_parent"
android:layout_height="50dp"
android:onClick="@{(v)->itemClickListener.onItemClick(v,position,bean)}"
android:textColor="@color/black"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
 Copy code 

We are XML In the middle of Click binding , And then we were in HomeAdapter Monitoring events and data settings

class HomeAdapter(private val listener: ItemClickListener<String>) : BaseAdapter<String, ItemHomeBinding>() {
override fun ItemHomeBinding.setListener() {
itemClickListener = listener
}
override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) {
this.bean = bean
this.position = position
tv.text = bean
}
}
 Copy code 

And then we have MainActivity adopt 2 Click to view the log

class MainActivity : BaseActivity<ActivityMainBinding>() {
lateinit var homeAdapter:HomeAdapter
override fun ActivityMainBinding.initBinding() {
homeAdapter = HomeAdapter(itemClickListener)
with(recyclerView){
layoutManager = LinearLayoutManager(this@MainActivity).apply {
orientation = RecyclerView.VERTICAL
}
adapter = homeAdapter
}
homeAdapter.setData(listOf("a","b","c","d","e","f"))
btn.setOnClickListener {
Log.d(" Refresh ", " The second time setData")
homeAdapter.setData(listOf("c","d","e","f"))
}
}
private val itemClickListener = object : ItemClickListener<String> {
override fun onItemClick(view: View, position: Int, data: String) {
Log.d("onItemClick", "data:$data position:$position")
}
}
}
 Copy code 
D/onItemClick: data:a position:0
D/onItemClick: data:b position:1
D/onItemClick: data:c position:2
D/onItemClick: data:d position:3
D/onItemClick: data:e position:4
D/onItemClick: data:f position:5
D/ Refresh : The second time setData
D/onItemClick: data:c position:2
D/onItemClick: data:d position:3
D/onItemClick: data:e position:4
D/onItemClick: data:f position:5
 Copy code 

So we need to use position When , It's better to go through getActualPosition To get the real location , So let's revise that itemClickListener Log output in .

 private val itemClickListener = object : ItemClickListener<String> {
override fun onItemClick(view: View, position: Int, data: String) {
Log.d("onItemClick", "data:$data position:${homeAdapter.getActualPosition(data)}")
}
}
 Copy code 

At this time, when we repeat the above operation , You can see that position Where it is is is where it is now .

D/onItemClick: data:c position:0
D/onItemClick: data:d position:1
D/onItemClick: data:e position:2
D/onItemClick: data:f position:3
 Copy code 

Only this and nothing more , We're talking about this BaseAdapter<T, VB : ViewDataBinding> The abstract principle of , And how to use it .

It should be noted that For easy demonstration , Let's assume that , Not in the xml You can use Databinding Binding . Because of some complex logic, we can't simply xml Binding in .

image.png

Obviously our work is not over here , Because of our adapter There are also multiple layouts in common scenes , How should we deal with it .

This is actually very easy to handle . Because we have multiple layouts , So that means we need to put onCreateViewHolder Part of the work in is exposed to subclass processing , So we need to be on top BaseAdapter Make some changes on the basis of . Code as usual :

abstract class BaseMultiTypeAdapter<T> : RecyclerView.Adapter<BaseMultiTypeAdapter.MultiTypeViewHolder>() {
private var mData: List<T> = mutableListOf()
fun setData(data: List<T>?) {
data?.let {
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return mData.size
}
override fun getNewListSize(): Int {
return it.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldData: T = mData[oldItemPosition]
val newData: T = it[newItemPosition]
return this@BaseMultiTypeAdapter.areItemsTheSame(oldData, newData)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldData: T = mData[oldItemPosition]
val newData: T = it[newItemPosition]
return this@BaseMultiTypeAdapter.areItemContentsTheSame(oldData, newData, oldItemPosition, newItemPosition)
}
})
mData = data
result.dispatchUpdatesTo(this)
} ?: let {
mData = mutableListOf()
notifyItemRangeChanged(0, mData.size)
}
}
fun addData(data: List<T>?, position: Int? = null) {
if (!data.isNullOrEmpty()) {
with(LinkedList(mData)) {
position?.let {
val startPosition = when {
it < 0 -> 0
it >= size -> size
else -> it
}
addAll(startPosition, data)
} ?: addAll(data)
setData(this)
}
}
}
protected open fun areItemContentsTheSame(oldItem: T, newItem: T, oldItemPosition: Int, newItemPosition: Int): Boolean {
return oldItem == newItem
}
protected open fun areItemsTheSame(oldItem: T, newItem: T): Boolean {
return oldItem == newItem
}
fun getData(): List<T> {
return mData
}
fun getItem(position: Int): T {
return mData[position]
}
fun getActualPosition(data: T): Int {
return mData.indexOf(data)
}
override fun getItemCount(): Int {
return mData.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiTypeViewHolder {
return MultiTypeViewHolder(onCreateMultiViewHolder(parent, viewType))
}
override fun onBindViewHolder(holder: MultiTypeViewHolder, position: Int) {
holder.onBindViewHolder(holder, getItem(position), position)
holder.binding.executePendingBindings()
}
abstract fun MultiTypeViewHolder.onBindViewHolder(holder: MultiTypeViewHolder, item: T, position: Int)
abstract fun onCreateMultiViewHolder(parent: ViewGroup, viewType: Int): ViewDataBinding
protected fun <VB :ViewDataBinding> loadLayout(vbClass: Class<VB>,parent: ViewGroup): VB {
val inflate = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
return inflate.invoke(null, LayoutInflater.from(parent.context), parent, false) as VB
}
class MultiTypeViewHolder(var binding: ViewDataBinding) :
RecyclerView.ViewHolder(binding.root)
}
 Copy code 

You can see from the above code , We didn't BaseMultiTypeAdapter Define generics in VB :ViewDataBinding, Because we have multiple layouts , If it's all written in the definition of a class, it's obviously inappropriate , We don't know how many layouts are needed in the implementation .

So we onCreateViewHolder When initializing the layout, an abstract onCreateMultiViewHolder Method , This method is implemented by our specific business implementation class . At the same time, we are right onBindViewHolder Make changes , Added one holder Parameters for external use . Let's start with the data entity type

sealed class Person(open val id :Int, open val name:String)
data class Student(
override val id:Int,
override val name:String,
val grade:String):Person(id, name)
data class Teacher(
override val id:Int,
override val name:String,
val subject:String):Person(id, name)
 Copy code 

And what we need to achieve Adapter Business class ,:

class SecondAdapter: BaseMultiTypeAdapter<Person>() {
companion object{
private const val ITEM_DEFAULT_TYPE = 0
private const val ITEM_STUDENT_TYPE = 1
private const val ITEM_TEACHER_TYPE = 2
}
override fun getItemViewType(position: Int): Int {
return when(getItem(position)){
is Student -> ITEM_STUDENT_TYPE
is Teacher -> ITEM_TEACHER_TYPE
else -> ITEM_DEFAULT_TYPE
}
}
override fun onCreateMultiViewHolder(parent: ViewGroup, viewType: Int): ViewDataBinding {
return when(viewType){
ITEM_STUDENT_TYPE -> loadLayout(ItemStudentBinding::class.java,parent)
ITEM_TEACHER_TYPE -> loadLayout(ItemTeacherBinding::class.java,parent)
else -> loadLayout(ItemPersionBinding::class.java,parent)
}
}
override fun MultiTypeViewHolder.onBindViewHolder(holder: MultiTypeViewHolder, item: Person, position: Int) {
when(holder.binding){
is ItemStudentBinding ->{
Log.d("ItemStudentBinding","item : $item position : $position")
}
is ItemTeacherBinding ->{
Log.d("ItemTeacherBinding","item : $item position : $position")
}
}
}
}
 Copy code 
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun ActivityMainBinding.initBinding() {
val secondAdapter = SecondAdapter()
with(recyclerView){
layoutManager = LinearLayoutManager(this@MainActivity).apply {
orientation = RecyclerView.VERTICAL
}
adapter = secondAdapter
}
secondAdapter.setData(
listOf(
Teacher(1,"Person"," Chinese language and literature "),
Student(2,"Person"," In grade one "),
Teacher(3,"Person"," mathematics "),
))
}
 Copy code 

Run it and you'll see what we want :

D/ItemTeacherBinding: item : Teacher(id=1, name=Person, subject= Chinese language and literature ) position : 0
D/ItemStudentBinding: item : Student(id=2, name=Person, grade= In grade one ) position : 1
D/ItemTeacherBinding: item : Teacher(id=3, name=Person, subject= mathematics ) position : 2
 Copy code 

After the above treatment , We're creating ActiviyFragmentAdapter It reduces a lot of code when it's done . At the same time, it also saves the time of repeated garbage code , At least let your work efficiency at least improve 10 percentage , Do you feel that you have become a lot more handsome .

image.png

After the above packaging treatment , Can we do the same for Dialog,PopWindow、 dynamic initialization View Deal with it . What are you waiting for , Let's do it . After all, teach people fish , It's better to teach people to fish .

Originality is not easy. . If you like this article , You can use your little hand to like the collection image.png.

Related articles

This article is about kotlin coroutines It's expanded when it comes to , If the kotlin coroutines Those who are interested can read through the link below

版权声明
本文为[A program delayed by photography]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/05/20210503083529955I.html

  1. HTML + CSS + JavaScript to achieve cool Fireworks (cloud like particle text 3D opening)
  2. HTML + CSS + JavaScript realizes 520 advertising love tree (including music), which is necessary for programmers to express themselves
  3. Solve the problem of Web front-end deployment server (it can be deployed online without a server)
  4. HTML + CSS + JS make wedding countdown web page template (520 / Tanabata Valentine's Day / programmer advertisement)
  5. What else can driverless minibus do besides "Park connection"?
  6. Cloud native leads the era of all cloud development
  7. NRM mirror source management tool
  8. Bring it to you, flex Jiugong
  9. Lolstyle UI component development practice (II) -- button group component
  10. Deconstruction assignment in ES6
  11. Luo 2 peerless Tang clan was officially launched. The official gave a key point, and the broadcast time was implied
  12. 20初识前端HTML(1)
  13. 当新零售遇上 Serverless
  14. 20 initial knowledge of front-end HTML (1)
  15. When new retail meets serverless
  16. [golang] - go into go language lesson 5 type conversion
  17. [golang] - go into go language lesson 6 conditional expression
  18. HTML5(八)——SVG 之 path 详解
  19. HTML5 (8) -- detailed explanation of SVG path
  20. 需要开通VIP以后页面内容才能复制怎么办?控制台禁用javascript即可
  21. Web前端|CSS入门教程(超详细的CSS使用讲解,适合前端初学者)
  22. 实践积累 —— 用Vue3简单写一个单行横向滚动组件
  23. Serverless 全能选手,再下一城
  24. What if you need to open a VIP to copy the page content? Just disable JavaScript on the console
  25. Web front end | CSS introductory tutorial (super detailed CSS explanation, suitable for front-end beginners)
  26. Practice accumulation - write a single line horizontal scroll component simply with vue3
  27. Dili Reba is thin again. She looks elegant and high in a strapless hollow skirt, and her "palm waist" is beautiful to a new height
  28. Serverless all-round player, next city
  29. The difference between MySQL semi synchronous replication and lossless semi synchronous replication
  30. Vue表单设计器的终极解决方案
  31. The ultimate solution for Vue form designer
  32. Nginx从理论到实践超详细笔记
  33. Yu Shuxin's red backless swimsuit is split to the waist and tail, with a concave convex figure and excessive color matching, and his face is white to dazzling
  34. Nginx ultra detailed notes from theory to practice
  35. 【动画消消乐|CSS】086.炫酷水波浪Loading过渡动画
  36. typecho全站启用https
  37. CCTV has another popular employee. The off-site interpretation is very professional, and the appearance ability is no less than that of Wang Bingbing
  38. [animation Xiaole | CSS] 086. Cool water wave loading transition animation
  39. Enable HTTPS in Typecho
  40. 50天用JavaScript完成50个web项目,我学到了什么?
  41. 根据JavaScript中原生的XMLHttpRequest实现jQuery的Ajax
  42. What have I learned from completing 50 web projects with JavaScript in 50 days?
  43. "My neighbor doesn't grow up" has hit the whole network. There are countless horse music circles, and actor Zhou Xiaochuan has successfully made a circle
  44. 根据JavaScript中原生的XMLHttpRequest实现jQuery的Ajax
  45. Implement the Ajax of jQuery according to the native XMLHttpRequest in JavaScript
  46. Implement the Ajax of jQuery according to the native XMLHttpRequest in JavaScript
  47. 30 + women still wear less T-shirts and jeans. If they wear them like stars, they will lose weight
  48. 数栈技术分享前端篇:TS,看你哪里逃~
  49. Several stack technology sharing front end: TS, see where you escape~
  50. 舍弃Kong和Nginx,Apache APISIX 在趣链科技 BaaS 平台的落地实践
  51. Abandon the landing practice of Kong and nginx, Apache apisik on the baas platform of fun chain technology
  52. 浪迹天涯king教你用elementui做复杂的表格,去处理报表数据(合并表头,合并表体行和列)
  53. 前端HTML两万字图文大总结,快来看看你会多少!【️熬夜整理&建议收藏️】
  54. Wandering around the world king teaches you to use elementui to make complex tables and process report data (merge header, merge table body rows and columns)
  55. 路由刷新数据丢失 - vuex数据读取的问题
  56. Front end HTML 20000 word graphic summary, come and see how much you can【 Stay up late to sort out & suggestions]
  57. Route refresh data loss - vuex data reading problem
  58. Systemctl系统启动Nginx服务脚本
  59. Systemctl system startup nginx service script
  60. sleepless