封裝DataBinding讓你少寫萬行程式碼
筆者也只是一個普普通通的開發者,設計不一定合理,大家可以自行吸收文章精華,去糟粕。
現在我們就可以開始做一些基礎的封裝工作,同時在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
}
}
複製程式碼
這種情況下,每建立一個activity
和Fragment
都需要重寫一遍。所以我們建立一個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
的時候,透過使用layout
的activity_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
呼叫ViewBinding
的inflate
方法。然後我們再建立一個介面用於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
,我們都需要些一堆囉嗦程式碼,如果再加上item
的click
事件的話,我們要做的 工作就變得更多。那麼我們現在要解決這麼一個問題的話,我們要處理哪裡東西呢:
- 統一
Adapter
的初始化工作。 - 簡化
onBindViewHolder
的使用。 - 去掉每次都需要重複建立
ViewHolder
。 - 統一我們設定
Item
的監聽事件方式。 - 統一
Adapter
的資料重新整理。
首先我們需要修改一下我們之前定義的擴充套件getViewBinding
,因為我們是不知道具體這個getViewBinding
是用在哪個類上,這個類又定義了幾個泛型。所以我們增加一個預設值為0
的position
引數代替之前寫死的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
中的程式碼是不是變的超簡潔,我相信現在即使讓你再寫100
個Adapter
也不害怕。
現在我們來一步一步的拆解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
中已經處理了:
- 統一
Adapter
的初始化工作。 - 簡化
onBindViewHolder
的使用。 - 去掉每次都需要重複建立
ViewHolder
。 - 統一我們設定
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
中的areItemsTheSame
和areContentsTheSame
2個對比資料的方法,實際上是透過我們在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個方法,我們使用帶有position
的onItemClick
方法來演示:
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
複製程式碼
經過上面的處理以後,我們在建立Activiy
、Fragment
、Adapter
的時候減少了大量的程式碼。同時也節省了碼這些重複垃圾程式碼的時間,起碼讓你們的工作效率起碼提升10
個百分點,是不是感覺到自己無形中又變帥了許多。
經過以上封裝處理以後,我們是不是也可以對Dialog
,PopWindow
、動態初始化View
進行處理呢。那還等什麼,趕緊去實現吧。畢竟授人以魚,不如授人以漁。
作者:一個被攝影耽誤的程式猿
連結:
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4328/viewspace-2797625/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- CSS 程式碼線上生成工具,讓你少寫幾百行程式碼~CSS行程
- 能讓你少寫1000行程式碼的20個正規表示式行程
- 知道這20個正規表示式,能讓你少寫1,000行程式碼行程
- 為了讓你搞定資料庫選型,這些工程師重寫了 26 萬行程式碼資料庫工程師行程
- 如何讓Java編譯器幫你寫程式碼Java編譯
- 少壯不努力 老大寫程式碼
- 我一行程式碼都不寫實現Toolbar!你卻還在封裝BaseActivity?行程封裝
- MultiItem進階 使用DataBinding 讓 RecyclerView程式碼更簡潔清爽View
- 多些時間能少寫些程式碼
- 身為程式設計師寫一百萬行程式碼的感覺程式設計師行程
- 拿工資不僅僅是讓你寫程式碼的
- Android結合DataBinding封裝的BaseBindingAdapterAndroid封裝APT
- FMDB 二次封裝工具類,讓你快速學會封裝,整合資料庫封裝資料庫
- 六百萬行程式碼行程
- [積德篇] 如何少寫PHP "爛"程式碼PHP
- [積德篇] 如何少寫PHP “爛”程式碼PHP
- 【JS】裝飾器讓你的程式碼更簡潔JS
- javascript操作cookie程式碼封裝JavaScriptCookie封裝
- ScaleHeight 的封裝程式碼封裝
- 你寫註釋她幫你寫程式碼
- 寫了 50 萬行 Go 程式碼後,我明白這些道理Go
- IDEA 外掛推薦 —— 讓你寫出好程式碼的神器!Idea
- 為什麼寫程式碼讓人家覺得你很厲害?
- javascript字串操作程式碼封裝程式碼例項JavaScript字串封裝
- 畢業前寫了20萬行程式碼,讓我從成為同學眼裡的麵霸!行程
- 七個不一樣的Python程式碼寫法,讓你寫出一手漂亮的程式碼Python
- 微軟釋出2萬行Linux裝置驅動程式程式碼微軟Linux
- 不給程式碼寫文件,讓程式碼文件化
- 用Assert(斷言)封裝異常,讓程式碼更優雅(附專案原始碼)封裝原始碼
- 你得會寫程式碼。。。
- 為什麼程式設計師應該少寫程式碼程式設計師
- python程式碼怎麼封裝Python封裝
- retrofit如何配合Rxjava封裝程式碼RxJava封裝
- ECMAScript 6 之用模組封裝程式碼封裝
- 封裝我們的VBA程式碼封裝
- 【譯】五個ES6功能,讓你編寫程式碼更容易
- 編寫良好的程式碼:如何減少程式碼的認知負荷
- 為什麼程式設計師要儘量少寫程式碼程式設計師