0x3、ViewBinding基本用法
ViewBinding 的作用:代替findViewById,还可以保证空安全和类型安全,支持Java。
注:使用ViewBinding,AGP版本需 >= 3.6
接着介绍下基本用法,部分内容搬运自官方文档:《视图绑定》
① 启用ViewBinding
需要启用视图绑定的 Module
,在其 build.gradle
添加下述配置:
android { ... viewBinding { enabled = true } }
不需要生成绑定类的布局XML文件,可在根节点中添加下述属性:
<LinearLayout ... tools:viewBindingIgnore="true" > ... </LinearLayout>
编译后,AGP会为Module中包含的XML布局文件生成一个绑定类,类名规则:
XML文件名转换为Pascal大小写,并加上Binding,比如:result_profile.xml → ResultProfileBinding。
② 三个类绑定API
// View已存在 fun <T> bind(view : View) : T // View未存在 fun <T> inflate(inflater : LayoutInflater) : T fun <T> inflate(inflater : LayoutInflater, parent : ViewGroup?, attachToParent : Boolean) : T
接下来演示一波各种场景下的ViewBinding用法,其实都是围绕上述三个API进行的~
③ Activity
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 1、实例化绑定实例 binding = ActivityMainBinding.inflate(layoutInflater) // 2、获得对根视图的引用 val view = binding.root // 3、让根视图称为屏幕上的活动视图 setContentView(view) // 4、引用视图控件 binding.tvContent.text = "修改TextView文本" } }
④ Fragment
class ContentFragment: Fragment() { private var _binding: FragmentContentBinding? = null private val binding get() = _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentContentBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.ivLogo.visibility = View.GONE } override fun onDestroyView() { super.onDestroyView() // Fragment的存活时间比View长,务必在此方法中清除对绑定类实例的所有引用 // 否则会引发内存泄露 _binding = null } }
如果布局已inflated,还可以采用另一种写法(调bind):
class TestFragment: Fragment(R.layout.fragment_content) { private var _binding: FragmentContentBinding? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val binding = FragmentContentBinding.bind(view) _binding = binding binding.ivLogo.visibility = View.VISIBLE } override fun onDestroyView() { super.onDestroyView() // 同样需要置空 _binding = null } }
⑤ Dialog
如果是继承DialogFragment写法同Fragment,如果是继承Dialog写法示例如下(PopupWindow类似)~
class TestDialog(context: Context) : Dialog(context) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DialogTestBinding.inflate(layoutInflater) setContentView(binding.root) binding.tvTitle.text = "对话框标题" } }
⑥ RecyclerView
class TestAdapter(list: List<String>) : RecyclerView.Adapter<TestAdapter.ViewHolder>() { private var mList: List<String> = list override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // 需在此初始化以获得父类容器 val binding = ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.tvItem.text = "Adapter" } override fun getItemCount() = mList.size // 传递Binding对象 class ViewHolder(binding: ItemTestBinding) : RecyclerView.ViewHolder(binding.root) { var tvItem: TextView = binding.tvItem } }
⑦ 自定义ViewGroup
ViewGroup子类才能使用视图绑定,View子类不可使用,示例如下:
class TestLayout: LinearLayout { constructor(context: Context): super(context) constructor(context: Context, attrs: AttributeSet): super(context, attrs) { val inflater = LayoutInflater.from(this.context) val binding = ItemLayoutBinding.inflate(inflater, this, true) binding.tvLayout.text = "自定义ViewGroup" } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) } }
⑧ include
根据include的布局xml是否带**<merge>标签**,分为两种,先是不带的情况:
include的xml文件名为sub_include_test.xml,id为include_layout:
然后是带的情况,布局文件改下:
使用部分的代码不变,运行奔溃报错信息如下:
原因是merge并不会加载到布局里,解法:把include标签的id去掉,然后bind传入父布局~
⑨ ViewStub
基础用法很简单,也很好上手,但存在下述问题:
需重复编写:创建和回收ViewBinding实例的样板代码,特别是Fragment,还要手动置空。
所以有必要封装优化一波~
0x4、封装优化思路
① 泛型 + 父类实现模板代码
最容易想到的常规写法,配合泛型,把模板代码都在父类中写好,非常简单:
abstract class BaseFragment<T : ViewBinding>(layoutId: Int) : Fragment(layoutId) { private var _binding: T? = null val binding get() = _binding!! override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = initBinding(view) init() } abstract fun initBinding(view: View): T abstract fun init() override fun onDestroyView() { _binding = null super.onDestroyView() } } // 子类实现 class TestFragment : BaseFragment<FragmentContentBinding>(R.layout.fragment_content) { override fun initBinding(view: View) = FragmentContentBinding.bind(view) override fun init() { binding.ivLogo.visibility = View.VISIBLE } }