背景
之前我们通过Kotlin Android Extensions来访问布局文件中的元素,但是这个现在被废弃了,原因如下:
- 空安全:res下的任何id都可以被访问,有可能因访问了非当前Layout下的id而出错
- 兼容性:只能在kotlin中使用,java不能用
- 局限性:不能跨模块使用
访问布局中元素方法
- findViewById
- ButterKnife
- DataBinding
- Kotlin Android Extensions
- ViewBinding
ViewBinding
2019年Google I/O大会上公布的一款Android视图绑定工具:ViewBinding。使用方式类似DataBinding,但相比DataBinding,ViewBinding是一个更轻量级、更纯粹的findViewById的替代方案。它具有如下优点:
- 类型安全: ViewBinding会基于布局中的View生成类型正确的属性。比如,在布局中放入了一个 TextView ,视图绑定就会暴露出一个 TextView 类型的属性供开发中使用。
- 空安全:ViewBinding会检测某个视图是否只在某些配置下存在,并依据结果生成带有 @Nullable 注解的属性,所以即使在多种配置下定义的布局文件,视图绑定依然能够保证空安全。
- ViewBinding生成的绑定类是一个Java类,并且添加了Kotlin注解,可以很好地支持 Java 和 Kotlin 两种编程语言。
使用详解
- Activity中基础使用方式
- 修改build.gradle文件
在android节点下增加buildFeatures 信息,具体如下:
android {...buildFeatures {viewBinding = true}
}
- Activity中调用方式
class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)binding.textView1.text = "textView1"}
}
注意:ActivityMainBinding是根据布局文件activity_main.xml来生成的。
- 代码变化点说明
之前的setContentView(R.layout.activity_main)
修改为了:
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
访问布局文件中元素时,使用:binding.textView1
这样的命名方式即可。
2. 布局中引用其他布局
- 主布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/textView1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="111111" /><includeandroid:id="@+id/subView"layout="@layout/view_sub" />
</LinearLayout>
注意:必须要给被引用布局设置id属性。
- 被引用布局文件view_sub.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><TextViewandroid:id="@+id/textView2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="222222" />
</LinearLayout>
- 调用方法
binding.subView.textView2.text = "textView2"
- 引用merge布局
- 主布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/textView1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="111111" /><include layout="@layout/view_merge" />
</LinearLayout>
- 被引用布局文件view_merge.xml
<merge xmlns:android="http://schemas.android.com/apk/res/android"><TextViewandroid:id="@+id/textView3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="333333" />
</merge>
- 调用方法
class MainActivity : AppCompatActivity() {private lateinit var binding: ActivityMainBindingprivate lateinit var viewMergeBinding: ViewMergeBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding = ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)viewMergeBinding = ViewMergeBinding.bind(binding.root)viewMergeBinding.textView3.text = "textView3"}
}
- 在Activity基类中使用
- 基类BaseActivity
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {protected lateinit var binding: Tabstract fun initBinding(): Tabstract fun init()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)binding = initBinding()setContentView(binding.root)init()}
}
- 子类MainActivity
class MainActivity : BaseActivity<ActivityMainBinding>() {override fun initBinding() = ActivityMainBinding.inflate(layoutInflater)override fun init() {binding.textView1.text = "textView1"}
}
- Fragment用法
- 布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/holo_blue_light"android:gravity="center"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="第一个Fragment" /></LinearLayout>
- 调用方式
class FirstFragment : Fragment() {private var _binding: FragmentFirstBinding? = nulloverride fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {_binding =FragmentFirstBinding.inflate(LayoutInflater.from(container?.context), container, false)return _binding?.root}override fun onDestroyView() {_binding = nullsuper.onDestroyView()}
}
- Fragment基类用法
- 布局文件同上
- 调用方式
class FirstFragment : BaseFragment<FragmentFirstBinding>() {override fun initBinding() = FragmentFirstBinding.inflate(layoutInflater)override fun init() {_binding?.textView1?.text="textView1"}
}
- RecyclerView中使用
篇幅有限,只列出有变化的Adapter类:
class RvAdapter : RecyclerView.Adapter<RvAdapter.MyViewHolder>() {private var mDataList = mutableListOf<String>()private lateinit var mContext: Contextoverride fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {mContext = parent.contextval itemBinding =ItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)return MyViewHolder(mContext, itemBinding)}override fun onBindViewHolder(viewHolder: MyViewHolder, position: Int) {val data = mDataList[position]viewHolder.bind(data)}fun setData(dataList: List<String>) {mDataList.clear()mDataList.addAll(dataList)notifyDataSetChanged()}override fun getItemCount(): Int = mDataList.sizeclass MyViewHolder(private var context: Context, private var itemBinding: ItemLayoutBinding) :RecyclerView.ViewHolder(itemBinding.root) {fun bind(data: String) {//更新UI上nameTv展示内容itemBinding.nameTv.text = data//设置点击事件itemBinding.root.setOnClickListener {Toast.makeText(context, data, Toast.LENGTH_SHORT).show()}}}
}
关于我
厦门大学计算机专业|华为八年高级工程师
十年软件开发经验,5年编程培训教学经验
目前从事编程教学,软件开发指导,软件类毕业设计指导。
参考资料
https://blog.csdn.net/qq_20521573/article/details/110278319
https://weilu.blog.csdn.net/article/details/109557820
https://www.jianshu.com/p/f284dd13b953