当前位置:   article > 正文

Android Jectpack-ViewBinding 实践(kotlin实现)_reified viewbinding

reified viewbinding


I. 文档

官方文档

id kotlin-android-extensions 插件过时了,官方推荐 ViewBinding
以下为官方文档中的一段话:

与 findViewById 的区别
与使用 findViewById 相比,视图绑定具有一些很显著的优点:
Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用 @Nullable 标记。
类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。
这些差异意味着布局和代码之间的不兼容将会导致构建在编译时(而非运行时)失败。


II. 配置及简要说明

[application Module]/build.gradle

android {
 //viewBinding {
 //  enabled = true
 //} //本写法已在 stuido4.0过时 换成如下
 buildFeatures {
   viewBinding = true
 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 插件会将 ab_cd_ef.xml 生成一个 AbCdEfBinding 的类(即去掉下划线,并驼峰式)。

  • 会对布局xml中的view id 生成 驼峰式类名。
    源码位置:build/generated/data_binding_base_class_source_out/debug/out/[package name]/databinding/PrintDialogPrinterConnectBinding.java

  • <include id="@+id/includeLayout"../> ,要想访问include layout 内的子view,子view id为 “testTv”,只需:XxxBinding.IncludeLayout.testTv。

  • 若不想生成 Binding 文件。在根视图中使用 tools:viewBindingIgnore="true"


III. 源码

public interface ViewBinding {
    /**
     * Returns the outermost {@link View} in the associated layout file. If this binding is for a
     * {@code <merge>} layout, this will return the first view inside of the merge tag.
     */
    @NonNull
    View getRoot();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

所有生成的 Binding类都实现了该接口。


IV. 一个生成类的源码

public final class PrintDialogPrinterConnectBinding implements ViewBinding {
  
    @NonNull
    public ConstraintLayout getRoot() {
      return rootView;
    }

    @NonNull
    public static PrintDialogPrinterConnectBinding inflate(@NonNull LayoutInflater inflater {
      return inflate(inflater, null, false);
    }

    @NonNull
    public static PrintDialogPrinterConnectBinding inflate(@NonNull LayoutInflater inflater,
                                                           @Nullable ViewGroup parent, boolean attachToParent) {
      View root = inflater.inflate(R.layout.print_dialog_printer_connect, parent, false);
      if (attachToParent) {
        parent.addView(root);
      }
      return bind(root);
    }
                                                           
  @NonNull
  public static PrintDialogPrinterConnectBinding 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.
    int id;
    missingId: {
      id = R.id.dialog_printer_connect_close;
      ImageView dialogPrinterConnectClose = rootView.findViewById(id);
      if (dialogPrinterConnectClose == null) {
        break missingId;
      }
      if ...
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • getRoot() 获取根视图 view
  • inflate(@NonNull LayoutInflater inflatere) ==> inflate(inflate, null, false)
  • inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent) ==> bind(root)
  • bind(@NonNull View rootView)

外部使用时,通常用 inflate() 的两个函数。
最终都会调用 bind()。
所以可以 不使用Binding类的 inflate(),而通过其它方式 inflate 出 rootView, 再调用XxxBinding.bind(rootView) 。


V. 使用方法

通过 XxxBinding.getRoot() ,获取到根视图,然后 被 setContentView(root)、被Fragment#onCreateView() 用作返回值…


VI. 封装

基于ViewBind使用反射方式;

import android.app.Dialog
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding

/**
 * AppCompatActivity#onCreate()使用
 */
inline fun <reified VB: ViewBinding> AppCompatActivity.inflate(): VB {
    return inflateBinding<VB>(layoutInflater).apply {
        setContentView(this.root)
    }
}

/**
 * Dialog#onCreate() 使用
 */
inline fun <reified VB: ViewBinding> Dialog.inflate(): VB {
    return inflateBinding<VB>(layoutInflater).apply {
        setContentView(this.root)
    }
}

/**
 * 继承自 ViewGroup 使用
 */
inline fun <reified VB: ViewBinding> ViewGroup.inflate(viewGroup: ViewGroup, attachToRoot: Boolean = true): VB {
    return inflateBinding(LayoutInflater.from(context), viewGroup, attachToRoot)
}

/**
 * Recycler.Adapter#onCreateViewHolder() 使用
 */
inline fun <reified VB: ViewBinding> inflate(parent: ViewGroup): VB {
    return inflateBinding(LayoutInflater.from(parent.context), parent, false)
}

/**
 * 这是一个基础方法。所有创建ViewBinding对象的地方都可以直接调用。
 * 反射调用 ViewBinding.inflate(layoutInflater, viewGroup, attachToRoot) 。
 * 对于 Fragment、DialogFragment 都直接使用本方法。
 * 调用时,viewGroup可以不传,默认为null。
 */
@Suppress("UNCHECKED_CAST")
inline fun <reified VB: ViewBinding> inflateBinding(layoutInflater: LayoutInflater, viewGroup: ViewGroup? = null, attachToRoot: Boolean = false): VB {
    return VB::class.java.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
        .invoke(null, layoutInflater, viewGroup, attachToRoot) as VB
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

VI.i. 一个DialogFragment例子

class TestDialogFragment : DialogFragment() {

    private lateinit var mViewBind: DialogFragmentTestBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        mViewBind = inflateBinding(inflater)
        //mViewBind = inflateBinding(inflater, container)
        return mViewBind.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        mViewBind.tvTest.setOnClickListener {
            Toast.makeText(requireContext(), "test msg", Toast.LENGTH_SHORT).show()
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
//activity 中启动
TestDialogFragment().show(suuportFragmentManager, "tag-test")
//fragment 中启动
TestDialogFragment().show(childFragmentManager, "tag-test")
  • 1
  • 2
  • 3
  • 4

VI.ii. 强制Fragment子类实例化ViewBinding对象

abstract class BaseExtendFragment: Fragment() {

    private var mBinding: ViewBinding? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mBinding = (getViewBind(inflater, container, savedInstanceState) as ViewBinding)
        return mBinding?.root
    }

    abstract fun <VB: ViewBinding> getViewBind(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): VB

	override fun onDestroyView() {
        super.onDestroyView()
        mBinding = null
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

官方文档中示例是要在 onDestroyView() 中 置空 ViewBinding 对象的。
这样抽象,还是需要每个子Fragment , 声明自己的 XxxViewBinding 对象。


/**
 * desc:
 * author:  stone
 * email:   aa86799@163.com
 * time:    3/10/21 15:59
 */
class MeFragment : BaseExtendFragment() {

    private lateinit var mViewBind: FragmentMeBinding

    @Suppress("UNCHECKED_CAST")
    override fun <VB : ViewBinding> getViewBind(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): VB {
        mViewBind = inflateBinding(inflater, container)
        return mViewBind as VB
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

尝试过 在 BaseExtendFragment<T: ViewBinding> 这样声明。是会精简一些重复代码,然而在多重Fragment抽象与继承,加 permisstion-diapster 框架时,无法通过编译。


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/285393
推荐阅读
相关标签
  

闽ICP备14008679号