بوت استرپ 5 - تاریخ انتشار و ویژگی های جدید مورد انتظار
بوت استرپ 5 – تاریخ انتشار و ویژگی های جدید مورد انتظار
2020-03-20
کار با اشیاء داده observable در دیتا بایندینگ Data Binding
کار با اشیاء داده observable در دیتا بایندینگ Data Binding
2020-04-03
پیکربندی لایه ها Layout و دستورات binding - آموزش Data Binding

پیکربندی لایه ها Layout و دستورات binding - آموزش Data Binding

پیکربندی لایه ها ( Layout ) و دستورات binding – آموزش Data Binding در اندروید ، در ادامه پست قبل که پیکربندی پروژه اندروید برای استفاده از دیتا بایندینگ data binding را آموزش دادیم حال زمان آن است که با پیکربندی Layout در دیتا بایندینگ ، دستورات binding و ویژگی های data binding در اندروید بیشتر آشنا شویم و هرکدام را با مثال های متعددی آموزش دهیم.

پیکربندی لایه ها ( Layout ) و دستورات binding در دیتا بایندینگ

با استفاده از دیتا بایندینگ شما قادر خواهید بود event یا به عبارتی رخداد های view های درون لایه ( Layout ) را مدیریت یا هندل کنید.اما این به چه معناست ؟

کتابخانه دیتا بایندینگ به صورت اتوماتیک کلاس هایی مورد نیاز برای متصل کردن ( bind ) لایه ها ایجاد می کند که با استفاده از آنها می توان داده ها را به لایه متصل یا اصطلاحا بایند کرد.

اما آیا زمانی که شما دیتا بایندینگ را در پروژه فعال می کنید برای همه لایه ها کلاس های مخصوص دیتا بایندینگ تولید یا Generate می شود ؟

قطعا خیر ! چرا که لایه های (Layouts) مجهز به دیتا بایندینگ با لایه های عادی متفاوت است بدین معنی که شما هم زمان می توانید در پروژه خود که کتابخانه data binding را هم در آن فعال کردید هر دو نوع لایه ساده و مجهز به دیتا بایندینگ داشته باشید.

نکته :‌
منظور لایه مجهز به دیتا باندینگ درواقع لایه ای است که می توان دستورات بایند کردن داده و حتی مدیریت رخداد ها یا event ها برای تمام view های درون آن پیاده کرد.

تفاوت لایه xml عادی با لایه xml با دیتا بایندینگ در اندروید

خب قطعا شما ساختار لایه های عادی که در پروژه های خود ایجاد می کردید را به یاد دارید اما جهت یادآوری و مقایسه با لایه دارای ساختار دیتا بایندینگ در زیر نمونه ای از لایه ساده ای که تنها یک TextView دارد آورده ایم:

<?xml version="1.0" encoding="utf-8"?>
   <LinearLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="Tejariapp.com"/>
   </LinearLayout>

حال اگر بخواهیم لایه معمول فوق را به دیتا بایندینگ مجهز کنیم بدین صورت کدها را تغییر خواهیم داد:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
   </LinearLayout>
</layout>

همانطور که می بینید ساختار لایه ای که قوانین دیتا بایندینگ درون آن رعایت شده کمی متفاوت است.

اما تفاوت لایه xml عادی با لایه xml با دیتا بایندینگ در اندروید چیست ؟

اگر از خط اول شروع کنیم خواهیم دید که لایه های دیتابایندیگ با تگ layout شروع و تمام می شوند.

<layout >
   .....
</layout>

که معمولا namespace های مورد استفاده در لایه هم درون همین تگ قرار می گیرند برای مثال در نمونه فوق ما namespace زیر را در تگ layout قرار دادیم

<layout xmlns:android="http://schemas.android.com/apk/res/android">

سپس یک تگ data خواهیم داشت که درواقع به کلاس دیتای مورد نظر که می خواهیم درون این لایه بایند شود و یا به ViewModel مربوطه که وظیفه مدیریت لایه را به عهده دارد اشاره می کند.

در نمونه فوق ما به یک مدل data اشاره کردیم که قصد داریم فیلد های درون آنرا به لایه بایند کنیم

   <data>
       <variable name="user" type="com.example.User"/>
   </data>

برای بایند کردن هم درون تگ data باید از تگ variable استفاده کنیم که یک نام و آدرس آن کلاس در پروژه را باید مشخص کنیم.

توجه:
می توانید بیش از یک variable برای لایه درون تگ data مشخص کنید.

پس از تگ data ما باید با استفاده از view ها لایه را طراحی کنیم بدین صورت که درواقع همان ساختار طراحی لایه که در لایه های معمولی داشتیم را در این قسمت قرار می دهیم.

نکته :‌
توجه کنید که در بخش پیاده سازی view های لایه شما تنها می توانید یک لایه روت که می توانید یکی از view های LinearLayout ، RelativeLayout ، ConstraintLayout و … باشد و درون آن به هر تعداد المان دیگر و هیچ گاه نمی توانید بیش از یک المان در بخش پیاده سازی UI لایه دیتا بایندینگ پیاده کنید.

  <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
   </LinearLayout>

حال می توان از variable ایجاد شده درون المان ها استفاده کرد .بدین صورت که با ساختار @{} به فیلد های درون variable مشخص شده دسترسی پیدا کرد.

همانطور که می بینید ما از دیتا بایندینگ برای بایند کردن یک داده خاص درون TextView استفاده کردیم.

بدین صورت که از سینتکس مشخص شده دیتا بایندینگ یعنی @{} در پراپرتی android:text که برای تخصیص یک رشته به TextView استفاده می شود استفاده کردیم و درون ساختار دیتا بایندینگ ( @{} ) ما ابتدا نامی که برای variable مشخص کردیم را فراخوانی کرده و سپس می توانیم فیلد مورد نظر را از کلاس ایمپورت شده انتخاب کرد و دیتای درون آن را به TextView اختصاص داد.

شی داده ( Data Object ) در دیتا بایندینگ

Data Object ی که در نمونه فوق استفاده شد به چه صورت است ؟

فرض کنید ما کلاسی به صورت زیر داریم که حال به طریقی دیتای درون آن تامین می شود یا از بک اند و یا لوکال:

جاوا

public class User {
  public final String firstName;
  public final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

کاتلین

data class User(val firstName: String, val lastName: String)

یک نکته مهم درباره کلاس مدلی که دربالا با جاوا و کاتلین تعریف کردیم این ست که کلاس ها Read Only هستند بدین معنا که امکان تغییر آنها وجود ندارد و فقط میتوان از داده درون آنها که یک بار پر شدند استفاده کرد.اما می توان کلاس ها را با متدهای getter ( که معمولا در کلاس هایی که از مدل ها استفاده می کنند کاربرد دارد ) هم بدین صورت پیاده کرد.

جاوا

public class User {
  private final String firstName;
  private final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
  public String getFirstName() {
      return this.firstName;
  }
  public String getLastName() {
      return this.lastName;
  }
}

کاتلین

// در کاتلین کاربردی ندارد

data class User(val firstName: String, val lastName: String)

اما از دید دیتا بایندینگ هر دو نوع کلاس چه آن که متد های getter دارد و چه آنکه تنها فیلد ها را بدون هیچ متد getی تعریف کرده بود یکی است و خود data binding می تواند دسترسی به داده ها داشته باشد.

درواقع @{user.firstName} که برای پراپرتی androdi:text در TextView استفاده شده به فیلد firstName در کلاس نوع اول (بدون متد getter) و به متد getFirstName() در کلاس نوع دوم ( با متد getter ) دسترسی دارد و از داده درون آنها استفاده می کند.

نحوه بایند کردن داده به view ها یا ( Binding Data )

همانطور که در ابتدای مقاله گفته شد ، کتابخانه دیتا بایندینگ خود بصورت اتوماتیک برای لایه هایی که با ساختار دیتا بایندینگ پیاده شدند ( منظور استفاده از همان تگ های layout و data ست که در سکشن قبل گفته شد ) کلاس هایی را تولید یا Generate میکند که شامل کدهای مورد نیاز برای bind شدن داده ها به تمام ویو هایی ست که ما در لایه XML مشخص کردیم برای مثال متغیر user که داده ای از این متغیر هم به TextView بایند شده بود.

به عبارتی می توان گفت کلاس های تولید شده توسط کتابخانه دیتا بایندینگ شامل کدهایی برای پیاده کردن منطق دیتاهایی ست که ما در لایه xml به انواع ویو ها bind کردیم.

نام کلاس های تولیدی data binding چیست ؟

نام کلاس های auto generate دیتا بایندینگ درواقع از همان نام لایه ها گرفته می شود بدین صورت که نام لایه بصورت Pascal case و با پسوند Binding ترکیب شده و نام کلاس قرار می گیرد یعنی اگر شما لایه ای با نام activity_main.xml داشته باشید کلاس دیتا بایندینگی که از این لایه تولید می شود ActivityMainBinding خواهد بود.

چگونه ازاین کلاس استفاده کنیم؟

ما می توانیم بدین صورت از کلاس تولید شده در اکتیوتی برای دسترسی به ویو ها و یا مقدار دهی آنها استفاده کنیم :

جاوا

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}

کاتلین

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}

همانطور که می بینید متد onCreate اکتیویتی کمی متفاوت وجدید شده است چرا که لایه هم جدید شده است ! بدین معناست که زمانی که لایه با ساختار دیتا بایندینگ پیاده می شود نحوه اتچ لایه به اکتیوتی هم متفاوت خواهد شد.

همانطور که می بینید ما یک instance یا نمونه از کلاسی که توسط خود data binding از لایه ساخته شده ایجاد کردیم که در واقع مقدار آن هم همان لایه ای است که می خواهیم به اکتیوتی متصل کنیم.درواقع با استفاده از کلاس DataBindingUtil و متد setContentView لایه مورد نظر یعنی activity_main را به اکتیویتی متصل کردیم و چون خروجی این دستور یعنی:

DataBindingUtil.setContentView(this, R.layout.activity_main);

نمونه ای از کلاس تولید شده ActivityMainBinding خواهد بود پس خروجی آن را درون متغیری قرار داده تا در ادامه از آن استفاده کنیم

   ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

در ادامه ما از نمونه binding ایجاد شده استفاده می کنیم تا متغیری که در لایه درون تگ variable تعریف کردیم

 <data>
       <variable name="user" type="com.example.User"/>
   </data>

را مقدار دهی کنیم تا داده درون TextView هم قرار گیرد و طبیعتا چون مدل متغیر تعریف شده کلاس User است

public class User {
  public final String firstName;
  public final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

پس متغیر را با یک object از نوع کلاس User مقدار دهی میکنیم:

   User user = new User("Test", "User");
   binding.setUser(user);

و پس از اجرا خواهید دید که درون لایه TextView مقدار Test را نمایش می دهد.

ویژگی های معمول و رایج دیتا بایندینگ

دستورات binding که درون لایه های xml پیاده می کنید بسیار شبیه به دستورات جاوا یا کاتلینی است که در کلاس ها یا اکتیویتی ها پیاده میکنید.در ادامه با دستورات binding بیشتر آشنا خواهید شد.

در زیر لیستی از عملگر هایی که می توانید در دیتا بایندینگ ( دستورات binding ) لایه ها استفاده کنید را آورده ایم:

  • عملگر های ریاضی: + – / * %
  • دستور الحاق رشته ها: +
  • دستورات منطقی:‌ && ||
  • دستورات باینری:‌ & | ^
  • دستورات Unary: + – ! ~
  • شیفت ها:‌ >> >>> <<
  • دستورات مقایسه ای:‌ == > < >= <=
  • دستور instanceof
  • گروه بندی:‌ ()
  • لیترال ها ، کاراکتر ، رشته ، عدد و null
  • تبدیل Cast
  • فراخوانی متدها
  • دسترسی به فیلد ها
  • دسترسی به آرایه []
  • اپراتور های چندگانه مانند ؟:

برای مثال:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

عملگرهایی که دیتا بایندینگ پشتیبانی نمی کند

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

  • this
  • super
  • new
  • Explicit generic invocation

عملگر data binding برای چک کردن null بودن یا نبودن

در دیتا بایندینگ می توان با استفاده از عملگر ?? تصمیم گرفت که اگر مقدار مقدار سمت چپ null نبود از همان استفاده کند و اگر هم null بود از مقدار سمت راست

برای مثال:

android:text="@{user.displayName ?? user.lastName}"

در نمونه فوق درصورتی که user.displayName مقدار داشته باشد و null نباشد پس از مقدار درونش استفاده می شود اما اگر null باشد پس از مقدار user.lastName استفاده می شود.درواقع نمونه فوق دقیقا برابر با نمونه زیر است:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

قابلیت دیتا بایندینگ برای جلوگیری از خطاهای null بودن مقادیر:

کلاسی که توسط خود دیتا بایندینگ برای لایه های xml تولید یا Generate می شود به این نکته توجه دارد که مقادیر null نباشند یا اگر هم باشند مقدار deault یا پیش فرضی برای آن در نظر می گیرد.

برای مثال اگر از @{user.name} استفاده می کنید و اگر user برابر با null باشد user.name مقدار پیشفرضی استفاده می کند که خالی است .یا اگر مثلا از user.age استفاده می کنید و age از نوع int باشد سپس دیتا بایندینگ پیشفرض مقدار 0 را برایش درنظر میگیرد.

مجموعه ها (Collections) در دیتا بایندینگ

در دیتا بایندینگ می توان به داده های درون مجموعه های معمول مانند arrys ، lists ، sparse list و maps با استفاده از [ ] دسترسی داشته باشید.مانند زیر:

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="Map<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

در نمونه فوق فرض کنید یک لایه داریم که با ساختار دیتا بایندینگ پیاده شده و تگ data آن نیز شامل این موارد است.توجه کنید که ما از import هم در تگ data استفاده می کنیم چرا که نیاز است چندین کلاس را استفاده کنیم که بطور پیش فرض در لایه نیست .

نکته :‌
در XML برای اینکه کدهایی که درون آن می نویسیم از لحاظ سینتکسی درست باشد و خطایی رخ ندهد باید کاراکتر > رابه < تغییر دهیم یعنی عبارت List به List<String> تبدیل می شود.
شما همچنین می توانید به مقادیر درون map با استفاده از key دسترسی داشته باشید برای مثال @{map[key]} درون مثال فوق می تواند با @{map.key} جایگزین شود.

رشته ها در دیتا بایندینگ

شما برای نمایش یک مقدار از مجموعه map که با استفاده از کلید قصد نمایش آن را دارید هم میتوانید از تک کوتیشن استفاده کنید مطابق مثال زیر:

android:text='@{map["firstName"]}'

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

android:text="@{map[`firstName`]}"

اما باید کلید را در تک کوتیشن قرار دهید.

استفاده از Resource در دیتا بایندینگ data binding اندروید

شما همچنین زمان استفاده از data bidning می توانید به resouce ها هم دسترسی داشته باشید و از آنها هم به حد مطلوبی استفاده کنید.برای مثال:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

در مثال فوق ما یک متغیر boolean با نام large را در نظر گرفتیم و یک شرط if هم پیاده کردیم ،بدین صورت که گفتیم اگر مقدار متغیر بولین large برابر با true بود پس از مقدار @dimen/largePadding استفاده کند و در غیر این صورت از @dimen/smallPadding استفاده کند .

همانطور که می دانید مقادیر dimen (dimensions) یا ابعاد مقادیری ست برای مقدار دهی ابعاد یک ویو که فایل xml آن را هم خودِ اندروید بصورت پیش فرض دارد و هم خود برنامه نویس می تواند به صورت سفارشی پیاده کند.

مدیریت Event ها در دیتا بایندینگ اندروید

دیتا بایندینگ این امکان را به شما می دهد که event یا رخدادهایی که از سمت view به وجود می آید ( برای مثال رخداد onClick ) را مدیریت کنید.رخدادها در برنامه نویسی یک شنونده خاص خود دارند که هر شنونده هم یک متد درون خود برای اجرای عملیات خاص زمان فراخوانی همان رخداد دارد و همچنین هم متد شنونده یک attribute هم سمت لایه xml دارد که کاربر بتواند آن رخداد را از سمت لایه هم فراخوانی و مدیریت کند.برای مثال شنونده View.onClickListener یک متد onClick درون خود دارد که این متد هم یک attribute درون لایه xml دارد به نام android:onClick .

ما یک سری event یا رخداد کلیک دیگر هم داریم که صرفا کلیک برروی یک ویو خاص نیست و عملکرد دیگری دارد برای مثال ،زوم کردن که به نوعی هم کلیک می شود و هم یک اکشن جدید است اما برای تمامی انواع کلیک ها event های متفاوتی در نظرگرفته شده تا تداخلی ایجاد نشود.

لایه در دیتا بایندینگ - Layout در دیتا بایندینگ - دستورات binding - آموزش Data Binding
لایه در دیتا بایندینگ – Layout در دیتا بایندینگ – دستورات binding – آموزش Data Binding

شما به دوصورت زیر می توانید رخداد ها را با دیتا باندینگ مدیریت کنید:

ارجاع به متد :‌

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

جاوا

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

کاتلین

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

اما یک مزیت مهم android:onClick که مستقیما به یک متد درون اکتیوتی ارجاع داده می شود این است که دستوارت زمان کامپایل پردازش می شود بدین معنی که اگر خطایی وجود داشته باشد برای مثال متد وجود نداشته باشد همان زمان کامپایل خطا نمایش داده می شود که چنین متدی وجود ندارد.برای اختصاص دادن یک handler به یک رخداد تنها کافیست یک متد با پارامتر view پیاده سازی کنید. دقیقا نام متد را به رخداد اختصاص دهید.بدین صورت:

جاوا

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

کاتلین

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

حال برای بایند کردن متد به view مورد نظر تنها کافی ست متد را بصورت زیر به رخداد onCllick ویو مورد نظر اختصاص دهیم:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

توجه کنید که پارامترهای شنونده ای که می خواهید با روش ارجاع به متد پیاده کنید باید دقیقا در متدی که پیاده می کنید باشد.
بدین معنی که اگر ما شنونده onClick را فراخوانی کنیم خواهیم دید بدین صورت است:

buttonname.setOnClickListener(new View.OnClickListener() {

   @Override
   public void onClick(View view) {

   }
});

همانطور که می بینید شنونده onClick تنها یک پارامتر از نوع view دارد پس در حالت ارجاع به متد هم باید متد تنها همین پارامتر را داشته باشد یعنی بدین صورت پیاده شود

 public void onClickFriend(View view) { ... }

بایند کردن یک شنونده :‌

بایند کردن یک شنونده یا listener binding دستورات binding هستند.دستورات binding بایند کردن یک شنونده یا listener binding تنها زمانی اجرا می شوند که رخداد فراخوانی شود.استفاده از این قابلیت دیتا بایندینگ یا دستورات binding تنها در Gradle ورژن 2.0 به بالا قابل استفاده ست.همانطور که در بالا گفته شد در حالت ارجاع به متد ما باید عینا پارامتر های همان شنونده را در متد پیاده می کردیم.اما در حالت listener binding باید نوع بازگشتی متد دقیقا با نوع بازگشتی شنونده رخداد یکی باشد.مگر اینکه خروجی void باشد.

برای نمونه ما در زیر یک مثال آورده ایم که یک متد برای پیاده سازی رخداد onClick پیاده شده که خروجی آن نیز void است:

جاوا

public class Presenter {
    public void onSaveClick(Task task){}
}

کاتلین

class Presenter {
    fun onSaveClick(task: Task){}
}

حال می توان متد onSaveClick را به android:onClick در لایه بدین صورت اختصاص داد:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>
    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>

همانطور که می بینید متد را برای فراخوانی زمان onClick در لایه پیاده کردیم همچنین پارامتری که نیاز دارد هم برایش ارسال کردیم.زمانی که شما رخدادها را بدین صورت تعریف می کنید خودِ دیتا بایندینگ بصورت اتوماتیک در همان کلاسی که برای لایه های خود generate یا تولید می کند ، شنونده های لازم را نیز برای قسمت هایی که شما فراخوان خاصی کرده اید پیاده می کند تا زمان اتفاق افتادن آن رخداد خاص ، مطلع شده و عملیات ما انجام شود.

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

buttonname.setOnClickListener(new View.OnClickListener() {

   @Override
   public void onClick(View view) {

   }
});

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

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

و همچنین میتوانید پارامتر ها را سمت کدنویسی هم پیاده کنید ، که بدین صورت خواهد شد:

جاوا

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

کاتلین

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

و در نهایت در لایه هم ما هردو پارارمتر view و task را خواهیم داشت:

android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

همچنین می توانید بیش از یک پارامتر هم داشته باشید برای نمونه شنونده هایی مانند android:onCheckedChanged بدین صورت خواهند شد:

جاوا

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}

کاتلین

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

و همچنین درلایه هم ما هردو پارامتر مورد نیاز شنونده onCheckedChanged

satView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {

       @Override
       public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {

       }
   }
); 

که بصورت فوق است را بدین صورت پیاده میکنیم:

<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

همانطور که گفتیم مدیریت رخدادها در حالت listener binding به نوع خروجی شنوند ها حساس است بدین معنی که شنونده هر نوع خروجی داشته باشند ، متد پیاده سازی شما نیز باید همان نوع خروجی را داشته باشد.برای نمونه شنونده onLongClick مقدار boolean برمیگرداند

tv.setOnLongClickListener(new View.OnLongClickListener() {
      @Override
      public boolean onLongClick(View v) {
       
      }
  });

پس شنونده ما هم باید نوع خروجی boolean داشته باشد

جاوا

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}

کاتلین

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

لایه xml

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

توجه کنید که دیتا بایندینگ حتی درصورت نال بودن مقدار بازگشتی ، مقدار null برنمی گرداند بلکه مقادیر پیش فرض هرنوع را برمی گرداند بدین معنی که درصورت int بودن مقدار 0 و در صورت boolean بودن مقدار flase و … ارسال میکند.

همچنین در دیتا بایندینگ می توانید دستور بنویسید که درصورت برقرار بودن شرطی یک سری عملیات خاص انجام دهد و درصورت false برگرداندن آن هیچ کار خاصی انجام ندهد.بدین صورت :

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

تفاوت اصلی بین دو حالت ارجاع به متد (method reference) و بایند کردن شنونده (listener binding) در این است که یک شنونده واقعی زمان متصل شدن داده ها پیاده سازی می شود و نه زمان فراخوانی رخداد یا event .

پس اگر میخواهید دستورات تنها زمانی که رخداد فراخوانی می شود پیاده سازی شده و به نوعی تا زمانی که نیاز نشده فضایی اشغال نکند بهتر است از حالت listener binding استفاده کنید.

خودداری از listeners پیچیده

دستورات شنونده listener که دیتا بایندینگ برای شما فراهم کرده بسیار قدرتمند است و می تواند کد شما را بسیار خوانا تر کند.اما در عین حال شما می توانید شنونده ها را به صورتی پیاده کنید که خیلی پیچیده شوند و کد شما را از حالت خوانایی یا به عبارتی easy to read خارج کند.

اما توجه کنید زمانی که میخواهید شنونده هایی در لایه خود با استفاده از دیتا بایندینگ پیاده کنید از ساده ترین حالت ممکن استفاده کنید به صورتی که یک سری داده از سمت لایه به سمت کد جهت انجام یک سری عملیات پاس دهد و به هیچ عنوان عملیات منطقی را سمت لایه و UI نیاورید چرا که تمامی عملیات logic و منطقی باید در callback یا فراخوان انجام شود و سمت لایه شما تنها باید متد مربوطه را فراخوانی کنید و یک سری داده به آن پاس دهید.

استفاده از Imports ، variables و includes در دیتا بایندینگ data binding

کتابخانه data binding از تمام ویژگی های import ، include و variable بطور کامل پشتیبانی میکند ،اما هر کدام از این دستورات چه زمانی استفاده میشود ؟

  • دستور import یک reference یا ارجاع به یک کلاس مورد نیاز شما در لایه ایجاد میکند.
  • دستور variables به شما این اجازه را می دهد که یک متغیر یا variable تعریف کنید و از آن در دستورات binding لایه استفاده کنید.
  • دستور include به شما اجازه می دهد از یک لایه پیچیده دیگر درون لایه خود استفاده مجدد کنید.

اما حال به هرکدام نگاهی دقیق تر همراه با مثال می اندازیم:

Imports

همانطور که گفتیم دستور import به شما اجازه می دهد به هر تعداد کلاس مورد نیاز خود را در لایه import کنید ،دقیقا مثل کلاس های جاوا و کاتلین که کلاس های مورد نیاز خود که از آنها در کدنویسی استفاده می کنید دربالای فایل کلاس import می کنید ( برای مثال کلاس Context که درصورت استفاده باید بدین صورت آنرا ایمپورت کنید import android.content.Context ).

در لایه ها نیز می توانید هر کلاسی که می خواهید را بدین صورت در تگ data خیلی ساده import کنید

<data>
    <import type="android.view.View"/>
</data>

الان شما با استفاده از کد زیر میتوانید از کلاس View در هر قسمت از لایه که نیاز دارید استفاده کنید.برای مثال می توانید به صورت زیر از کلاس View استفاده کنید:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

در نمونه کد فوق ما از کلاس View برای نمایش ( VISIBLE ) یا مخفی ( GONE ) کردن یک TextView طبق مقدار یک متغیر boolean استفاده کردیم .

نکته :‌
همانطور که می دانید مقادیر VISIBLE و GONE فیلدی از کلاس View هستند.

استفاده از aliase در دیتا بایندینگ

ممکن است شما نیاز به دو کلاس داشته باشید که نام های یکسانی دارند. برای مثال دو کلاس با نام یکسان در دو package مختلف از پروژه که این package هم می تواند از پکیج های خود شما باشد و هم می تواند پکیج اندروید باشد.در این صورت شما زمان import هردو کلاس می توانید به یکی از آنها مقدار alias دهید تا زمان استفاده از آن به مشکل یا تداخل نخورید.

برای مثال در نمونه کد زیر ما دو کلاس View ایمپورت کردیم که یکی از آنها مربوط به پکیج اندروید و دیگری جز پکیج های خود ما می باشد:

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="Vista"/>

همانطور که می بینید ما به کلاس View که مربوط به پکیج های خودمان است (com.example.real) یک مقدار alias با نام Vista دادیم که زمان استفاده از آن بجای View. از Vista. استفاده کنیم.

ما از import علاوه براینکه می توانیم در خود لایه استفاده کنیم ، می توانیم در variable هم استفاده کنیم.برای مثال به نمونه کد زیر توجه کنید:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List<User>"/>
</data>

همانطور که می بینید ما کلاس List را به لایه import کردیم تا از آن در تعریف variable که type آن List است استفاده کنیم.

‌Variables

ما در تگ data می توانیم هر تعداد variable پیاده و استفاده کنیم.

اما variable چیست؟ درواقع variable تعریف کننده یک پراپرتی ست که قرار است در لایه استفاده شود.

برای به مثال variable های زیر توجه کنید:

 <data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note" type="String"/>
</data>

در دیتا بایندینگ می توان variable ها را به لایه های include شده پاس دهیم این بدین معناست که می توان در لایه ای که include کردیم از این variable استفاده کنیم و درواقع ما در لایه اصلی تنها مقدار variable مورد نیاز لایه include شده را به آن پاس داده ایم.

امیدورام آموزش پیکربندی لایه ها Layout در دیتا بایندینگ و دستورات binding برای شما مفید یوده باشد…

1 Comment

  1. mehrdad گفت:

    لطفا ادامه بدید خیلی خوبه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *