Bộ chuyển đổi liên kết chịu trách nhiệm thực hiện các lệnh gọi khung thích hợp đến đặt giá trị. Một ví dụ là đặt giá trị thuộc tính, chẳng hạn như gọi phương thức setText()
. Một ví dụ khác là thiết lập trình nghe sự kiện, chẳng hạn như gọi phương thức setOnClickListener()
.
Thư viện liên kết dữ liệu cho phép bạn chỉ định phương thức được gọi để đặt giá trị, cung cấp logic liên kết của riêng bạn và chỉ định loại đối tượng được trả về bằng cách sử dụng bộ chuyển đổi.
Đặt giá trị thuộc tính
Bất cứ khi nào giá trị ràng buộc thay đổi, lớp liên kết được tạo phải gọi một phương thức setter trên khung hiển thị bằng biểu thức liên kết. Bạn có thể để Thư viện liên kết dữ liệu tự động xác định phương thức, hoặc bạn có thể khai báo rõ phương thức hoặc cung cấp logic tuỳ chỉnh để chọn một phương thức.
Chọn phương thức tự động
Đối với thuộc tính có tên example
, thư viện sẽ tự động tìm phương thức setExample(arg)
chấp nhận các loại tương thích làm đối số. Không gian tên của thuộc tính không được xem xét. Chỉ tên và loại thuộc tính được sử dụng khi tìm kiếm một phương thức.
Ví dụ: với biểu thức android:text="@{user.name}"
, thư viện sẽ tìm một phương thức setText(arg)
chấp nhận loại do user.getName()
trả về. Nếu loại dữ liệu trả về của user.getName()
là String
, thư viện sẽ tìm phương thức setText()
chấp nhận đối số String
. Nếu biểu thức trả về một int
, thư viện sẽ tìm kiếm phương thức setText()
chấp nhận đối số int
. Biểu thức phải trả về loại dữ liệu chính xác. Bạn có thể truyền giá trị trả về nếu cần.
Tính năng liên kết dữ liệu hoạt động ngay cả khi không tồn tại thuộc tính nào có tên đã đặt. Bạn có thể tạo thuộc tính cho mọi phương thức setter bằng cách sử dụng tính năng liên kết dữ liệu. Ví dụ: lớp hỗ trợ DrawerLayout
không có các thuộc tính nhưng có nhiều phương thức setter. Bố cục sau đây sẽ tự động dùng phương thức setScrimColor(int)
và addDrawerListener(DrawerListener)
làm phương thức setter cho các thuộc tính app:scrimColor
và app:drawerListener
tương ứng:
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
Chỉ định tên phương thức tuỳ chỉnh
Một số thuộc tính có phương thức setter không khớp theo tên. Trong những trường hợp như vậy, bạn có thể liên kết một thuộc tính với phương thức setter bằng cách sử dụng chú giải BindingMethods
. Chú giải này được dùng với một lớp và có thể chứa nhiều chú giải BindingMethod
, mỗi chú giải tương ứng với một phương thức đã đổi tên. Phương thức liên kết là các chú giải mà bạn có thể thêm vào bất kỳ lớp nào trong ứng dụng của mình.
Trong ví dụ sau, thuộc tính android:tint
được liên kết với phương thức setImageTintList(ColorStateList)
chứ không phải với phương thức setTint()
:
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
Thông thường, bạn không cần đổi tên phương thức setter trong các lớp khung Android. Các thuộc tính đã được triển khai bằng cách sử dụng quy ước tên để tự động tìm các phương thức so khớp.
Cung cấp logic tuỳ chỉnh
Một số thuộc tính cần logic liên kết tuỳ chỉnh. Ví dụ: không có phương thức setter liên kết cho thuộc tính android:paddingLeft
. Thay vào đó, phương thức setPadding(left,
top, right, bottom)
sẽ được cung cấp. Phương thức bộ chuyển đổi liên kết tĩnh có chú giải BindingAdapter
cho phép bạn tuỳ chỉnh cách gọi phương thức setter cho một thuộc tính.
Các thuộc tính của các lớp khung Android đã có chú giải BindingAdapter
. Ví dụ sau đây cho thấy phương thức điều hợp liên kết (binding adapter) cho thuộc tính paddingLeft
:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
Loại thông số rất quan trọng. Tham số đầu tiên xác định loại của thành phần hiển thị liên kết với thuộc tính này. Tham số thứ hai xác định loại được chấp nhận trong biểu thức liên kết cho thuộc tính đã cho.
Bộ chuyển đổi liên kết cũng hữu ích cho các loại tuỳ chỉnh khác. Ví dụ: bạn có thể gọi trình tải tuỳ chỉnh từ một luồng worker để tải hình ảnh.
Bạn cũng có thể có các bộ chuyển đổi nhận nhiều thuộc tính, như trong ví dụ sau:
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
Bạn có thể sử dụng trình chuyển đổi trong bố cục, như trong ví dụ sau. Hãy lưu ý rằng @drawable/venueError
tham chiếu đến một tài nguyên trong ứng dụng của bạn. Việc xung quanh tài nguyên bằng @{}
sẽ khiến tài nguyên này trở thành một biểu thức liên kết hợp lệ.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
Bộ chuyển đổi được gọi nếu imageUrl
và error
được dùng cho một đối tượng ImageView
, imageUrl
là một chuỗi và error
là một Drawable
. Nếu bạn muốn bộ chuyển đổi được gọi khi đặt bất kỳ thuộc tính nào, hãy đặt cờ requireAll
(không bắt buộc) của bộ chuyển đổi thành false
, như minh hoạ trong ví dụ sau:
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Java
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Phương thức của bộ chuyển đổi liên kết có thể lấy các giá trị cũ trong trình xử lý. Phương thức nhận các giá trị cũ và mới phải khai báo tất cả các giá trị cũ cho các thuộc tính trước, sau đó là các giá trị mới, như trong ví dụ sau:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); } }
Bạn chỉ có thể dùng trình xử lý sự kiện với giao diện hoặc lớp trừu tượng bằng một phương thức trừu tượng, như trong ví dụ sau:
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
Java
@BindingAdapter("android:onLayoutChange") public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } } }
Sử dụng trình xử lý sự kiện này trong bố cục của bạn như sau:
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
Khi một trình nghe có nhiều phương thức, bạn phải chia thành nhiều trình nghe.
Ví dụ: View.OnAttachStateChangeListener
có hai phương thức: onViewAttachedToWindow(View)
và onViewDetachedFromWindow(View)
.
Thư viện cung cấp 2 giao diện để phân biệt các thuộc tính và trình xử lý:
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
Java
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
Vì việc thay đổi một trình nghe có thể ảnh hưởng đến trình nghe còn lại, nên bạn cần có một bộ chuyển đổi hoạt động cho một trong hai thuộc tính hoặc cả hai. Bạn có thể đặt requireAll
thành false
trong chú giải để chỉ định rằng không phải thuộc tính nào cũng phải được gán một biểu thức liên kết, như minh hoạ trong ví dụ sau:
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
Java
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } } }
Ví dụ trên hơi phức tạp vì lớp View
sử dụng phương thức addOnAttachStateChangeListener()
và removeOnAttachStateChangeListener()
thay vì phương thức setter cho OnAttachStateChangeListener
.
Lớp android.databinding.adapters.ListenerUtil
giúp theo dõi các trình nghe này để có thể xoá trong bộ chuyển đổi liên kết.
Chuyển đổi đối tượng
Tự động chuyển đổi đối tượng
Khi một Object
được trả về từ một biểu thức liên kết, thư viện sẽ chọn phương thức dùng để đặt giá trị của thuộc tính. Object
được truyền thành loại tham số của phương thức đã chọn. Hành vi này thuận tiện trong các ứng dụng dùng lớp ObservableMap
để lưu trữ dữ liệu, như trong ví dụ sau:
<TextView
android:text='@{userMap["lastName"]}'
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Đối tượng userMap
trong biểu thức sẽ trả về một giá trị. Giá trị này được tự động truyền đến loại tham số tìm thấy trong phương thức setText(CharSequence)
dùng để đặt giá trị của thuộc tính android:text
. Nếu loại tham số không rõ ràng, hãy truyền loại dữ liệu trả về trong biểu thức.
Lượt chuyển đổi tuỳ chỉnh
Trong một số trường hợp, bạn phải có lượt chuyển đổi tuỳ chỉnh giữa các loại cụ thể. Ví dụ: thuộc tính android:background
của khung hiển thị yêu cầu Drawable
, nhưng giá trị color
được chỉ định lại là một số nguyên. Ví dụ sau đây cho thấy một thuộc tính yêu cầu Drawable
, nhưng lại cung cấp một số nguyên:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Bất cứ khi nào Drawable
dự kiến và một số nguyên được trả về, hãy chuyển đổi int
thành ColorDrawable
.
Để thực hiện việc chuyển đổi, hãy sử dụng phương thức tĩnh có chú giải BindingConversion
như sau:
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
Tuy nhiên, các loại giá trị được cung cấp trong biểu thức liên kết phải nhất quán. Bạn không thể sử dụng nhiều loại trong cùng một biểu thức, như trong ví dụ sau:
// The @drawable and @color represent different value types in the same
// expression, which causes a build error.
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Tài nguyên khác
Để tìm hiểu thêm về liên kết dữ liệu, hãy xem các tài nguyên sau.
Mẫu
Lớp học lập trình
Bài đăng trên blog
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Thư viện liên kết dữ liệu
- Bố cục và biểu thức liên kết
- Liên kết thành phần hiển thị