再见findViewById之View Binding
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再见findViewById之View Binding相关的知识,希望对你有一定的参考价值。
在我们的开发过程中,需要获取XML布局文件中的ViewId,以便其赋值显示,我们习惯使用findViewById进行操作,可这样会导致很多的模版代码出现。直到android大神 Jake Wharton开源了Butter Knife框架,通过Bind方式绑定获取ViewId。近几年谷歌对Kotlin的支持,我们开始使用 Android Kotlin extensions。在文件中导入布局文件直接引用viewId。无需做其他额外操作,最为方便。目前,谷歌在 Android Studio 3.6 Canary 11 及更高版本中加入了新的视图绑定方式ViewBinding。
注意:要使用ViewBinding功能,AndroidStudio至少要升级到3.6。
ViewBinding 介绍
ViewBinding 是什么?
- 顾名思义ViewBinding的意思就是如何将view与代码绑定在一起。所以其主要解决如何安全优雅地从代码中引用到XML layout文件中的view控件的问题。直到目前为止,Android构建用户界面的主流方式仍然是使用XML格式的layout文件。 ViewBinding官网
View Binding 的优点:
访问视图的方法 | 简明 | 编译安全 | 编译速度 |
---|---|---|---|
findViewById | × | × | √ |
DataBinding | √ | √ | × |
ButterKnife | √ | × | × |
Kotlin Synthetic | √ | × | √ |
View Binding | √ | √ | √ |
- Null 安全:由于视图绑定会创建对视图的直接引用,因此不存在因视图 ID 无效而引发 Null 指针异常的风险。此外,如果视图仅出现在布局的某些配置中,则绑定类中包含其引用的字段会使用
@Nullable
标记。 - 类型安全:每个绑定类中的字段均具有与它们在 XML 文件中引用的视图相匹配的类型。这意味着不存在发生类转换异常的风险。
ViewBinding 的使用
ViewBinding可按模块启用。要在某个模块中启用,在该模块的build.gradle中添加如下配置:
android
...
buildFeatures
viewBinding = true
...
重新编译后系统会为每个布局文件生成对应的Binding类,该类中包含对应布局中具有 ID 的所有视图的直接引用。生成类的目录在 模块根目录/build/generated/data_binding_base_class_source_out
下。
如果想在生成绑定类时忽略某个布局文件,将 tools:viewBindingIgnore="true"
属性添加到相应布局文件的根视图中:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true" <!--忽略生成布局文件属性-->
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
ViewBinding 使用实例
Activity 使用
为用视图绑定功能后,系统会为该模块中包含的每个 XML 布局文件生成一个绑定类。这个类的类名是以xml布局文件名去掉下换线后,单词首字母大写加上Binding命名的。如activity_main.xml生成的类ActivityMainBinding.
-
如何在Activity中设置绑定,请在 Activity 的 onCreate() 方法中执行以下步骤:
- 调用生成的绑定类中包含的静态
inflate()
方法。此操作会创建该绑定类的实例以供 Activity 使用 - 通过调用
getRoot()
方法或使用 Kotlin 属性语法获取对根视图的引用。 - 将根视图传递到 setContentView(),使其成为屏幕上的活动视图。
- 调用生成的绑定类中包含的静态
-
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="100dp"
android:layout_height="40dp"
android:text="这是按钮"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/textView"
android:layout_width="100dp"
android:layout_height="40dp"
android:gravity="center"
android:text="Hello World!"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
// MainActivity.java:
public class MainActivity extends AppCompatActivity
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
//关键代码
binding = ActivityMainBinding.inflate(getLayoutInflater());
View rootView = binding.getRoot();
setContentView(rootView);
//如何使用
binding.textView.setText("这是修改的");
binding.button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Log.d("Main","点击了按钮");
);
Fragment 使用
- 如何在 Fragment 中使用视图绑定 请在 Fragment 的 onCreateView()方法中执行以下步骤(注意:Fragment 的存在时间比其视图长。请务必在 Fragment 的 onDestroyView() 方法中清除对绑定类实例的所有引用。 )
- 调用生成的绑定类中包含的静态
inflate()
方法。此操作会创建该绑定类的实例以供 Fragment 使用。 - 通过调用
getRoot()
方法或使用 Kotlin 属性语法获取对根视图的引用。 - 从
onCreateView()
方法返回根视图,使其成为屏幕上的活动视图。如果view已经创建可以在onViewCreated()中使用bind(view)方法。官方示例
- 调用生成的绑定类中包含的静态
- fragment_my.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="这是Fragment按钮"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:gravity="center"
android:text="这是FragmentTextView"
app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- MyFragment.java
public class MyFragment extends Fragment
private FragmentMyBinding binding;
public MyFragment()
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
binding = FragmentMyBinding.inflate(inflater, container, false);
return binding.getRoot();
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
binding.textView.setText("这是Fragment");
binding.button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Log.d("Fragment", "点击了按钮");
);
@Override
public void onDestroy()
super.onDestroy();
binding = null;
- 在onViewCreated中使用View Binding
public class MyFragment extends Fragment
private FragmentMyBinding binding;
public MyFragment()
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
return inflater.inflate(R.layout.fragment_my,container,false);
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
FragmentMyBinding binding = FragmentMyBinding.bind(view);
this.binding = binding;
binding.textView.setText("这是Fragment");
binding.button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Log.d("Fragment", "点击了按钮");
);
@Override
public void onDestroy()
super.onDestroy();
binding = null;
封装 ViewBinding 基类
通过反射的方法封装基类。
java 封装
- Activity的基类
public class BaseActivity<T extends ViewBinding> extends AppCompatActivity
protected T viewBinding;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[0];
try
Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
viewBinding = (T) inflate.invoke(null, getLayoutInflater());
setContentView(viewBinding.getRoot());
catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e)
e.printStackTrace();
//使用
public class MainActivity extends BaseActivity<ActivityMainBinding>
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
viewBinding.button.setText("这是 MainActivity ViewBinding");
viewBinding.button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Log.d("MainView","点击按钮");
);
- Fragment 基类
public class BaseFragment<T extends ViewBinding> extends Fragment
protected T viewBinding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
Class cls = (Class) type.getActualTypeArguments()[0];
try
Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
viewBinding = (T) inflate.invoke(null, inflater, container, false);
catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e)
e.printStackTrace();
return viewBinding.getRoot();
//使用
public class MainFragment extends BaseFragment<FragmentMainBinding>
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
super.onViewCreated(view, savedInstanceState);
viewBinding.button.setText("这是 MainFragment ViewBinding");
viewBinding.button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Log.d("MainView","点击按钮");
);
kotlin 封装
- Activity 封装
open class BaseActivity<T : ViewBinding> : AppCompatActivity()
protected lateinit var binding: T
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
val type = javaClass.genericSuperclass as ParameterizedType
val aClass = type.actualTypeArguments[0] as Class<*>
val method = aClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
binding = method.invoke(null, layoutInflater) as T
setContentView(binding.root)
//使用
class MainActivity : BaseActivity<ActivityMainBinding>()
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
binding.textView.text = "这是MainActivity"
- Fragment 封装
open class BaseFragment<T:ViewBinding>:Fragment()
lateinit var binding: T
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
val type = javaClass.genericSuperclass as ParameterizedType
val aClass = type.actualTypeArguments[0] as Class<*>
val method = aClass.getDeclaredMethod("inflate", LayoutInflater::class.java,ViewGroup::class.java,Boolean::class.java)
binding <以上是关于再见findViewById之View Binding的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin Android Extensions: 与 findViewById 说再见 (KAD 04) -- 更新版
从 ListView 获取孩子会导致 android.view.View android.view.View.findViewById(int) 在空对象引用上
ListView 适配器中的 findViewById 与 View Holder 模式
getActivity().findViewById(R.layout.contacts_list_view) 返回 null