Thay thế findViewById
bằng cách tạo ra binding object ứng với mỗi xml layout file.
- Enable trong build.gradle ứng với mỗi module muốn sử dụng view binding (không cần thêm bất cứ thư viện ngoài nào).
- View binding sẽ tạo ra binding object cho mọi xml layout có trong module được enable. Qui tắc đặt tên như sau:
about_view.xml
->AboutViewBinding.java
. - Ứng với mỗi view có khai báo id, binding object sẽ chứa một property cho view đó. Property sẽ bảo đảm đúng type (type-safety) và không null (null-safety).
- Type-safe: properties trong binding object sẽ luôn luôn đúng type dựa vào tag khai báo view đó trong xml. Ví dụ trong xml layout có một
TextView
thì view binding sẽ tạo ra 1 property có type làTextView
. - Null-safe: property được truyền trực tiếp vào hàm dựng của binding class với @NonNull annotation. Nếu có bất kì vấn đề gì xảy ra thì binding class sẽ fail ở compile-time thay vì run-time. Đảm bảo null-safety.
- Type-safe: properties trong binding object sẽ luôn luôn đúng type dựa vào tag khai báo view đó trong xml. Ví dụ trong xml layout có một
- Có thể sử dụng binding class bất cứ nơi nào có inflate layout như
Activity
,Fragment
, RecyclerViewAdapter
(hayViewHolder
). - Khi đổi tên id trong xml thì property của binding update sẽ được update real-time theo, không cần phải rebuild lại.
- Trong một module, khi không muốn generate ra binding class cho một layout nào đó thì thêm
tools:viewBindingIgnore=”true”
vào root view của layout đó. - Không sử dụng được view binding với Custom View.
inflate(inflater)
: sử dụng phương thức này trongActivity
onCreate
khi không có parent view để pass vào binding object.inflate(inflater, parent, attachToParent)
: sử dụng phương thức này vớiFragment
hay RecyclerViewAdapter
(hayViewHolder
) khi cần passparent
ViewGroup
vào binding object.bind(rootView)
: sử dụng phương thức này khi đã inflate view rồi và chỉ muốn dùng view binding để tránh lời gọi hàmfindViewById
. Phù hợp để sử dụng với ViewStub (check cách sử dụng bên dưới).
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SplashBinding = SplashBinding.inflate(layoutInflater);
setContentView(binding.getRoot());
binding.tvVersionName.setText(getString(R.string.version));
}
class HomeFragment extends Fragment {
private FragmentHomeBinding binding;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null
}
}
Lưu ý: ZaloView
cũng giống với Fragment
, nên nếu sử dụng view binding cho ZaloView
thì phải override lại hàm onDestroyView()
và gán null cho binding object.
public class TextFontAdapter extends RecyclerView.Adapter<TextFontAdapter.ViewHolder> {
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return ViewHolder.from(parent);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
TextFontItem fontItem = fontItems.get(position);
holder.bind(fontItem);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextFontItemBinding binding;
static ViewHolder from(ViewGroup parent) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
TextFontItemBinding binding = TextFontItemBinding.inflate(layoutInflater, parent, false);
return new ViewHolder(binding);
}
ViewHolder(TextFontItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(TextFontItem fontItem) {
binding.text.setText(fontItem.getText());
}
}
Trong đó text_font_item.xml
là file layout xml cho mỗi row của RecyclerView
, và tương ứng là binding class TextFontItemBinding.java
được tạo ra.
Với ViewStub
, binding object không được khai báo thông qua hàm inflate
lúc onCreateView
mà phải thông qua hàm inflate
của ViewStub
.
ViewStub stubText = findViewById(R.id.stub_camera_caption_layout);
CaptionView captionView = (CaptionView) stubText.inflate();
Nên để tạo được binding object cho view được inflate
thông qua ViewStub
, ta dùng phương thức bind(rootView)
của binding class trong onFinishInflate
@Override
protected void onFinishInflate() {
super.onFinishInflate();
binding = CaptionLayoutBinding.bind(this);
}
Từ đó về sau ta có thể truy xuất các property của binding object như bình thường:
binding.captionDone.setOnClickListener(this);
View binding cũng có thể dùng với <include>
tag. Có 2 loại:
Nếu layout được include vào không sử dụng tag <merge>
thì bắt buộc nơi khai báo tag <include>
phải khai báo android:id
để có thể truy xuất view bên trong layout được include.
Giả sử ta có một layout app_bar.xml
như sau:
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="?actionBarSize"
android:background="?colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
main_layout.xml
sẽ include layout app_bar.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/appbar"
layout="@layout/app_bar"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Để truy xuẩt được Toolbar
view trong app_bar.xml
bằng view binding. Ta làm như sau:
@Override
protected onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
MainLayoutBinding binding = MainLayoutBinding.inflate(layoutInflater);
setContentView(binding.getRoot());
setSupportActionBar(binding.appbar.toolbar)
}
binding
object sẽ truy xuất Toolbar
thông qua id appbar
được khai báo trong tag <include>
: binding.appbar.toolbar
Đối với layout sử dụng tag <merge>
(đọc thêm ở đây) để optimize ViewGroup hierarchy.
placeholder.xml
:
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/tvPlaceholder"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</merge>
Và placeholder.xml
layout này được include vào main_layout.xml
như sau:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/placeholder" />
</androidx.constraintlayout.widget.ConstraintLayout>
Nếu chúng ta thêm ID cho tag <include>
như cách ở trên thì view binding sẽ không generate ID trong binding class. Do đó ta không thể truy xuất view được.
Để xử lý case này, PlaceholderBinding.java
class sẽ được tạo ra từ placeholder.xml
, và ta phải gọi hàm bind()
với param truyền vào là root view của layout mà ta muốn include <merge>
layout vào.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = MainLayoutBinding.inflate(inflater, container, false);
PlaceholderBinding placeholderBinding = PlaceholderBinding.bind(binding.getRoot());
placeholderBinding.tvPlaceholder.setText(getString(R.string.please_wait));
return binding.getRoot();
}
Từ đó, ta sẽ truy xuất view bên trong layout được include vào bằng bindind object của layout đó: placeholderBinding.tvPlaceholder
Để sử dụng Viewbinding với ViewStub, chỉ cần gọi bind sau khi chúng ta đã inflate layout của ViewStub.