آداپتورهای صحافی

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

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

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

هر زمان که یک مقدار کران تغییر کند، کلاس binding تولید شده باید یک متد setter را در view با عبارت binding فراخوانی کند. می توانید اجازه دهید Data Binding Library به طور خودکار روش را تعیین کند، یا می توانید به صراحت روش را اعلام کنید یا منطق سفارشی برای انتخاب یک روش ارائه دهید.

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

برای مشخصه ای به نام 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:paddingLeft وجود ندارد. در عوض، متد setPadding(left, top, right, bottom) ارائه شده است. یک روش آداپتور اتصال ایستا با حاشیه نویسی BindingAdapter به شما امکان می دهد نحوه فراخوانی یک تنظیم کننده برای یک ویژگی را سفارشی کنید.

ویژگی‌های کلاس‌های فریمورک اندروید از قبل دارای حاشیه‌نویسی BindingAdapter هستند. مثال زیر آداپتور binding برای ویژگی 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());
}

انواع پارامترها مهم هستند. پارامتر اول نوع نمای مرتبط با ویژگی را تعیین می کند. پارامتر دوم نوع پذیرفته شده در عبارت binding برای ویژگی داده شده را تعیین می کند.

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

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

کاتلین

@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 تنظیم کنید تا مشخص کنید که نباید به هر ویژگی یک عبارت binding اختصاص داده شود:

کاتلین

@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 از متدهای addOnAttachStateChangeListener() و removeOnAttachStateChangeListener() به جای متد تنظیم کننده برای OnAttachStateChangeListener استفاده می کند. کلاس android.databinding.adapters.ListenerUtil به پیگیری این شنوندگان کمک می کند تا بتوان آنها را در آداپتور binding حذف کرد.

تبدیل شی

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

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

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

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

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

در برخی شرایط، یک تبدیل سفارشی بین انواع خاصی مورد نیاز است. برای مثال، ویژگی android:background یک view انتظار یک 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);
}

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

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

پست های وبلاگ

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}