Skip to content

Instantly share code, notes, and snippets.

@phamducminh
Last active October 14, 2020 03:11
Show Gist options
  • Save phamducminh/858e0e3b06eeead2aadefa390d195d4c to your computer and use it in GitHub Desktop.
Save phamducminh/858e0e3b06eeead2aadefa390d195d4c to your computer and use it in GitHub Desktop.
Update image

How to Use View Binding in Project

View Binding là gì

Thay thế findViewById bằng cách tạo ra binding object ứng với mỗi xml layout file.

image

Cách hoạt động của View Binding

  • 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.
  • Có thể sử dụng binding class bất cứ nơi nào có inflate layout như Activity, Fragment, RecyclerView Adapter (hay ViewHolder).
  • 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.

Các phương thức chính của Binding class:

  • inflate(inflater): sử dụng phương thức này trong Activity 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ới Fragment hay RecyclerView Adapter (hay ViewHolder) khi cần pass parent 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àm findViewById. Phù hợp để sử dụng với ViewStub (check cách sử dụng bên dưới).

Cách sử dụng

Trong Activity

@Override
protected void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SplashBinding = SplashBinding.inflate(layoutInflater);
    setContentView(binding.getRoot());
    
    binding.tvVersionName.setText(getString(R.string.version));
}

Trong Fragment

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.

RecyclerView adapter

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

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);

Với tag trong layout

View binding cũng có thể dùng với <include> tag. Có 2 loại:

<include> không có <merge>

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

<include><merge>

Đố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>

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

References

@maxheisenberg22
Copy link

Để sử dụng Viewbinding với ViewStub, chỉ cần gọi bind sau khi chúng ta đã inflate layout của ViewStub.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <TextView
        android:text="start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/name"/>
    <ViewStub
        android:id="@+id/stub_import"
        android:inflatedId="@+id/panel_import"
        android:layout="@layout/progress_overlay"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
    ActivityMainBinding binding;
    ProgressOverlayBinding proBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);

        View viewStubRoot = binding.stubImport.inflate();
        proBinding = ProgressOverlayBinding.bind(viewStubRoot);
        proBinding.inside.setText("new text for inside");
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment