在Android Studio 3.6的穩定版本中,咱們就能夠使用ViewBinding替代findViewById
java
官方介紹android
另外關於ViewBinding
與Kotlin Android Extensions
的區分這裏很少作介紹,
能夠參考下stackoverflow中的討論json
ViewBinding如何使用?若是是Kotlin DSL的話這樣添加:app
android { ... viewBinding.isEnabled = true }
不然:ide
android { ... viewBinding { enabled = true } }
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.tabs.TabLayout app:layout_constraintTop_toTopOf="parent" android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="fixed" /> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/vp2" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/tabs" /> </androidx.constraintlayout.widget.ConstraintLayout>
而後在activity中:ui
private lateinit var mBinding: ActivityTabBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBinding = ActivityTabBinding.inflate(layoutInflater) setContentView(mBinding.root) attachTabsOnViewPager2() }
在app/buildle/generated/data_binding_base_class_source_out/...
目錄看下生成的ActivityTabBinding
類this
// Generated by view binder compiler. Do not edit! package tt.reducto.instantsearch.databinding; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.viewbinding.ViewBinding; import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.tabs.TabLayout; import java.lang.NullPointerException; import java.lang.Override; import java.lang.String; import tt.reducto.instantsearch.R; public final class ActivityTabBinding implements ViewBinding { @NonNull private final ConstraintLayout rootView; @NonNull public final TabLayout tabs; @NonNull public final ViewPager2 vp2; private ActivityTabBinding(@NonNull ConstraintLayout rootView, @NonNull TabLayout tabs, @NonNull ViewPager2 vp2) { this.rootView = rootView; this.tabs = tabs; this.vp2 = vp2; } @Override @NonNull public ConstraintLayout getRoot() { return rootView; } @NonNull public static ActivityTabBinding inflate(@NonNull LayoutInflater inflater) { return inflate(inflater, null, false); } @NonNull public static ActivityTabBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) { View root = inflater.inflate(R.layout.activity_tab, parent, false); if (attachToParent) { parent.addView(root); } return bind(root); } @NonNull public static ActivityTabBinding bind(@NonNull View rootView) { // The body of this method is generated in a way you would not otherwise write. // This is done to optimize the compiled bytecode for size and performance. String missingId; missingId: { TabLayout tabs = rootView.findViewById(R.id.tabs); if (tabs == null) { missingId = "tabs"; break missingId; } ViewPager2 vp2 = rootView.findViewById(R.id.vp2); if (vp2 == null) { missingId = "vp2"; break missingId; } return new ActivityTabBinding((ConstraintLayout) rootView, tabs, vp2); } throw new NullPointerException("Missing required view with ID: ".concat(missingId)); } }
關注下ActivityTabBinding
類中的inflate
方法是否是跟咱們RecycleView中的onCreateViewHolder
方法特別像?建立View root
時都不會將其添加到父對象ViewGroup
上,通常咱們建立ViewHolder
像這樣:google
class CategoryViewHolder constructor(itemView: View) : RecyclerView.ViewHolder(itemView) { constructor(parent: ViewGroup) : this(LayoutInflater.from(parent.context).inflate(R.layout.category_item, parent, false)) fun bind(category: Category) { itemView.categoryName.text = category.name itemView.categoryID.text = " " } }
因此咱們能夠給自定義ViewHolder類傳入ViewBinding引用 :代理
open class BaseBindingViewHolder<T : ViewBinding> private constructor(val mBinding: T) : RecyclerView.ViewHolder(mBinding.root) { // constructor( parent: ViewGroup, creator: (inflater: LayoutInflater, root: ViewGroup, attachToRoot: Boolean) -> T ) : this(creator(LayoutInflater.from(parent.context), parent, false)) }
咱們再給ViewGroup提供一個擴展方法省去ViewHolder在onCreateViewHolder
中的建立 :code
fun <T : ViewBinding> ViewGroup.getViewHolder( creator: (inflater: LayoutInflater, root: ViewGroup, attachToRoot: Boolean) -> T ): BaseBindingViewHolder<T> = BaseBindingViewHolder(this, creator)
利用ViewBinding 一個簡單的Adapter就這樣:
CategoryItemBinding
是根據xml文件自動生成的
class CategoryAdapter : RecyclerView.Adapter<BaseBindingViewHolder<CategoryItemBinding>>() { private var list: List<Category> = listOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseBindingViewHolder<CategoryItemBinding> { return parent.getViewHolder(CategoryItemBinding::inflate) } override fun onBindViewHolder(holder: BaseBindingViewHolder<CategoryItemBinding>, position: Int) { holder.mBinding.categoryName.text = list[position].name } fun setItem(list: List<Category>) { this.list = list notifyDataSetChanged() } override fun getItemCount(): Int = list.size }
綜上,這些是比較簡單的操做..
kotlin源碼中的實現判空的委託屬性:
/** * Standard property delegates. */ public object Delegates { /** * Returns a property delegate for a read/write property with a non-`null` value that is initialized not during * object construction time but at a later time. Trying to read the property before the initial value has been * assigned results in an exception. * * @sample samples.properties.Delegates.notNullDelegate */ public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar() ...... } private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> { private var value: T? = null public override fun getValue(thisRef: Any?, property: KProperty<*>): T { return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.") } public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } }
其中 NotNullVar 繼承了 ReadWriteProperty,並實現了他的兩個方法,而Delegates.notNull() 屬於委託屬性。
看一個自定義委託findViewById的例子:
class MainActivity : AppCompatActivity(){ private val etSearch : FixedKeyboardEditText by bindView(R.id.et_search) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) etSearch.setText("test") } } fun <T: View> bindView( id : Int): FindView<T> = FindView(id) class FindView<T : View >(val id:Int) : ReadOnlyProperty<Any?, T> { override fun getValue(thisRef: Any?, property: KProperty<*>): T { if(this.value == null) { this.value = (thisRef as Activity).findViewById<T>(id) } return this.value?:throw RuntimeException("can not find this view") } var value : T? = null }
若是咱們把itemView與數據源的綁定經過自定義委託來代理,那是否是會方便不少??
簡單說就是在一個map裏存儲屬性的值,能夠使用映射實例自身做爲委託來實現委託屬性。例如json解析
那itemView的setTag與getTag是否能夠放在MutableMap中進行處理?
adapter中還有大量工做須要去作,好比itemView的setTag、OnClickListener()、ViewHolder中進行數據源與itemView的綁定,那麼如何利用kotlin特性將這些行爲進一步抽取?......