美文网首页
ViewBinding与RecycleView(一)

ViewBinding与RecycleView(一)

作者: 魁地奇 | 来源:发表于2020-04-17 15:14 被阅读0次

    如何使用

    在Android Studio 3.6的稳定版本中,我们就可以使用ViewBinding替代findViewById

    官方介绍

    另外关于ViewBindingKotlin Android Extensions的区分这里不多做介绍,
    可以参考下stackoverflow中的讨论

    ViewBinding如何使用?如果是Kotlin DSL的话这样添加:

     android {
            ...
            viewBinding.isEnabled = true
    
    }
        
    

    否则:

    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中:

    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

    // 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));
      }
    }
    
    

    与RecycleView结合

    关注下ActivityTabBinding类中的inflate方法是不是跟我们RecycleView中的onCreateViewHolder方法特别像?创建View root时都不会将其添加到父对象ViewGroup上,一般我们创建ViewHolder像这样:

    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中的创建 :

    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属性委托

    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特性将这些行为进一步抽取?......

    相关文章

      网友评论

          本文标题:ViewBinding与RecycleView(一)

          本文链接:https://www.haomeiwen.com/subject/crqyvhtx.html