封裝DataBinding讓你少寫萬行程式碼

25minutes發表於2021-09-09

圖片描述

筆者也只是一個普普通通的開發者,設計不一定合理,大家可以自行吸收文章精華,去糟粕。

現在我們就可以開始做一些基礎的封裝工作,同時在app的bulid.gradle檔案中開啟dataBinding的使用

android {
    buildFeatures {
        dataBinding  = true
    }
   //省略...
}
複製程式碼

基於DataBinding的封裝

我們先建立一個簡單的佈局檔案activity_main.xml。為了節約時間,同時我們也建立一個fragment_main.xml保持一樣的佈局。


layout xmlns:android=""
    xmlns:app=""
    xmlns: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>
複製程式碼

我們在使用DataBinding初始化佈局時候,我們通常喜歡使用下面幾種方式, 在Activity中:

class MainActivity : AppCompatActivity() {
 private lateinit var mBinding:ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         mBinding = DataBindingUtil.setContentViewActivityMainBinding>(this,R.layout.activity_main)
    }
}
複製程式碼

Fragment中:

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
    }
}
複製程式碼

這種情況下,每建立一個activityFragment都需要重寫一遍。所以我們建立一個BaseActivity進行抽象,然後使用泛型傳入我們需要的ViewDataBinding物件VB,再透過構造方法或者抽象方法獲取LayoutRes資源

abstract class BaseActivityVB : ViewDataBinding>(@LayoutRes resId:Int) : AppCompatActivity() {
    lateinit var mBinding:VB
    override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentViewVB>(this,resId)
    }
    //...
}
 //或者
abstract class BaseActivityVB : ViewDataBinding> : AppCompatActivity() {
    lateinit var mBinding:VB
    @LayoutRes abstract fun getLayoutId():Int
    override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
        mBinding = DataBindingUtil.setContentViewVB>(this,getLayoutId())
    }
}
複製程式碼

這個時候是不是經過我們經過上面處理後,再使用的時候我們會方便很多。可能有些人封裝過的人看到這裡會想,你講的這都是啥,這些我都會,沒有一點創意。筆者想說:不要捉急,我們要講的可不是上面的東西,畢竟做事情都需要前奏鋪墊滴。

圖片描述

雖然經過上面的抽象以後,我們是減少了一些步驟。但是筆者還寫覺得有些麻煩,因為我們還是需要手寫的透過外部傳一個LayoutRes資源才能進行使用。想要再次細化縮減程式碼,那我們就得看看ActivityMainBinding的實現。

我們在開啟DataBinding的時候,透過使用layoutactivity_main.xml佈局,DataBinding在編譯的時候會自動在我們的工程app/build/generated/data_binding_base_class_source_out/packname/databinding目錄下為我們生成一個ActivityMainBinding類,我們看看它的實現:

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);
  }
    //省略...
}
複製程式碼

我們可以看到在ActivityMainBinding中的有4個inflate方法,同時他們最後的都會直接使用我們的佈局檔案activity_main.xml進行載入。所以我們想在上面的基礎上進一步的簡化使用方式,我們就必須透過反射的機制,從拿到ActivityMainBinding中的inflate方法,使用相對應的inflate方法去載入我們的佈局。程式碼如下:

inline fun VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater):VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstanceClassVB>>()
    val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java)
    return  inflate.invoke(null, inflater) as VB
}
複製程式碼

我們先定義個擴充套件方法,透過反射的方式,從我們的Class中拿到我們想要的泛型類ViewBinding,然後invoke呼叫ViewBindinginflate方法。然後我們再建立一個介面用於BaseActivity子類進行UI初始化繫結操作。

interface BaseBindingVB : ViewDataBinding> {
    fun VB.initBinding()
}
複製程式碼
abstract class BaseActivityVB : ViewDataBinding> : AppCompatActivity(), BaseBindingVB> {
 internal val mBinding: VB by lazy(mode = LazyThreadSafetyMode.NONE) {
       getViewBinding(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        mBinding.initBinding()
    }
}
複製程式碼

現在我們就可以繼承BaseActivity實現我們的Activity

class MainActivity : BaseActivityActivityMainBinding>() {

    override fun ActivityMainBinding.initBinding() {
        Log.d("MainActivity","btn  :${btn.text}")
    }
}
複製程式碼
D/MainActivity: btn  :Hello World
複製程式碼

現在我們的程式碼是不是變得簡潔、清爽很多。這樣我們不僅節省了編寫大量重複程式碼的時間,同時也讓我們程式碼的變得更加合理、美觀。

圖片描述

Activity有一些不同,因為Fragment建立佈局的時候需要傳入ViewGroup,所以我們稍微做一個變化。

inline fun VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater, container: ViewGroup?):VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstanceClassVB>>()
    val inflate = vbClass[0].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
    return inflate.invoke(null, inflater, container, false) as VB
}
複製程式碼

可能在某些環境下有些人在建立Fragment的時候需要把attachToRoot設定成true,這個時候自己擴充套件一個就好了,我們這裡就不再演示。同理我們再抽象一個BaseFragment

abstract class BaseFragmentVB : ViewDataBinding>: Fragment(),BaseBindingVB> {
    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()
    }
}
複製程式碼
class HomeFragment:BaseFragmentFragmentMainBinding>() {
    override fun FragmentMainBinding.initBinding() {
        Log.d("HomeFragment","btn  :${btn.text}")
    }
}
複製程式碼

看到這裡是不是心靈舒暢了很多,不僅程式碼量減少了,逼格也提升了許多,同時又增加了XX技術群裡摸魚吹水的時間。

圖片描述

當然我們也不能僅僅滿足於此,在碼程式碼的過程中還有一個大量重複工作的就是我們的Adapter,我們就拿使用到做多的RecyclerView.Adapter為例,假設我們建立一個最簡單的HomeAdapter

class HomeAdapter(private val data: ListString>? = null) : RecyclerView.AdapterHomeAdapter.BindingViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder {
        val mBinding = DataBindingUtil.inflateItemHomeBinding>(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) ?: ""
        //其他繫結...
        holder.binding.executePendingBindings()
    }

    fun setData(){
        //重新整理資料...
    }

    override fun getItemCount(): Int {
        return data?.size ?: 0
    }

    class BindingViewHolder constructor(val binding: ItemHomeBinding) : RecyclerView.ViewHolder(binding.root)
}
複製程式碼

就這樣一個最簡單的Adapter,我們都需要些一堆囉嗦程式碼,如果再加上itemclick事件的話,我們要做的 工作就變得更多。那麼我們現在要解決這麼一個問題的話,我們要處理哪裡東西呢:

  1. 統一Adapter的初始化工作。
  2. 簡化onBindViewHolder的使用。
  3. 去掉每次都需要重複建立ViewHolder
  4. 統一我們設定Item的監聽事件方式。
  5. 統一Adapter的資料重新整理。

首先我們需要修改一下我們之前定義的擴充套件getViewBinding,因為我們是不知道具體這個getViewBinding是用在哪個類上,這個類又定義了幾個泛型。所以我們增加一個預設值為0position引數代替之前寫死的0,透過這種方式讓呼叫者自行設定VB:ViewBinding所在的位置順序:

inline fun VB:ViewBinding> Any.getViewBinding(inflater: LayoutInflater,position:Int = 0):VB{
    val vbClass =  (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments.filterIsInstanceClassVB>>()
    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.filterIsInstanceClassVB>>()
    val inflate = vbClass[position].getDeclaredMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
    return inflate.invoke(null, inflater, container, false) as VB
}
複製程式碼

建立我們的BaseAdapter,先將完整程式碼貼出:

abstract class BaseAdapterT, VB : ViewDataBinding> : RecyclerView.AdapterBaseAdapter.BindViewHolderVB>>() {

    private var mData: ListT> = mutableListOf()

    fun setData(data: ListT>?) {
        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: ListT>?, 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(): ListT> {
        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): BindViewHolderVB> {
        return with(getViewBindingVB>(LayoutInflater.from(parent.context), parent,1)) {
            setListener()
            BindViewHolder(this)
        }
    }

    override fun onBindViewHolder(holder: BindViewHolderVB>, position: Int) {
        with(holder.binding){
           onBindViewHolder(getItem(position), position)
           executePendingBindings()
        }
    }

    open fun VB.setListener() {}

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

    class BindViewHolderM : ViewDataBinding>(var binding: M) :
            RecyclerView.ViewHolder(binding.root)
}
複製程式碼

我們這裡先忽略這個BaseAdapter的定義,現在我們將HomeAdapter修改一下就變成了:

class HomeAdapter : BaseAdapterString, ItemHomeBinding>() {

    override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) {
       tv.text = bean
    }
}
複製程式碼

我們在Activity中使用時:

class MainActivity : BaseActivityActivityMainBinding>() {
    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"))
    }
}
複製程式碼

現在我們的adapter中的程式碼是不是變的超簡潔,我相信現在即使讓你再寫100Adapter也不害怕。

圖片描述

現在我們來一步一步的拆解BaseAdapter。我們看到BaseAdapter需要傳入2個泛型,T是我們需要顯示的實體的資料型別,VB是我們的佈局繫結ViewDataBinding

abstract class BaseAdapterT, VB : ViewDataBinding> 
    : RecyclerView.AdapterBaseAdapter.BindViewHolderVB>>() {
    //...
}
複製程式碼

往下可以看到我們透過在BaseAdapter實現onCreateViewHolder來處理Item佈局的初始化工作,我們這裡呼叫getViewBinding的時候position傳入的是1,正好對應我們VB所在的順序:

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindViewHolderVB> {
        return with(getViewBindingVB>(LayoutInflater.from(parent.context), parent,1)) {
            setListener()
            BindViewHolder(this)
        }
    }
複製程式碼

同時我們建立了一個內部類BindViewHolder來進行統一ViewHolder的建立工作。

    class BindViewHolderM : ViewDataBinding>(var binding: M) :
            RecyclerView.ViewHolder(binding.root)
複製程式碼

我們在初始化佈局的同時又呼叫了一個空實現的setListener方法。為什麼我們在這裡採用open而不是採用abstract來定義。是因為我們不是每一個Adapter都需要設定Item的監聽事件,因此我們把setListener只是作為一個可選的項來處理。

    open fun VB.setListener() {}
複製程式碼

圖片描述

初始化完成以後,我們需要進行佈局繫結,但是因為不同的Adapter的介面,需要處理的繫結是不一樣的,所以我們在實現onBindViewHolder的同時,透過呼叫內部建立的抽象方法VB.onBindViewHolder將我們的繫結處理交由子類進行處理。

    override fun onBindViewHolder(holder: BindViewHolderVB>, position: Int) {
        with(holder.binding){
           onBindViewHolder(getItem(position), position)
           executePendingBindings()
        }
    }
複製程式碼

同時將VB.onBindViewHolder引數轉換為實際的資料bean和對應的位置position.

 abstract fun VB.onBindViewHolder(bean: T, position: Int)
複製程式碼

到目前為止,我們在BaseAdapter中已經處理了:

  1. 統一Adapter的初始化工作。
  2. 簡化onBindViewHolder的使用。
  3. 去掉每次都需要重複建立ViewHolder
  4. 統一我們設定Item的監聽事件方式。

現在我們就來看下是如何統一Adapter的資料重新整理。可以看到我們在BaseAdapter建立了一個私有資料集合mData,在mData中存放的是我們需要顯示的泛型T的資料型別。

private var mData: ListT> = mutableListOf()
複製程式碼

同時我們增加了一個setData方法,在此方法中我們使用對我們的資料進行對比重新整理。,如果對不太熟的可以查一下它的方法。

    fun setData(data: ListT>?) {
        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)
        }
    }
複製程式碼

這裡我們需要注意一下,DiffUtil.Callback中的areItemsTheSameareContentsTheSame2個對比資料的方法,實際上是透過我們在BaseAdapter中定義2個open方法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
    }
複製程式碼

為什麼要這麼去定義的呢。雖然在BaseAdapter中實現了這2個方法,因為我們不知道子類在實現的時候是否需要改變對比的方式。比如我在使用areItemsTheSame的時候,泛型T如果泛型T不是一個基本資料型別,通常只需要對比泛型T中的唯一key就可以。現在假設泛型T是一個資料實體類User:

    data class User(val id:Int,val name:String)
複製程式碼

那我們在子類複寫areItemsTheSame方法的時候,就可以在我們的實現的apapter如下使用:

    protected open fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }
複製程式碼

細心的可能注意到我們有定義一個getActualPosition方法,為什麼不是叫getPosition。這是因為在有些為了方便,我們在onBindViewHolder的時候把此時position儲存下來,或者設定監聽器的時候傳入了position

如果我們在之前的資料基礎上插入或者減少幾條資料的話,但是又因為我們使用了DiffUtil的方式去重新整理,由於之前已存在bean的資料沒變,只是位置變了,所以onBindViewHolder不會執行,這個時候我們直接使用position的時候會出現位置不對問題,或者是越界的問題。比如如下使用:

interface ItemClickListenerT> {
    fun onItemClick(view: View,position:Int, data: T){}
    fun onItemClick(view: View, data: T)
}
複製程式碼

我們在ItemClickListener定義了2個方法,我們使用帶有positiononItemClick方法來演示:


layout xmlns:android=""
    xmlns:app="">

    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>
複製程式碼

我們在XML中進行Click繫結,然後我們在HomeAdapter進行監聽事件和資料設定

class HomeAdapter(private val listener: ItemClickListenerString>) : BaseAdapterString, ItemHomeBinding>() {

    override fun ItemHomeBinding.setListener() {
      itemClickListener = listener
    }

    override fun ItemHomeBinding.onBindViewHolder(bean: String, position: Int) {
        this.bean = bean
        this.position = position
        tv.text = bean
    }
}
複製程式碼

接下來我們在MainActivity透過2次設定資料在點選檢視日誌

class MainActivity : BaseActivityActivityMainBinding>() {
    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("重新整理", "第二次setData")
            homeAdapter.setData(listOf("c","d","e","f"))
        }
    }

    private val itemClickListener = object : ItemClickListenerString> {
        override fun onItemClick(view: View, position: Int, data: String) {
            Log.d("onItemClick", "data:$data   position:$position")
        }
    }
}
複製程式碼
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/重新整理: 第二次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
複製程式碼

所以我們需要在使用position的時候,最好是透過getActualPosition來獲取真實的位置,我們修改一下itemClickListener中的日誌輸出。

    private val itemClickListener = object : ItemClickListenerString> {
        override fun onItemClick(view: View, position: Int, data: String) {
            Log.d("onItemClick", "data:$data   position:${homeAdapter.getActualPosition(data)}")
        }
    }
複製程式碼

這個時候我們再重複上面操作的時候,就可以看到position的位置就是它目前所處的真實位置。

D/onItemClick: data:c   position:0
D/onItemClick: data:d   position:1
D/onItemClick: data:e   position:2
D/onItemClick: data:f   position:3
複製程式碼

到此為止,我們對於這個BaseAdapter的抽象原理,以及使用方式有了大概的瞭解。

需要注意的是為了方便簡單演示,我們這裡假設是,沒有在xml中直接使用Databinding進行繫結。因為有些複雜邏輯我們是沒有辦法簡單的在xml中進行繫結的。

圖片描述

很顯然我們的工作並沒有到此結束,因為我們的adapter在常用的場景中還有多佈局的情況,那我們又應該如何處理呢。

這個其實很好辦。因為我們是多佈局,那麼就意味著我們需要把onCreateViewHolder中的一部分工作暴露給子類處理,所以我們需要在上面BaseAdapter的基礎上做一些修改。照例上程式碼:

abstract class BaseMultiTypeAdapterT> : RecyclerView.AdapterBaseMultiTypeAdapter.MultiTypeViewHolder>() {

    private var mData: ListT> = mutableListOf()

    fun setData(data: ListT>?) {
        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: ListT>?, 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(): ListT> {
        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: ClassVB>,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)
}
複製程式碼

透過上面的程式碼可以看到,我們沒有在BaseMultiTypeAdapter中定義泛型VB :ViewDataBinding,因為我們是多佈局,如果都寫在類的定義中明顯是不合適的,我們也不知道在具體實現需要有多少個佈局。

所以我們onCreateViewHolder初始化佈局的時候呼叫了一個抽象的onCreateMultiViewHolder方法,這個方法交由我們具體業務實現類去實現。同時我們對onBindViewHolder進行修改,增加了一個holder引數供外部使用。 我們先資料實體型別

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)
複製程式碼

和我們需要實現的Adapter業務類,:

class SecondAdapter: BaseMultiTypeAdapterPerson>() {

    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")
            }
        }
    }
}
複製程式碼
class MainActivity : BaseActivityActivityMainBinding>() {
    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","語文"),
                        Student(2,"Person","一年級"),
                        Teacher(3,"Person","數學"),
                ))
    }
複製程式碼

執行一下就可以看到我們想要的結果:

D/ItemTeacherBinding: item : Teacher(id=1, name=Person, subject=語文)   position : 0
D/ItemStudentBinding: item : Student(id=2, name=Person, grade=一年級)   position : 1
D/ItemTeacherBinding: item : Teacher(id=3, name=Person, subject=數學)   position : 2
複製程式碼

經過上面的處理以後,我們在建立ActiviyFragmentAdapter的時候減少了大量的程式碼。同時也節省了碼這些重複垃圾程式碼的時間,起碼讓你們的工作效率起碼提升10個百分點,是不是感覺到自己無形中又變帥了許多。

圖片描述

經過以上封裝處理以後,我們是不是也可以對DialogPopWindow、動態初始化View進行處理呢。那還等什麼,趕緊去實現吧。畢竟授人以魚,不如授人以漁。

作者:一個被攝影耽誤的程式猿
連結:
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2797625/,如需轉載,請註明出處,否則將追究法律責任。

相關文章