Android Jectpack-ViewBinding 实践(kotlin实现)

Posted 匆忙拥挤repeat

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Jectpack-ViewBinding 实践(kotlin实现)相关的知识,希望对你有一定的参考价值。

文章目录


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
 

  • 插件会将 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();

所有生成的 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 ...
    
  

  • 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

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()
        
    

//activity 中启动
TestDialogFragment().show(suuportFragmentManager, "tag-test")
//fragment 中启动
TestDialogFragment().show(childFragmentManager, "tag-test")

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
    

官方文档中示例是要在 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
    

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


开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于Android Jectpack-ViewBinding 实践(kotlin实现)的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向Android 权限 ( Android 逆向中使用的 android.permission 权限 | Android 系统中的 Linux 用户权限 )

android 21 是啥版本

Android逆向-Android基础逆向(2-2)

【Android笔记】android Toast

图解Android - Android核心机制

Android游戏开发大全的目录