ViewBinding与RecycleView(一)

如何使用

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容