During our development, we needed to get the ViewId in the XML layout file to display the assignment, and we used to use findViewById, which resulted in a lot of template code. Until Jake Wharton open-source the Butter Knife framework to Bind ViewId. With Google’s support for Kotlin in recent years, we started using the Android Kotlin Extensions. Import the layout file in the file and reference viewId directly. No additional operations required, most convenient. Google has now added ViewBinding to Android Studio 3.6 Canary 11 and later.
Note: to use the ViewBinding feature, AndroidStudio needs to be upgraded to at least 3.6.
ViewBinding introduction
What is a ViewBinding?
- As the name implies, ViewBinding means how to bind a view to your code. So the main problem is how to safely and elegantly refer from code to view controls in XML layout files. Until now, the dominant way to build user interfaces on Android has been to use LAYOUT files in XML format. ViewBinding website
View Binding benefits:
Methods to access the view | concise | Compile safety | Compilation speed |
---|---|---|---|
findViewById | x | x | Square root |
DataBinding | Square root | Square root | x |
ButterKnife | Square root | x | x |
Kotlin Synthetic | Square root | x | Square root |
View Binding | Square root | Square root | Square root |
- Null security: Because view binding creates a direct reference to the view, there is no risk that Null pointer exceptions will be thrown because the view ID is invalid. In addition, if the view appears only in some configuration of the layout, the fields in the binding class that contain its references are used
@Nullable
The tag. - Type safety: Fields in each binding class have a type that matches the view they reference in the XML file. This means that there is no risk of class conversion exceptions.
The use of ViewBinding
ViewBinding can be enabled by module. To enable it in a module, add the following configuration to the build.gradle for that module:
android {
...
buildFeatures {
viewBinding = true
}
...
}
Copy the code
The recompilation generates a Binding class for each layout file that contains direct references to all views with ids in the layout. The generated classes are stored in the /build/generated/data_binding_base_class_source_out directory.
If you want to ignore a layout file when generating binding classes, add tools:viewBindingIgnore=”true” to the root view of the corresponding layout file:
<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" <!--Ignore generating layout file properties-->
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>
Copy the code
ViewBinding Use instance
Use the Activity
Once the view binding function is used, a binding class is generated for each XML layout file contained in the module. The class name is named after the XML layout file name with the word uppercase and Binding removed after the drop line. For example, activity_main.xml generates the class ActivityMainBinding.
-
To set the binding in your Activity, perform the following steps in the Activity’s onCreate() method:
- Call the static contained in the generated binding class
inflate()
Methods. This action creates an instance of the binding class for the Activity to use - By calling the
getRoot()
Method or useKotlin attribute syntaxGets a reference to the root view. - Pass the root view to setContentView() to make it the active view on the screen.
- Call the static contained in the generated binding class
-
activity_main.xml:
<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="Here's the button."
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>
Copy the code
// MainActivity.java:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Key code
binding = ActivityMainBinding.inflate(getLayoutInflater());
View rootView = binding.getRoot();
setContentView(rootView);
// How to use it
binding.textView.setText("This is modified.");
binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("Main"."Click the button."); }}); }}Copy the code
Fragments using
- How to use view binding in your Fragment Do the following steps in your Fragment’s onCreateView() method (Note: Fragments exist longer than their views. Be sure to clear all references to the bound class instance in the Fragment onDestroyView() method. )
- Call the static contained in the generated binding class
inflate()
Methods. This creates an instance of the bound class for use by the Fragment. - By calling the
getRoot()
Method or useKotlin attribute syntaxGets a reference to the root view. - from
onCreateView()
Method returns the root view, making it the active view on the screen. You can use the bind(view) method in onViewCreated() if the view is already created.The official sample
- Call the static contained in the generated binding class
- fragment_my.xml
<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="This is the Fragment button."
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="It's 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>
Copy the code
- MyFragment.java
public class MyFragment extends Fragment {
private FragmentMyBinding binding;
public MyFragment(a) {}@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("It's fragments");
binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("Fragment"."Click the button."); }}); }@Override
public void onDestroy(a) {
super.onDestroy();
binding = null;
}
Copy the code
- Use a View Binding in onViewCreated
public class MyFragment extends Fragment {
private FragmentMyBinding binding;
public MyFragment(a) {}@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("It's fragments");
binding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("Fragment"."Click the button."); }}); }@Override
public void onDestroy(a) {
super.onDestroy();
binding = null;
}
Copy the code
Encapsulate the ViewBinding base class
Encapsulate base classes through reflective methods.
Java packages
- The Activity of the base class
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(); }}}/ / use
public class MainActivity extends BaseActivity<ActivityMainBinding> {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewBinding.button.setText("This is MainActivity ViewBinding");
viewBinding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MainView"."Click the button"); }}); }}Copy the code
- Fragments base class
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();
}
returnviewBinding.getRoot(); }}/ / use
public class MainFragment extends BaseFragment<FragmentMainBinding>{
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewBinding.button.setText("This is the MainFragment ViewBinding");
viewBinding.button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MainView"."Click the button"); }}); }}Copy the code
Kotlin encapsulation
- The Activity to encapsulate
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)
}
}
/ / use
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
binding.textView.text = "It's MainActivity"}}Copy the code
- Fragments encapsulation
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 = method.invoke(null,layoutInflater,container,false) as T
return binding.root
}
}
/ / use
class FirstFragment : BaseFragment<FragmentFirstBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
binding.textView.text = "It's FirstFragment"}}Copy the code
Encapsulate base classes without using reflective methods.
- Kotlin encapsulates the Activity
abstract class BaseActivity<T : ViewBinding> : AppCompatActivity() {
private lateinit var _binding: T
protected val binding get() = _binding;
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
_binding = getViewBinding()
setContentView(_binding.root)
}
protected abstract fun getViewBinding(a): T
}
/ / use
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
binding.textView.text = "It's MainActivity"
}
override fun getViewBinding(a) = ActivityMainBinding.inflate(layoutInflater)
}
Copy the code
- Kotlin encapsulation fragments
abstract class BaseFragment<T : ViewBinding> : Fragment() {
private lateinit var _binding: T
protected val binding get() = _binding;
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup? , savedInstanceState:Bundle?).: View? {
_binding = getViewBinding(inflater, container)
return _binding.root
}
protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?).: T
}
/ / use
class FirstFragment : BaseFragment<FragmentFirstBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?). {
super.onViewCreated(view, savedInstanceState)
binding.textView.text = "It's FirstFragment"
}
override fun getViewBinding(
inflater: LayoutInflater,
container: ViewGroup?). = FragmentFirstBinding.inflate(inflater, container, false)}Copy the code
ViewBinding confusion
-keepclassmembers class * implements androidx.viewbinding.ViewBinding { public static ** inflate(...) ; public static ** bind(***); }Copy the code