آداپتورهای اتصال

آداپتورهای اتصال مسئول برقراری تماس‌های چارچوب مناسب برای تنظیم مقادیر هستند. یک نمونه تنظیم مقدار ویژگی است، مثل فراخوانی روش setText(). مثال دیگر تنظیم شنونده رویداد است، مثل فراخوانی روش setOnClickListener().

«کتابخانه اتصال داده» به شما امکان می‌دهد روش فراخوانی‌شده برای تنظیم مقدار را مشخص کنید، منطق اتصال خودتان را ارائه دهید، و نوع شیء برگردانده‌شده را بااستفاده از آداپتورها مشخص کنید.

تنظیم مقادیر مشخصه

هرگاه مقدار محدودشده‌ای تغییر کند، کلاس اتصال تولیدشده باید روش تنظیم‌کننده را در نما با عبارت اتصال فراخوانی کند. می‌توانید به «کتابخانه اتصال داده» اجازه دهید روش را به‌طور خودکار تعیین کند، یا می‌توانید روش را به‌طور صریح اعلام کنید یا منطق سفارشی برای انتخاب روش ارائه دهید.

انتخاب خودکار روش

برای مشخصه‌ای به‌نام example، کتابخانه به‌طور خودکار روش setExample(arg) را که انواع سازگار را به‌عنوان آرگومان می‌پذیرد پیدا می‌کند. فضای نام مشخصه درنظر گرفته نمی‌شود. هنگام جستجوی روش، فقط از نام و نوع مشخصه استفاده می‌شود.

برای مثال، با داشتن عبارت android:text="@{user.name}"، کتابخانه به‌دنبال روش setText(arg) می‌گردد که نوع برگردانده‌شده توسط user.getName() را می‌پذیرد. اگر نوع پاسخ user.getName()، String باشد، کتابخانه به‌دنبال روش setText() می‌گردد که آرگومان String را می‌پذیرد. اگر عبارت int را برگرداند، کتابخانه روش setText() را جستجو می‌کند که آرگومان int را می‌پذیرد. عبارت باید نوع صحیح را برگرداند. درصورت لزوم می‌توانید مقدار برگشتی را تبدیل کنید.

اتصال داده حتی اگر هیچ مشخصه‌ای با نام ارائه‌شده وجود نداشته باشد کار می‌کند. می‌توانید بااستفاده از اتصال داده، برای هر تنظیم‌کننده‌ای مشخصه ایجاد کنید. برای مثال، کلاس پشتیبانی DrawerLayout مشخصه ندارد، اما تنظیم‌کننده‌های زیادی دارد. چیدمان زیر به‌طور خودکار از روش‌های setScrimColor(int) و addDrawerListener(DrawerListener) به‌ترتیب به‌عنوان تنظیم‌کننده برای مشخصه‌های app:scrimColor و app:drawerListener استفاده می‌کند:

<androidx.drawerlayout.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">

نام روش سفارشی را مشخص کنید

برخی‌از مشخصه‌ها دارای تنظیم‌کننده‌هایی هستند که ازنظر نام مطابقت ندارند. در این شرایط، می‌توان بااستفاده از حاشیه‌نویسی BindingMethods، مشخصه‌ای را به تنظیم‌کننده مرتبط کرد. این حاشیه‌نویسی با کلاس استفاده می‌شود و می‌تواند حاوی چندین BindingMethod حاشیه‌نویسی باشد، یکی برای هر روش تغییرنام‌یافته. روش‌های اتصال حاشیه‌نویسی‌هایی هستند که می‌توانید به هر کلاسی در برنامه‌تان اضافه کنید.

در مثال زیر، مشخصه android:tint با روش setImageTintList(ColorStateList) منسوب شده است—نه با روش setTint():

کاتلین

@BindingMethods(value = [
    BindingMethod(
        type = android.widget.ImageView::class,
        attribute = "android:tint",
        method = "setImageTintList")])

جاوا

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

معمولاً نیازی نیست نام تنظیم‌کننده‌ها را در کلاس‌های چارچوب Android تغییر دهید. ویژگی‌های ازقبل بااستفاده از قرارداد نام‌گذاری برای پیدا کردن خودکار روش‌های منطبق پیاده‌سازی شده‌اند.

ارائه منطق سفارشی

برخی‌از مشخصه‌ها به منطق اتصال سفارشی نیاز دارند. برای مثال، هیچ تنظیم‌کننده مرتبطی برای مشخصه android:paddingLeft وجود ندارد. درعوض، روش setPadding(left, top, right, bottom) ارائه شده است. روش آداپتور اتصال ایستا با حاشیه‌نویسی BindingAdapter به شما امکان می‌دهد نحوه فراخوانی تنظیم‌کننده برای یک مشخصه را سفارشی‌سازی کنید.

مشخصه‌های کلاس‌های چارچوب Android ازقبل حاشیه‌نویسی‌های BindingAdapter را دارند. مثال زیر آداپتور ترابست را برای مشخصه paddingLeft نشان می‌دهد:

کاتلین

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
    view.setPadding(padding,
                view.getPaddingTop(),
                view.getPaddingRight(),
                view.getPaddingBottom())
}

جاوا

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
  view.setPadding(padding,
                  view.getPaddingTop(),
                  view.getPaddingRight(),
                  view.getPaddingBottom());
}

انواع پارامتر مهم هستند. پارامتر اول نوع نمای منسوب به مشخصه را تعیین می‌کند. پارامتر دوم نوع پذیرفته‌شده در عبارت اتصال را برای مشخصه داده‌شده تعیین می‌کند.

آداپتورهای اتصال برای انواع دیگر سفارشی‌سازی نیز مفید هستند. برای مثال، می‌توان بارکننده سفارشی را از رشته عامل برای بار کردن تصویر فراخوانی کرد.

همچنین می‌توانید آداپتورهایی داشته باشید که چندین مشخصه را دریافت می‌کنند، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
    Picasso.get().load(url).error(error).into(view)
}

جاوا

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.get().load(url).error(error).into(view);
}

می‌توانید از آداپتور در چیدمانتان استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است. توجه داشته باشید که @drawable/venueError به منبعی در برنامه‌تان اشاره دارد. قرار دادن منبع در @{} آن را به عبارت اتصال معتبری تبدیل می‌کند.

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

اگر از imageUrl و error برای شیء ImageView استفاده شود، imageUrl رشته باشد، و error Drawable باشد، آداپتور فراخوانی می‌شود. اگر می‌خواهید وقتی هرکدام از مشخصه‌ها تنظیم شد، آداپتور فراخوانی شود، پرچم اختیاری requireAll آداپتور را روی false تنظیم کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

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

جاوا

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

روش‌های آداپتور اتصال می‌توانند مقادیر قدیمی را در گرداننده‌هایشان دریافت کنند. روشی که مقادیر قدیمی و جدید را می‌گیرد باید ابتدا همه مقادیر قدیمی را برای مشخصه‌ها اعلام کند و سپس مقادیر جدید را، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
    if (oldPadding != newPadding) {
        view.setPadding(newPadding,
                    view.getPaddingTop(),
                    view.getPaddingRight(),
                    view.getPaddingBottom())
    }
}

جاوا

@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());
   }
}

از گرداننده‌های رویداد فقط می‌توان با میاناها یا کلاس‌های انتزاعی با یک روش انتزاعی استفاده کرد، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

@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)
        }
    }
}

جاوا

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

از این کنترل‌کننده رویداد در چیدمانتان به‌صورت زیر استفاده کنید:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

وقتی شنونده چند روش دارد، باید به چند شنونده تقسیم شود. برای مثال، View.OnAttachStateChangeListener دو روش دارد: onViewAttachedToWindow(View) و onViewDetachedFromWindow(View). کتابخانه دو میانای متفاوت برای متمایز کردن مشخصه‌ها و گرداننده‌ها برای آن‌ها ارائه می‌دهد:

کاتلین

// 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)
}

جاوا

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
  void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
  void onViewAttachedToWindow(View v);
}

چون تغییر دادن یک شنونده می‌تواند بر دیگری تأثیر بگذارد، به آداپتوری نیاز دارید که برای هریک از این مشخصه‌ها یا برای هر دو کار کند. می‌توانید requireAll را روی false در حاشیه‌نویسی تنظیم کنید تا مشخص کنید که لازم نیست به هر مشخصه یک عبارت مقید اختصاص داده شود، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

@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)
        }
    }
}

جاوا

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

مثال بالا کمی پیچیده است زیرا کلاس View به‌جای روش تنظیم‌کننده برای OnAttachStateChangeListener، از روش‌های addOnAttachStateChangeListener() و removeOnAttachStateChangeListener() استفاده می‌کند. کلاس android.databinding.adapters.ListenerUtil کمک می‌کند این شنوندگان ردیابی شوند تا بتوان آن‌ها را در آداپتور اتصال برداشت.

تبدیل‌های شیء

تبدیل خودکار شیء

وقتی Object از عبارت مقیدسازی برگردانده می‌شود، کتابخانه روشی را که برای تنظیم مقدار ویژگی استفاده می‌شود انتخاب می‌کند. Object به نوع پارامتر روش انتخاب‌شده تبدیل می‌شود. این رفتار در برنامه‌هایی که از کلاس ObservableMap برای ذخیره داده استفاده می‌کنند مناسب است، همان‌طور که در مثال زیر نشان داده شده است:

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

شیء userMap در عبارت مقداری را برمی‌گرداند که به‌طور خودکار به نوع پارامتر یافت‌شده در روش setText(CharSequence) که برای تنظیم مقدار مشخصه android:text استفاده می‌شود، تبدیل می‌شود. اگر نوع پارامتر مبهم است، نوع پاسخ را در عبارت تبدیل کنید.

تبدیل‌های سفارشی

در برخی شرایط، تبدیل سفارشی بین انواع خاصی لازم است. برای مثال، مشخصه android:background یک نما باید Drawable باشد، اما مقدار color مشخص‌شده عدد صحیح است. مثال زیر مشخصه‌ای را نشان می‌دهد که انتظار Drawable دارد، اما به‌جای آن عدد صحیح ارائه شده است:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

هرگاه Drawable موردانتظار باشد و عدد صحیح برگردانده شود، int را به ColorDrawable تبدیل کنید. برای انجام تبدیل، از روش ایستا با حاشیه‌نویسی BindingConversion به‌صورت زیر استفاده کنید:

کاتلین

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

جاوا

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

بااین‌حال، انواع مقادیر ارائه‌شده در عبارت اتصال باید سازگار باشند. نمی‌توانید از انواع مختلف در یک عبارت استفاده کنید، همان‌طور که در مثال زیر نشان داده شده است:

// 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"/>

منابع بیشتر

برای کسب اطلاعات بیشتر درباره اتصال داده، منابع زیر را ببینید.

نمونه‌ها

Codelabs

پست‌های وبلاگ