其他
AS 3.6 Canary 中推出新技术 视图绑定 View Binding
本文作者
作者:黎赵太郎
链接:
https://tonnyl.io/Hello-ViewBinding/
本文由作者授权发布。
我也稍微查了一下 ViewBinding,为了让大家更好理解,对文章的描述有些许的修改。
作为一个 Android 开发者, 几乎写代码都离不开 findViewById,当然这样模板式的代码,也出现了非常多简化能力的写法:
例如:
1. Butter Knife(Kotter Knife)
2. Kotlin Android Extensions
3. Data Binding
除此以外,还有 Google 在Android Studio 3.6 Canary 11 及以上版本正式推出的
4. View Binding
先介绍 Google 最新推出的 View Binding,然后比较上述方案。
什么是 View Binding ?
View Binding 是一项使你能更轻松地编写与视图交互的代码的功能. 在模块中启用 View Binding 后, 它会为该模块中存在的每一个 XML 文件生成一个对应的绑定类(binding class). 绑定类的实例包含了对应布局中所有具有 ID 的 view 的直接引用. 大多数情况下, View Binding 可以替换 findViewById.
使用要求
设置指南
android {
...
viewBinding {
enabled = true
}
}
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
使用方法
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
private lateinit var binding: ResultProfileBinding
fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
}
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
先回顾下历史的几种写法:
// findViewById
val fab = findViewById<FloatingActionButton>(R.id.fab)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
}
// Kotter Knife
val fab: FloatingActionButton by bindView(R.id.fab)
val toolbar: Toolbar by bindView(R.id.toolbar)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
}
// Kotlin Android Extensions
import kotlinx.android.synthetic.main.activity_main.*
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
}
// Data Binding & View Binding
val binding = ActivityMainBinding.inflate(layoutInflater)
setSupportActionBar(binding.toolbar)
binding.fab.setOnClickListener { view ->
}
优雅程度
类型安全
// API 26 之前
public final View findViewById(int id) {
if (id < 0) {
return null;
}
return findViewTraversal(id);
}
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
return null;
}
// API 26 及以后
public final <T extends View> T findViewById( int id) {
if (id == NO_ID) {
return null;
}
return findViewTraversal(id);
}
protected <T extends View> T findViewTraversal( int id) {
if (id == mID) {
return (T) this;
}
return null;
}
空安全
import kotlinx.android.synthetic.main.fragment_main.*
class MainFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_main, container, false)
text.setOnClickListener {
}
return rootView
}
}
<!-- fragment_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout ...>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/text"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
Caused by: android.view.InflateException: Binary XML file line #24 in io.tonnyl.demo:layout/activity_main: Binary XML file line #8 in io.tonnyl.demo:layout/content_main: Error inflating class fragment
Caused by: android.view.InflateException: Binary XML file line #8 in io.tonnyl.demo:layout/content_main: Error inflating class fragment
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.widget.AppCompatTextView.setOnClickListener(android.view.View$OnClickListener)' on a null object reference
public final class MainFragment extends Fragment {
private HashMap _$_findViewCache;
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Intrinsics.checkParameterIsNotNull(inflater, "inflater");
View rootView = inflater.inflate(1300003, container, false);
((AppCompatTextView)this._$_findCachedViewById(id.text)).setOnClickListener((OnClickListener)null.INSTANCE);
return rootView;
}
public View _$_findCachedViewById(int var1) {
if (this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(var1);
if (var2 == null) {
View var10000 = this.getView();
if (var10000 == null) {
return null;
}
var2 = var10000.findViewById(var1);
this._$_findViewCache.put(var1, var2);
}
return var2;
}
}
问题就出在 _$_findCachedViewById 方法 this.getView() 这一行, 调用时 onCreateView() 方法还没有返回值, 所以 this.getView() 返回 null, 在 onCreateView 中调用 text.setOnClickListener {} 不会有任何的错误提示, 因为 text 在这里会被认为是非空的.
import kotlinx.android.synthetic.main.fragment_main.view.*
class MainFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_main, container, false)
rootView.text.setOnClickListener {}
return rootView
}
}
public final class MainFragment extends Fragment {
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Intrinsics.checkParameterIsNotNull(inflater, "inflater");
View rootView = inflater.inflate(1300003, container, false);
Intrinsics.checkExpressionValueIsNotNull(rootView, "rootView");
((AppCompatTextView)rootView.findViewById(id.text)).setOnClickListener((OnClickListener)null.INSTANCE);
return rootView;
}
}
val fab = findViewById<FloatingActionButton>(View.NO_ID)
val fab2: FloatingActionButton by bindView(0)
结语
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!