Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> ระบบ >> Android

เชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่น

Data binding เป็นเทคนิคที่ใช้เมื่อคุณต้องการ "ยึด" ชิ้นส่วนข้อมูล (data ของคุณ) เข้ากับองค์ประกอบ UI ที่รับข้อมูลจากผู้ใช้ ในกระบวนการนี้ เมื่อ input ถูกอัปเดต ข้อมูลเบื้องหลังก็จะอัปเดตตามไปด้วย

สิ่งนี้ไม่ใช่แนวคิดใหม่ มีเฟรมเวิร์กมากมายที่นำมาใช้ในการออกแบบ (เช่น AngularJS/React/Vue)

บทความนี้ไม่เน้นเฟรมเวิร์ก front-end แต่เน้นการพัฒนาบนมือถือ Google ได้นำเสนอ Data Binding Library ใน Android ซึ่งเป็นส่วนหนึ่งของ Android Jetpack

หากคุณไม่คุ้นเคยกับ Jetpack อาจเป็นเพราะ Google ประกาศหยุดพัฒนา support libraries แทนด้วยการรองรับ AndroidX libraries (เวอร์ชันใหม่ของ support libraries)

ผมรู้ว่ามีบทความมากมายอธิบาย data binding ผ่าน adapter แต่นี้จะไม่เน้นนั้น แทนที่จะเป็นการใช้งานแบบ minimalist ที่ช่วยประหยัดเวลาโดยลดโค้ดให้น้อยที่สุด

ทำไมต้องใช้ Data Binding?

ถ้ายังไม่เชื่อ ให้อธิบายประโยชน์ผ่านตัวอย่าง สมมติคุณมีเมนู 3 ปุ่มแบบกำหนดเอง แต่ละปุ่มเป็น layout ตัวเอง

เชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่น
Layout ของเรา

วิธีหนึ่งคือสร้าง XML layout 4 ไฟล์: 1 สำหรับ layout หลัก และ 3 สำหรับแต่ละปุ่ม

คุณต้องทำเช่นนี้เพราะแต่ละปุ่มนำทางไปส่วนต่างของแอป จึงต้องการข้อความและรูปภาพที่ต่างกัน

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_gravity="center_horizontal" android:layout_height="wrap_content" android:layout_width="wrap_content"> <ImageView android:id="@+id/imageView" android:layout_height="100dp" android:layout_width="100dp" android:src="@drawable/image_name" android:adjustViewBounds="true" android:scaleType="centerInside" /> <TextView android:id="@+id/textView" android:gravity="center_horizontal" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="Image Text" android:textSize="16sp" /> </LinearLayout>
Layout ของปุ่มหนึ่งปุ่ม

แม้โค้ดซ้ำไม่มาก (แค่ 3 layouts) แต่คิดดูแล้ว ค่อนข้างเสียเวลาจัดการโค้ดทั้งหมด ถ้านึกถึงแอปที่มี layout ซับซ้อนกว่านี้ เช่นแสดงสินค้าและรูปภาพ อาจนำไปสู่การทำซ้ำโค้ดที่น่าเบื่อมาก

ด้วย data binding เราสร้าง XML layout เพียง 1 ไฟล์ใช้กับปุ่มทั้งหมดได้

เริ่มต้นที่ไหน?

เราต้องให้โปรเจกต์เปิดใช้งาน data binding โดยเพิ่ม element dataBinding ใน build.gradle ของแอป:

android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.tomerpacific.example" minSdkVersion 15 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" dataBinding { //<------- enabled = true } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }
build.gradle

หลัง sync โปรเจกต์ เราจะสร้าง data class ที่จะใช้ bind กับ layout

package com.tomerpacific.example import android.graphics.drawable.Drawable data class ButtonData(val buttonText: String, val buttonImageSrc : Drawable) 
ButtonData.kt

สังเกตว่า class ButtonData มี 2 ฟิลด์:

  • buttonText — ข้อความที่จะแสดงใต้รูปภาพ
  • buttonImageSrc — รับผิดชอบรูปภาพของปุ่ม

หากต้องการข้อมูลมากขึ้น ให้เพิ่มฟิลด์ใน data class ได้เลย

การ Binding จริง

ต่อไป ต้องประกาศ property variable ใน layout เพื่อให้ใช้งานได้ variable นี้จะ bind เข้ากับ data class ที่สร้าง ต้องทำ 2 อย่าง:

  • ห่อ root layout element ด้วย tag layout
  • เพิ่ม tag data ที่ประกอบ variable (buttonData)
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="https://schemas.android.com/apk/res/android"> // <---- 1 <data> <variable name="buttonData" type="com.tomerpacific.example.ButtonData"/> // <---- 2 </data> <androidx.constraintlayout.widget.ConstraintLayout xmlns:app="https://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Example" android:textSize="30dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/android"> </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Image Text" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2" app:layout_constraintVertical_bias="0.504"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/android_p_logo"> </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Image Text" android:textSize="16sp" /> </LinearLayout> <LinearLayout android:id="@+id/linearLayout2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.200" app:layout_constraintStart_toEndOf="@+id/linearLayout3" app:layout_constraintTop_toBottomOf="@+id/textView2" app:layout_constraintVertical_bias="0.504"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/android_studio_icon"> </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="Image Text" android:textSize="16sp" /> </LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
activity_main.xml

สังเกตว่าเราสามารถลบ schema จาก main layout เดิมได้ เพราะย้ายไปที่ root layout tag แล้ว variable ที่เพิ่มมานั้นเชื่อมต่อกับ data class โดยตรง

ในไฟล์ MainActivity ต้องเพิ่มโค้ดจัดการ binding:

package com.tomerpacific.example import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.databinding.DataBindingUtil import com.tomerpacific.example.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.buttonData = ButtonData("First", resources.getDrawable(R.drawable.android)) } }
MainActivity.kt

ทุกครั้งที่สร้าง variable ภายใน layout จะมี binding class สร้างขึ้นอัตโนมัติ สำหรับ layout ชื่อ activity_main binding class จะชื่อ ActivityMainBinding

ข้อตกลงคือชื่อ layout + Binding ต่อท้าย

เนื่องจากเราประกาศ buttonData เป็น variable ใน layout มันจะถูกผนวกกับ binding object เราจึง assign instance ใหม่ของ ButtonData ได้

หลังทำทั้งหมด เราสามารถใช้ข้อมูลที่ bind ไว้ใน layout ได้

<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@{buttonData.buttonImageSrc}"> // <---- </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="@{buttonData.buttonText}" // <---- android:textSize="16sp" />
การใช้ variable buttonData ที่ bind ไว้

และผลลัพธ์คือ:

เชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่น
ลื่นไหลมากใช่ไหม?

รอสักครู่…

เรามี 3 ปุ่ม แต่ data class ใช้ได้แค่ 1 ปุ่ม แล้วจะผ่านได้อย่างไร?

package com.tomerpacific.example data class ButtonsData(val buttonsData : List<ButtonData>) { fun get(index: Int) : ButtonData { return buttonsData.get(index) } }
ButtonsData.kt

เราต้อง override method get เพื่อให้รู้จักเมื่อใช้ใน layout

จากนั้น เปลี่ยน reference ใน activity_main.xml:

 <data> <variable name="buttonsData" type="com.tomerpacific.example.ButtonsData"/> </data>
เปลี่ยนชื่อ variable และ class ให้ตรงกับ ButtonsData

และสร้าง binding ใหม่กับ data class ใหม่:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) val firstButton : ButtonData = ButtonData("First", resources.getDrawable(R.drawable.android)) val secondButton : ButtonData = ButtonData("Second", resources.getDrawable(R.drawable.android_p_logo)) val thirdButton : ButtonData = ButtonData("Third", resources.getDrawable(R.drawable.android_studio_icon)) val buttonsData : ButtonsData = ButtonsData(listOf(firstButton, secondButton, thirdButton)) binding.buttonsData = buttonsData }
MainActivity.kt

เราสร้าง 3 instances ของ ButtonData แล้ว instantiate ButtonsData object และ attach กับ binding object

สุดท้าย ใช้ data class ใน layout ได้อย่างถูกต้อง:

 <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:orientation="vertical" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/linearLayout3" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2"> <ImageButton android:layout_width="100dp" android:layout_height="100dp" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@{buttonsData.get(0).buttonImageSrc}"> // <------- </ImageButton> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="@{buttonsData.get(0).buttonText}" // <-------- android:textSize="16sp" /> </LinearLayout>
ตัวอย่างการใช้ element หนึ่ง
เชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่น
ใช้งานได้สบายๆ

บทความนี้เขียนจากประสบการณ์พัฒนาแอปนี้:

(อาจจะมีประโยชน์ก็ได้)

Laundry Symbols - Apps on Google Playเคยยุ่งยากกับสัญลักษณ์ซักผ้า? โชคดี! แอปนี้มีคำอธิบายสัญลักษณ์แปลกๆ ทั้งหมด พร้อม UI ง่ายและรูปภาพเชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่นtomerpacificApps on Google Playเชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่น
ดาวน์โหลดจาก Google Play Store

ดูซอร์สโค้ดเต็มได้ที่:

TomerPacific/LaundrySymbolsแอปพลิเคชันเขียนด้วย Kotlin แสดงสัญลักษณ์ซักผ้าต่างๆ พร้อมคำอธิบาย :cyclone: - TomerPacific/LaundrySymbolsเชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่นTomerPacificGitHubเชี่ยวชาญ Android Data Binding: ผสานข้อมูล UI อย่างราบรื่น
ดูซอร์สโค้ดเต็ม