Codelab นี้เป็นส่วนหนึ่งของหลักสูตรพื้นฐานเกี่ยวกับ Kotlin ใน Android คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากทํางานผ่าน Codelab ตามลําดับ Codelab ของหลักสูตรทั้งหมดจะแสดงอยู่ในหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals
บทนำ
ใน Codelab ก่อนหน้าของบทเรียนนี้ คุณได้ดูวิธีรับข้อมูลอสังหาริมทรัพย์บนดาวอังคารจากบริการทางเว็บ รวมทั้งวิธีสร้าง RecyclerView
ด้วยเลย์เอาต์แบบตารางกริดเพื่อโหลดและแสดงรูปภาพจากข้อมูลดังกล่าว ใน Codelab นี้ คุณทําแอป MarsRealEstate ให้เสร็จโดยนําความสามารถในการกรองพร็อพเพอร์ตี้ Mars มาใช้งานโดยให้เช่าหรือซื้อ นอกจากนี้ คุณยังสร้างมุมมองรายละเอียดได้ด้วย หากผู้ใช้แตะรูปภาพพร็อพเพอร์ตี้ในภาพรวม ผู้ใช้จะเห็นมุมมองรายละเอียดที่มีรายละเอียดเกี่ยวกับพร็อพเพอร์ตี้นั้น
สิ่งที่ควรทราบอยู่แล้ว
- วิธีสร้างและใช้ส่วนย่อย
- วิธีไปยังส่วนต่างๆ และ Fragment (ปลั๊กอิน Gradle) เพื่อส่งข้อมูลระหว่างส่วนย่อย
- วิธีใช้คอมโพเนนต์สถาปัตยกรรมรวมถึงโมเดลมุมมอง ดูโรงงานโมเดล การเปลี่ยนรูปแบบ และ
LiveData
- วิธีเรียกข้อมูลที่เข้ารหัส JSON จากบริการเว็บ REST และแยกวิเคราะห์ข้อมูลดังกล่าวลงในออบเจ็กต์ Kotlin ด้วยไลบรารี Retrofit และ Moshi
สิ่งที่จะได้เรียนรู้
- วิธีใช้นิพจน์การเชื่อมโยงที่ซับซ้อนในไฟล์เลย์เอาต์
- วิธีส่งคําขอ Retrofit ไปยังบริการเว็บด้วยตัวเลือกการค้นหา
สิ่งที่คุณจะทํา
- แก้ไขแอป MarsRealEstate เพื่อทําเครื่องหมายคุณสมบัติของดาวอังคารที่ขาย (เทียบกับวัตถุที่ให้เช่า) ด้วยไอคอนเครื่องหมายดอลลาร์
- ใช้เมนูตัวเลือกในหน้าภาพรวมเพื่อสร้างคําขอบริการเว็บที่กรองพร็อพเพอร์ตี้ดาวอังคารตามประเภท
- สร้างส่วนย่อยแบบละเอียดสําหรับพร็อพเพอร์ตี้ดาวอังคาร แล้วเชื่อมโยงส่วนย่อยนั้นไปยังตารางกริดภาพรวมด้วยการนําทาง และส่งผ่านข้อมูลพร็อพเพอร์ตี้ไปยังส่วนย่อยนั้น
ใน Codelab (และ Codelab ที่เกี่ยวข้อง) คุณใช้งานแอปที่ชื่อว่า MarsRealEstate ซึ่งแสดงพร็อพเพอร์ตี้สําหรับขายบนดาวอังคาร แอปนี้เชื่อมต่อกับเซิร์ฟเวอร์อินเทอร์เน็ตเพื่อดึงข้อมูลและแสดงข้อมูลพร็อพเพอร์ตี้ รวมถึงรายละเอียดต่างๆ เช่น ราคา และพร็อพเพอร์ตี้พร้อมให้ขายหรือให้เช่า รูปภาพที่เป็นตัวแทนของแต่ละสถานที่คือรูปภาพในชีวิตจริงจากดาวอังคารที่ถ่ายจากหุ่นยนต์สํารวจดาวอังคารของ NASA' ใน Codelab ก่อนหน้า ให้คุณสร้าง RecyclerView
ที่มีเลย์เอาต์แบบตารางกริดสําหรับรูปภาพที่พักทั้งหมด
ในแอปเวอร์ชันนี้ คุณทํางานกับประเภทของพร็อพเพอร์ตี้ (เช่าหรือซื้อ) และเพิ่มไอคอนลงในเลย์เอาต์ตารางกริดเพื่อทําเครื่องหมายพร็อพเพอร์ตี้ที่ขายได้ ดังนี้
คุณแก้ไขเมนูตัวเลือกของแอปเพื่อกรองตารางกริดเพื่อแสดงเฉพาะพร็อพเพอร์ตี้ที่ให้เช่าหรือขาย โดยทําดังนี้
และสุดท้าย ให้คุณสร้างมุมมองรายละเอียดสําหรับพร็อพเพอร์ตี้แต่ละรายการ และคุณเชื่อมต่อไอคอนในตารางภาพรวมกับส่วนรายละเอียดนั้นด้วยการนําทาง โดยทําดังนี้
จนถึงตอนนี้ ข้อมูลพร็อพเพอร์ตี้ Mars เพียงส่วนเดียวที่คุณใช้จะเป็น URL ของรูปภาพพร็อพเพอร์ตี้ แต่ข้อมูลที่พักที่คุณระบุไว้ในชั้นเรียน MarsProperty
จะมีรหัส ราคา และประเภท (เช่าหรือขาย) ด้วย ตัวอย่างหน่วยความจํา JSON ที่คุณได้รับจากบริการเว็บในการรีเฟรชหน่วยความจํามีดังนี้
{
"price":8000000,
"id":"424908",
"type":"rent",
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},
ในงานนี้ คุณจะเริ่มทํางานกับพร็อพเพอร์ตี้ประเภทดาวอังคารเพื่อเพิ่มรูปภาพสัญลักษณ์ดอลลาร์ลงในพร็อพเพอร์ตี้ในหน้าภาพรวมที่ขาย
ขั้นตอนที่ 1: อัปเดต MarsProperty ให้รวมประเภทดังกล่าว
คลาส MarsProperty
จะกําหนดโครงสร้างข้อมูลของพร็อพเพอร์ตี้แต่ละรายการที่ให้บริการโดยเว็บ ใน Codelab ก่อนหน้า คุณใช้ไลบรารี Moshi เพื่อแยกวิเคราะห์การตอบกลับ JSON ดิบจากบริการเว็บ Mars ไปยังออบเจ็กต์ข้อมูล MarsProperty
แต่ละรายการ
ในขั้นตอนนี้ คุณจะต้องเพิ่มตรรกะในคลาส MarsProperty
เพื่อระบุว่าพร็อพเพอร์ตี้นั้นให้เช่าหรือไม่ (กล่าวคือ เป็นสตริง "rent"
หรือ "buy"
) โดยคุณจะใช้ตรรกะนี้ในที่มากกว่า 1 แห่ง จึงควรมีไว้ที่นี่ในชั้นเรียนข้อมูลมากกว่าการทําซ้ํา
- เปิดแอป MarsRealEstate จาก Codelab ล่าสุด (คุณดาวน์โหลด MarsRealEstateGrid ได้หากไม่มีแอป)
- เปิด
network/MarsProperty.kt
เพิ่มเนื้อความในคําจํากัดความคลาสMarsProperty
และเพิ่ม Getter ที่กําหนดเองสําหรับisRental
ที่ส่งคืนtrue
หากออบเจ็กต์เป็นประเภท"rent"
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) {
val isRental
get() = type == "rent"
}
ขั้นตอนที่ 2: อัปเดตเลย์เอาต์รายการตารางกริด
ตอนนี้คุณอัปเดตเลย์เอาต์รายการสําหรับตารางกริดรูปภาพเพื่อแสดงสัญลักษณ์ดอลลาร์ที่ถอนได้เฉพาะในรูปภาพพร็อพเพอร์ตี้ที่ขายเท่านั้น
นิพจน์การเชื่อมโยงข้อมูลช่วยให้คุณทดสอบนี้ได้ในเลย์เอาต์ XML สําหรับตารางกริด
- เปิด
res/layout/grid_view_item.xml
นี่คือไฟล์เลย์เอาต์สําหรับแต่ละเซลล์ในเลย์เอาต์ตารางกริดสําหรับRecyclerView
ปัจจุบันไฟล์มีเฉพาะองค์ประกอบ<ImageView>
สําหรับรูปภาพที่พัก - ในองค์ประกอบ
<data>
ให้เพิ่มองค์ประกอบ<import>
สําหรับคลาสView
คุณจะใช้การนําเข้าได้เมื่อคุณต้องการใช้คอมโพเนนต์ของชั้นเรียนภายในนิพจน์การเชื่อมโยงข้อมูลในไฟล์เลย์เอาต์ ในกรณีนี้ คุณจะใช้ค่าคงที่View.GONE
และView.VISIBLE
จึงต้องมีสิทธิ์เข้าถึงคลาสView
<import type="android.view.View"/>
- ล้อมรอบมุมมองรูปภาพทั้งหมดด้วย
FrameLayout
เพื่อให้สัญลักษณ์เงินตราสามารถวางซ้อนบนรูปภาพพร็อพเพอร์ตี้ได้
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>
- สําหรับ
ImageView
ให้เปลี่ยนแอตทริบิวต์android:layout_height
เป็นmatch_parent
เพื่อป้อนข้อมูลระดับบนสุดใหม่FrameLayout
android:layout_height="match_parent"
- เพิ่มองค์ประกอบ
<ImageView>
รายการที่ 2 ด้านล่างองค์ประกอบแรกภายในFrameLayout
โปรดใช้คําจํากัดความที่แสดงด้านล่าง รูปภาพนี้จะปรากฏในมุมขวาล่างของรายการตารางกริด ที่ด้านบนของรูปภาพดาวอังคาร และใช้รูปที่วาดได้ตามที่กําหนดไว้ในres/drawable/ic_for_sale_outline.xml
สําหรับไอคอนเครื่องหมายดอลลาร์
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
tools:src="@drawable/ic_for_sale_outline"/>
- เพิ่มแอตทริบิวต์
android:visibility
ลงในมุมมองรูปภาพของmars_property_type
ใช้นิพจน์การเชื่อมโยงเพื่อทดสอบประเภทพร็อพเพอร์ตี้ และกําหนดระดับการเข้าถึงให้กับView.GONE
(สําหรับการเช่า) หรือView.VISIBLE
(สําหรับการซื้อ)
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
จนถึงตอนนี้คุณเห็นนิพจน์การเชื่อมโยงในเลย์เอาต์ที่ใช้ตัวแปรเดี่ยวที่กําหนดไว้ในองค์ประกอบ <data>
เท่านั้น การเชื่อมโยงการเชื่อมโยงมีประสิทธิภาพอย่างยิ่ง และช่วยให้คุณสามารถดําเนินการต่างๆ เช่น การทดสอบและการคํานวณคณิตศาสตร์ทั้งหมดในการจัดวาง XML ในกรณีนี้ คุณจะต้องใช้โอเปอเรเตอร์สามส่วน (?:
) เพื่อทําการทดสอบ (ออบเจ็กต์นี้เป็นอุปกรณ์เช่า) คุณระบุผลลัพธ์จริง 1 รายการ (ซ่อนไอคอนเครื่องหมายดอลลาร์ด้วย View.GONE
) และอีกรายการหนึ่งสําหรับเท็จ (แสดงไอคอนนั้นด้วย View.VISIBLE
)
ไฟล์ grid_view_item.xml
ที่สมบูรณ์ใหม่จะแสดงด้านล่าง
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View"/>
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
android:padding="2dp"
app:imageUrl="@{property.imgSrcUrl}"
tools:src="@tools:sample/backgrounds/scenic"/>
<ImageView
android:id="@+id/mars_property_type"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_gravity="bottom|end"
android:adjustViewBounds="true"
android:padding="5dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_for_sale_outline"
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
tools:src="@drawable/ic_for_sale_outline"/>
</FrameLayout>
</layout>
- คอมไพล์และเรียกใช้แอป และโปรดทราบว่าพร็อพเพอร์ตี้ที่ไม่ใช่การเช่ามีไอคอนเครื่องหมายดอลลาร์
ปัจจุบันแอปของคุณแสดงพร็อพเพอร์ตี้ Mars ทั้งหมดในตารางภาพรวม หากผู้ใช้กําลังเลือกซื้อที่พักให้เช่าบนดาวอังคาร การมีไอคอนซึ่งระบุที่พักที่พร้อมขายจะเป็นประโยชน์ แต่ก็ยังมีที่พักอื่นๆ จํานวนมากที่ต้องเลื่อนดู ในงานนี้ คุณจะได้เพิ่มเมนูตัวเลือกลงใน Fragment ภาพรวมที่อนุญาตให้ผู้ใช้แสดงเฉพาะบริการเช่าวิดีโอ เฉพาะพร็อพเพอร์ตี้ที่ประกาศขาย หรือแสดงทั้งหมด
วิธีหนึ่งที่คุณทําได้คือการทดสอบประเภทของ MarsProperty
แต่ละรายการในภาพรวมและแสดงเฉพาะพร็อพเพอร์ตี้ที่ตรงกัน แต่บริการเว็บ Mars จริงจะมีพารามิเตอร์หรือตัวเลือกการค้นหา (เรียกว่า filter
) ซึ่งช่วยให้คุณได้รับพร็อพเพอร์ตี้ประเภท rent
หรือประเภท buy
เท่านั้น คุณสามารถใช้การค้นหาตัวกรองนี้กับ URL ของบริการเว็บ realestate
ได้ในเบราว์เซอร์ดังนี้
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy
ในงานนี้ คุณจะได้แก้ไขคลาส MarsApiService
เพื่อเพิ่มตัวเลือกการค้นหาไปยังคําขอบริการเว็บด้วย Retrofit จากนั้นเปิดเมนูตัวเลือกเพื่อดาวน์โหลดข้อมูลพร็อพเพอร์ตี้ดาวอังคารทั้งหมดอีกครั้งโดยใช้ตัวเลือกการค้นหานั้น เนื่องจากคําตอบที่ได้รับจากบริการเว็บมีเฉพาะพร็อพเพอร์ตี้ที่คุณสนใจเท่านั้น จึงไม่จําเป็นต้องเปลี่ยนตรรกะการแสดงผลข้อมูลพร็อพเพอร์ตี้ของตารางกริดภาพรวมเลย
ขั้นตอนที่ 1: อัปเดตบริการ Mars API
หากต้องการเปลี่ยนคําขอ คุณต้องกลับไปที่ชั้นเรียน MarsApiService
ที่คุณใช้ใน Codelab แรกในชุดนี้ คุณจะแก้ไขคลาสเพื่อระบุ API การกรองได้
- เปิด
network/MarsApiService.kt
ใต้การนําเข้า ให้สร้างenum
ที่ชื่อMarsApiFilter
เพื่อกําหนดค่าคงที่ที่ตรงกับค่าคําค้นหาที่บริการบนเว็บต้องการ
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }
- แก้ไขเมธอด
getProperties()
เพื่อใช้สตริงสตริงสําหรับการค้นหาตัวกรอง และใส่คําอธิบายประกอบในอินพุตดังกล่าวด้วย@Query("filter")
ดังที่แสดงด้านล่าง
นําเข้าretrofit2.http.Query
เมื่อระบบแจ้ง
คําอธิบายประกอบ@Query
จะบอกเมธอดgetProperties()
(ซึ่งก็คือ "การเพิกถอน") เพื่อสร้างคําขอบริการเว็บที่มีตัวเลือกตัวกรอง ทุกครั้งที่มีการเรียกgetProperties()
URL คําขอจะมีส่วน?filter=type
ซึ่งสั่งให้บริการเว็บตอบกลับด้วยผลลัพธ์ที่ตรงกับคําค้นหานั้น
fun getProperties(@Query("filter") type: String):
ขั้นตอนที่ 2: อัปเดตโมเดลมุมมองภาพรวม
คุณขอข้อมูลจาก MarsApiService
ในเมธอด getMarsRealEstateProperties()
ใน OverviewViewModel
ตอนนี้คุณต้องอัปเดตคําขอดังกล่าวเพื่อรับอาร์กิวเมนต์ตัวกรอง
- เปิด
overview/OverviewViewModel.kt
คุณจะเห็นข้อผิดพลาดใน Android Studio เนื่องจากการเปลี่ยนแปลงที่คุณทําในขั้นตอนก่อนหน้า เพิ่มMarsApiFilter
(การแจกแจงค่าตัวกรองที่เป็นไปได้) เป็นพารามิเตอร์ในการเรียกgetMarsRealEstateProperties()
นําเข้าcom.example.android.marsrealestate.network.MarsApiFilter
ตามคําขอ
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- แก้ไขการเรียกใช้
getProperties()
ในบริการ Retrofit เพื่อส่งต่อคําค้นหาของตัวกรองนั้นเป็นสตริง
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
- ในบล็อก
init {}
ให้ส่งMarsApiFilter.SHOW_ALL
เป็นอาร์กิวเมนต์ไปยังgetMarsRealEstateProperties()
เพื่อแสดงพร็อพเพอร์ตี้ทั้งหมดเมื่อแอปโหลดเป็นครั้งแรก
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
- ในตอนท้ายของชั้นเรียน ให้เพิ่มเมธอด
updateFilter()
ที่มีอาร์กิวเมนต์MarsApiFilter
และเรียกgetMarsRealEstateProperties()
ด้วยอาร์กิวเมนต์นั้น
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}
ขั้นตอนที่ 3: เชื่อมต่อส่วนย่อยกับเมนูตัวเลือก
ขั้นตอนสุดท้ายคือการเชื่อมต่อเมนูรายการเพิ่มเติมกับส่วนย่อยเพื่อเรียก updateFilter()
ในโมเดลข้อมูลพร็อพเพอร์ตี้เมื่อผู้ใช้เลือกตัวเลือกเมนู
- เปิด
res/menu/overflow_menu.xml
แอป MarsRealEstate มีเมนูรายการเพิ่มเติมอยู่แล้วซึ่งมี 3 ตัวเลือก ได้แก่ การแสดงพร็อพเพอร์ตี้ทั้งหมด แสดงเฉพาะบริการเช่า และแสดงเฉพาะพร็อพเพอร์ตี้ที่ประกาศขาย
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/show_all_menu"
android:title="@string/show_all" />
<item
android:id="@+id/show_rent_menu"
android:title="@string/show_rent" />
<item
android:id="@+id/show_buy_menu"
android:title="@string/show_buy" />
</menu>
- เปิด
overview/OverviewFragment.kt
ในตอนท้ายของคลาส ให้ใช้เมธอดonOptionsItemSelected()
เพื่อจัดการการเลือกรายการในเมนู
override fun onOptionsItemSelected(item: MenuItem): Boolean {
}
- ใน
onOptionsItemSelected()
ให้เรียกใช้เมธอดupdateFilter()
ในโมเดลข้อมูลพร็อพเพอร์ตี้ด้วยตัวกรองที่เหมาะสม ใช้บล็อก Kotlinwhen {}
เพื่อสลับระหว่างตัวเลือก ใช้MarsApiFilter.SHOW_ALL
สําหรับค่าเริ่มต้นของตัวกรอง ส่งคืนtrue
เนื่องจากคุณจัดการรายการในเมนู นําเข้าMarsApiFilter
(com.example.android.marsrealestate.network.MarsApiFilter
) เมื่อส่งคําขอ เมธอดonOptionsItemSelected()
ที่สมบูรณ์จะแสดงอยู่ด้านล่าง
override fun onOptionsItemSelected(item: MenuItem): Boolean {
viewModel.updateFilter(
when (item.itemId) {
R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
else -> MarsApiFilter.SHOW_ALL
}
)
return true
}
- คอมไพล์และเรียกใช้แอป แอปจะเปิดตารางกริดภาพรวมแรกด้วยพร็อพเพอร์ตี้ทุกประเภทและพร็อพเพอร์ตี้สําหรับขายที่มีไอคอนดอลลาร์กํากับ
- เลือกเช่าจากเมนูตัวเลือก ระบบจะโหลดพร็อพเพอร์ตี้ซ้ําและไม่มีไอคอนใดปรากฏขึ้นพร้อมกับไอคอนดอลลาร์ (แสดงเฉพาะคุณสมบัติที่พักให้เช่า) คุณอาจต้องรอสักครู่เพื่อให้จอแสดงผลรีเฟรชเพื่อแสดงเฉพาะพร็อพเพอร์ตี้ที่กรอง
- เลือกซื้อจากเมนูตัวเลือก พร็อพเพอร์ตี้จะโหลดซ้ําอีกครั้ง และพร็อพเพอร์ตี้ทั้งหมดจะปรากฏพร้อมกับไอคอนดอลลาร์ (แสดงเฉพาะพร็อพเพอร์ตี้ที่ประกาศขาย)
ตอนนี้คุณจะมีตารางไอคอนแบบเลื่อนสําหรับพร็อพเพอร์ตี้ดาวอังคารแล้ว แต่ก็ถึงเวลาดูรายละเอียดเพิ่มเติมแล้ว ในงานนี้ คุณจะได้เพิ่มส่วนหนึ่งของรายละเอียดเพื่อแสดงรายละเอียดของพร็อพเพอร์ตี้นั้น ส่วนย่อยรายละเอียดจะแสดงรูปภาพขนาดใหญ่ ราคา และประเภทที่พัก ไม่ว่าจะเป็นบริการให้เช่าหรือขาย
ส่วนย่อยนี้จะเปิดขึ้นเมื่อผู้ใช้แตะรูปภาพในตารางภาพรวม หากต้องการทําเช่นนี้ คุณต้องเพิ่ม Listener onClick
ไปยังรายการในตาราง RecyclerView
แล้วไปยังส่วนใหม่ คุณไปยังส่วนต่างๆ ได้โดยทําให้เกิดการเปลี่ยนแปลง LiveData
ใน ViewModel
เช่นเดียวกับที่เรียนรู้ไปในบทเรียนเหล่านี้ นอกจากนี้ คุณยังใช้ปลั๊กอินการนําทางของ Fragment เพื่อความปลอดภัยของ Fragment เพื่อส่งข้อมูล MarsProperty
ที่เลือกจาก Fragment ภาพรวมไปยัง Fragment รายละเอียดได้ด้วย
ขั้นตอนที่ 1: สร้างโมเดลมุมมองรายละเอียดและอัปเดตเลย์เอาต์รายละเอียด
ตอนนี้คุณต้องใช้โมเดลมุมมองและไฟล์เลย์เอาต์สําหรับส่วนย่อยรายละเอียด เช่นเดียวกับกระบวนการที่คุณใช้กับโมเดลมุมมองภาพรวมและส่วนย่อย
- เปิด
detail/DetailViewModel.kt
เหมือนกับโฟลเดอร์ Kotlin ที่เกี่ยวข้องกับเครือข่ายอยู่ในโฟลเดอร์network
และไฟล์ภาพรวมในoverview
โฟลเดอร์detail
จะมีไฟล์ที่เกี่ยวข้องกับมุมมองรายละเอียด โปรดสังเกตว่าคลาสDetailViewModel
(ว่างเปล่าอยู่ในขณะนี้) ใช้marsProperty
เป็นพารามิเตอร์ในเครื่องมือสร้าง
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- ในคําจํากัดความชั้นเรียน ให้เพิ่ม
LiveData
สําหรับพร็อพเพอร์ตี้ Mars ที่เลือกไว้ เพื่อให้ระบบแสดงข้อมูลนั้นในมุมมองรายละเอียด ทําตามรูปแบบปกติของการสร้างMutableLiveData
เพื่อเก็บMarsProperty
เอง จากนั้นเปิดเผยพร็อพเพอร์ตี้LiveData
สาธารณะที่เปลี่ยนแปลงไม่ได้
นําเข้าandroidx.lifecycle.LiveData
และนําเข้าandroidx.lifecycle.MutableLiveData
ตามคําขอ
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
- สร้างบล็อก
init {}
และตั้งค่าของพร็อพเพอร์ตี้ Mars ที่เลือกด้วยออบเจ็กต์MarsProperty
จากเครื่องมือสร้าง
init {
_selectedProperty.value = marsProperty
}
- เปิด
res/layout/fragment_detail.xml
แล้วดูในมุมมองการออกแบบ
นี่คือไฟล์เลย์เอาต์สําหรับส่วนย่อยรายละเอียด โดยจะมีImageView
สําหรับรูปภาพขนาดใหญ่,TextView
สําหรับประเภทที่พัก (การเช่าหรือการขาย) และTextView
สําหรับราคา โปรดทราบว่ารูปแบบข้อจํากัดจะล้อมรอบด้วยScrollView
เพื่อให้เลื่อนโดยอัตโนมัติหากมุมมองมีขนาดใหญ่เกินไปสําหรับจอแสดงผล เช่น เมื่อผู้ใช้ดูในโหมดแนวนอน - ไปที่แท็บข้อความสําหรับเลย์เอาต์ ที่ด้านบนของเลย์เอาต์ ให้เพิ่มองค์ประกอบ
<data>
เพื่อเชื่อมโยงโมเดลมุมมองรายละเอียดกับเลย์เอาต์ ก่อนองค์ประกอบ<ScrollView>
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
- เพิ่มแอตทริบิวต์
app:imageUrl
ลงในองค์ประกอบImageView
ตั้งค่าเป็นimgSrcUrl
จากพร็อพเพอร์ตี้ข้อมูลพร็อพเพอร์ตี้โมเดลที่เลือก
ระบบจะนําอะแดปเตอร์การเชื่อมโยงที่โหลดรูปภาพโดยใช้ Glide มาที่นี่โดยอัตโนมัติด้วย เนื่องจากอะแดปเตอร์ดังกล่าวดูแอตทริบิวต์app:imageUrl
ทั้งหมด
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"
ขั้นตอนที่ 2: กําหนดการนําทางในโมเดลมุมมองภาพรวม
เมื่อผู้ใช้แตะรูปภาพในโมเดลภาพรวม รูปภาพควรเรียกไปยังส่วนย่อยที่แสดงรายละเอียดเกี่ยวกับรายการที่คลิก
- เปิด
overview/OverviewViewModel.kt
เพิ่มพร็อพเพอร์ตี้_navigateToSelectedProperty
MutableLiveData
และแสดงด้วยLiveData
ที่เปลี่ยนแปลงไม่ได้
เมื่อค่าLiveData
เปลี่ยนเป็น "ไม่มี" ระบบจะทริกเกอร์การนําทาง (ในเร็วๆ นี้ คุณจะเพิ่มโค้ดเพื่อสังเกตตัวแปรนี้และเรียกใช้การนําทาง)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty
- เมื่อสิ้นสุดคลาส ให้เพิ่มเมธอด
displayPropertyDetails()
ที่ตั้งค่า _navigateToSelectedProperty
ไปยังพร็อพเพอร์ตี้ Mars ที่เลือก
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}
- เพิ่มเมธอด
displayPropertyDetailsComplete()
เป็นค่าว่างของ_navigateToSelectedProperty
การดําเนินการเหล่านี้ต้องทําเพื่อทําเครื่องหมายสถานะการนําทางว่าเสร็จสมบูรณ์ และเพื่อหลีกเลี่ยงไม่ให้ระบบเรียกใช้การนําทางอีกครั้งเมื่อผู้ใช้กลับมาจากมุมมองรายละเอียด
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}
ขั้นตอนที่ 3: ตั้งค่า Listener การคลิกในอะแดปเตอร์และส่วนย่อยของตารางกริด
- เปิด
overview/PhotoGridAdapter.kt
ในตอนท้ายของชั้นเรียน ให้สร้างชั้นเรียนOnClickListener
ที่กําหนดเองซึ่งต้องแลมบ์ดาด้วยพารามิเตอร์marsProperty
ภายในคลาส ให้กําหนดฟังก์ชันonClick()
ที่ตั้งค่าเป็นพารามิเตอร์แลมบ์ดา
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
- เลื่อนขึ้นไปที่คําจํากัดความของคลาสสําหรับ
PhotoGridAdapter
แล้วเพิ่มพร็อพเพอร์ตี้OnClickListener
ส่วนตัวลงในเครื่องมือสร้าง
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- ทําให้รูปภาพคลิกได้โดยการเพิ่ม
onClickListener
ไปยังรายการในตารางกริดในวิธีonBindviewHolder()
กําหนด Listener การคลิกระหว่างการเรียกไปยังgetItem() and bind()
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}
- เปิด
overview/OverviewFragment.kt
ในเมธอดonCreateView()
ให้แทนที่บรรทัดที่จะเริ่มต้นพร็อพเพอร์ตี้binding.photosGrid.adapter
ด้วยบรรทัดที่แสดงด้านล่าง
โค้ดนี้จะเพิ่มออบเจ็กต์PhotoGridAdapter.onClickListener
ในเครื่องมือสร้างPhotoGridAdapter
และเรียกviewModel.displayPropertyDetails()
ด้วยออบเจ็กต์MarsProperty
ที่ส่งผ่าน ซึ่งจะทริกเกอร์LiveData
ในรูปแบบข้อมูลพร็อพเพอร์ตี้สําหรับการนําทาง
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})
ขั้นตอนที่ 4: แก้ไขกราฟการนําทางและทําให้ MarsProperty แยกวิเคราะห์ได้
เมื่อผู้ใช้แตะรูปภาพในตารางกริดภาพรวม แอปควรไปที่ส่วนรายละเอียดและผ่านรายละเอียดของพร็อพเพอร์ตี้ดาวอังคารที่เลือกเพื่อให้มุมมองรายละเอียดแสดงข้อมูลนั้นได้
ขณะนี้คุณมี Listener การคลิกจาก PhotoGridAdapter
เพื่อจัดการการแตะ และวิธีทริกเกอร์การนําทางจากโมเดลข้อมูลพร็อพเพอร์ตี้แล้ว แต่ยังไม่มีออบเจ็กต์ MarsProperty
ที่ส่งไปยัง Fragment รายละเอียด สําหรับคุณได้ใช้ Safe Args จากคอมโพเนนต์การนําทาง
- เปิด
res/navigation/nav_graph.xml
คลิกแท็บข้อความเพื่อดูโค้ด XML ของกราฟการนําทาง - ภายในองค์ประกอบ
<fragment>
สําหรับส่วนย่อยรายละเอียด ให้เพิ่มองค์ประกอบ<argument>
ที่แสดงด้านล่าง อาร์กิวเมนต์นี้ชื่อselectedProperty
เป็นประเภทMarsProperty
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
- คอมไพล์แอป การนําทางมีข้อผิดพลาดให้คุณเนื่องจาก
MarsProperty
ไม่พาร์เซล อินเทอร์เฟซParcelable
ช่วยให้เรียงอันดับออบเจ็กต์เพื่อให้ส่งผ่านข้อมูลได้ระหว่างส่วนย่อยหรือกิจกรรม ในกรณีนี้MarsProperty
ต้องใช้อินเทอร์เฟซParcelable
เพื่อให้ข้อมูลภายในออบเจ็กต์MarsProperty
ส่งไปยังส่วนย่อยรายละเอียดผ่าน Safe Args ข่าวดีคือ Kotlin มีทางลัดง่ายๆ สําหรับการใช้งานอินเทอร์เฟซดังกล่าว - เปิด
network/MarsProperty.kt
เพิ่มคําอธิบายประกอบ@Parcelize
ลงในคําจํากัดความชั้นเรียน
นําเข้าkotlinx.android.parcel.Parcelize
เมื่อมีการร้องขอ
คําอธิบายประกอบ@Parcelize
ใช้ส่วนขยาย Kotlin สําหรับ Android เพื่อใช้วิธีการโดยอัตโนมัติในอินเทอร์เฟซParcelable
สําหรับคลาสนี้ คุณไม่จําเป็นต้องดําเนินการใดๆ เพิ่มเติม
@Parcelize
data class MarsProperty (
- เปลี่ยนคําจํากัดความคลาสของ
MarsProperty
เพื่อขยายParcelable
นําเข้าandroid.os.Parcelable
เมื่อมีการขอ
ขณะนี้การกําหนดคลาสMarsProperty
มีลักษณะเช่นนี้
@Parcelize
data class MarsProperty (
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) : Parcelable {
ขั้นตอนที่ 5: เชื่อมต่อส่วนย่อย
คุณยังคงไม่ไปยังส่วนต่างๆ การนําทางจริงจะปรากฏในส่วนย่อย ในขั้นตอนนี้ ให้เพิ่มบิตสุดท้ายในการใช้การนําทางระหว่างภาพรวมและส่วนย่อยรายละเอียด
- เปิด
overview/OverviewFragment.kt
ในonCreateView()
ใต้บรรทัดเริ่มต้นอะแดปเตอร์ของตารางกริดรูปภาพ ให้เพิ่มบรรทัดที่แสดงด้านล่างเพื่อให้สังเกตnavigatedToSelectedProperty
จากโมเดลมุมมองภาพรวม
นําเข้าandroidx.lifecycle.Observer
และนําเข้าandroidx.navigation.fragment.findNavController
ตามคําขอ
ผู้สังเกตการณ์จะทดสอบว่าMarsProperty
it
ในแลมบ์ดาไม่ใช่ Null หรือไม่ และหากเป็นเช่นนั้น ระบบจะใช้เครื่องมือนําทางจากส่วนย่อยที่มีfindNavController()
เรียกdisplayPropertyDetailsComplete()
เพื่อบอกโมเดลมุมมองให้รีเซ็ตLiveData
เป็นสถานะ Null เพื่อไม่ให้คุณทริกเกอร์การนําทางอีกครั้งเมื่อแอปกลับไปที่OverviewFragment
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})
- เปิด
detail/DetailFragment.kt
เพิ่มบรรทัดนี้ใต้สายที่โทรหาsetLifecycleOwner()
ในเมธอดonCreateView()
บรรทัดนี้ได้รับออบเจ็กต์MarsProperty
ที่เลือกจาก Google Safe Browsing
โปรดสังเกตการใช้โอเปอเรเตอร์การยืนยัน Kotlin' (!!
) หากไม่พบselectedProperty
อยู่ มีข้อผิดพลาดเกิดขึ้นและคุณต้องการให้โค้ดส่งตัวชี้ Null (ในโค้ดที่ใช้งานจริง คุณควรจัดการข้อผิดพลาดนั้นในลักษณะใดลักษณะหนึ่ง)
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
- เพิ่มบรรทัดถัดไปเพื่อรับ
DetailViewModelFactory
ใหม่ คุณจะใช้DetailViewModelFactory
เพื่อรับอินสแตนซ์ของDetailViewModel
แอปเริ่มต้นมีการติดตั้งใช้งานDetailViewModelFactory
สิ่งที่คุณต้องทําก็แค่เริ่มต้นใช้งาน
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
- สุดท้าย ให้เพิ่มสายนี้เพื่อรับ
DetailViewModel
เป็นค่าเริ่มต้นและเชื่อมต่อทุกส่วน
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)
- คอมไพล์และเรียกใช้แอป แล้วแตะรูปภาพพร็อพเพอร์ตี้ Mars ส่วนย่อยของรายละเอียดจะปรากฏขึ้นสําหรับรายละเอียดของพร็อพเพอร์ตี้นั้น แตะปุ่ม "กลับ" เพื่อกลับไปที่หน้าภาพรวม แล้วสังเกตว่าหน้าจอรายละเอียดยังคงเป็นแถบที่เบาบาง เพิ่มข้อมูลพร็อพเพอร์ตี้ลงในหน้ารายละเอียดของงานถัดไปให้เสร็จ
ตอนนี้หน้ารายละเอียดจะแสดงเฉพาะรูปภาพของดาวอังคารเดียวกับที่คุณเห็นในหน้าภาพรวมเท่านั้น ชั้น MarsProperty
ยังมีประเภทที่พัก (เช่าหรือซื้อ) และราคาที่พักด้วย หน้าจอรายละเอียดควรมีทั้ง 2 ค่านี้และจะเป็นประโยชน์หากที่พักให้เช่าระบุว่าราคาเป็นค่ารายเดือน คุณมีการเปลี่ยนรูปแบบ LiveData
ในโมเดลข้อมูลพร็อพเพอร์ตี้เพื่อนําทั้ง 2 อย่างนี้มาใช้
- เปิด
res/values/strings.xml
โค้ดเริ่มต้นมีทรัพยากรสตริงดังที่แสดงด้านล่าง เพื่อช่วยคุณสร้างสตริงสําหรับมุมมองรายละเอียด สําหรับราคาคุณจะใช้ทรัพยากรdisplay_price_monthly_rental
หรือทรัพยากรdisplay_price
ทั้งนี้ขึ้นอยู่กับประเภทพร็อพเพอร์ตี้
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
- เปิด
detail/DetailViewModel.kt
เพิ่มโค้ดที่แสดงด้านล่างที่ด้านล่างของชั้นเรียน
นําเข้าandroidx.lifecycle.Transformations
หากระบบขอ
การเปลี่ยนรูปแบบนี้จะทดสอบว่าพร็อพเพอร์ตี้ที่เลือกเป็นที่พักให้เช่าหรือไม่ โดยใช้การทดสอบเดียวกันจากงานแรก หากพร็อพเพอร์ตี้เป็นบริการเช่าวิดีโอ การเปลี่ยนรูปแบบจะเลือกสตริงที่เหมาะสมจากทรัพยากรที่มีสวิตช์ Kotlinwhen {}
ทั้ง 2 สตริงนี้ต้องประกอบด้วยตัวเลขในตอนท้าย เพื่อให้คุณต่อproperty.price
หลังจากนั้น
val displayPropertyPrice = Transformations.map(selectedProperty) {
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.display_price_monthly_rental
false -> R.string.display_price
}, it.price)
}
- นําเข้าคลาส
R
ที่สร้างขึ้นเพื่อเข้าถึงทรัพยากรสตริงในโปรเจ็กต์
import com.example.android.marsrealestate.R
- หลังจากการเปลี่ยนรูปแบบ
displayPropertyPrice
ให้เพิ่มโค้ดที่แสดงด้านล่าง การเปลี่ยนแปลงนี้เชื่อมโยงทรัพยากรสตริงหลายรายการ โดยพิจารณาว่าที่พักเป็นที่พักให้เช่าหรือไม่
val displayPropertyType = Transformations.map(selectedProperty) {
app.applicationContext.getString(R.string.display_type,
app.applicationContext.getString(
when (it.isRental) {
true -> R.string.type_rent
false -> R.string.type_sale
}))
}
- เปิด
res/layout/fragment_detail.xml
สิ่งที่ต้องทําอีกอย่างก็คือการเชื่อมโยงสตริงใหม่ (ซึ่งคุณสร้างด้วยการเปลี่ยนรูปแบบLiveData
) กับมุมมองรายละเอียด ด้วยการตั้งค่าช่องข้อความสําหรับข้อความประเภทพร็อพเพอร์ตี้เป็นviewModel.displayPropertyType
ในช่องข้อความสําหรับข้อความค่าราคาเป็นviewModel.displayPropertyPrice
<TextView
android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
tools:text="To Rent" />
<TextView
android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
tools:text="$100,000" />
- คอมไพล์และเรียกใช้แอป ตอนนี้ข้อมูลพร็อพเพอร์ตี้ทั้งหมดปรากฏในหน้ารายละเอียดที่มีการจัดรูปแบบอย่างถูกต้อง
โปรเจ็กต์ Android Studio: MarsRealEstateFinal
การเชื่อมโยงการเชื่อมโยง
- ใช้นิพจน์การเชื่อมโยงในไฟล์เลย์เอาต์ XML เพื่อดําเนินการแบบเป็นโปรแกรมแบบเป็นโปรแกรม เช่น คณิตศาสตร์หรือการทดสอบแบบมีเงื่อนไข กับข้อมูลที่เชื่อมโยงกัน
- หากต้องการอ้างอิงชั้นเรียนในไฟล์เลย์เอาต์ ให้ใช้แท็ก
<import>
ในแท็ก<data>
ตัวเลือกการค้นหาบริการเว็บ
- คําขอไปยังบริการในเว็บอาจมีพารามิเตอร์ที่ไม่บังคับ
- หากต้องการระบุพารามิเตอร์การค้นหาในคําขอ ให้ใช้คําอธิบายประกอบ
@Query
ใน Retrofit
หลักสูตร Udacity:
เอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์ Android
- ภาพรวมของ ViewModel
- ภาพรวมของ LiveData
- การเชื่อมโยงอะแดปเตอร์
- เลย์เอาต์และการเชื่อมโยงการเชื่อมโยง
- การไปยังส่วนต่างๆ
- เริ่มต้นใช้งานคอมโพเนนต์การนําทาง
- ส่งผ่านข้อมูลระหว่างปลายทาง (และอธิบายเรื่องสัญญาณเตือนด้านความปลอดภัย)
- ชั้นเรียน
Transformations
- ชั้นเรียน
ViewModelProvider
- ชั้นเรียน
ViewModelProvider.Factory
อื่นๆ:
- เลื่อนผ่าน
- คลาส Retrofit Query
- ส่วนขยายเครื่องมือสร้าง Parcelable จาก Kotlin'
ส่วนนี้จะอธิบายการบ้านและรายงานสําหรับนักเรียนที่ทํางานผ่าน Codelab นี้ซึ่งเป็นส่วนหนึ่งของหลักสูตรที่นําโดยผู้สอน สิ่งที่ผู้สอนต้องทํามีดังนี้
- มอบหมายการบ้านหากจําเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานทําการบ้าน
- ตัดเกรดการบ้าน
ผู้สอนจะใช้คําแนะนําเหล่านี้เท่าใดก็ได้หรือตามที่ต้องการก็ได้ และสามารถกําหนดให้การบ้านอื่นๆ ที่ตนคิดว่าเหมาะสมได้
หากคุณใช้ Codelab ด้วยตัวเอง ก็ให้ใช้การบ้านเพื่อทดสอบความรู้ของคุณได้
ตอบคําถามเหล่านี้
คำถามที่ 1
แท็ก <import>
ในไฟล์เลย์เอาต์ XML ใช้ทําอะไรได้บ้าง
▢ จะรวมไฟล์เลย์เอาต์ 1 ไฟล์ไว้ในไฟล์อื่น
▢ ฝังโค้ด Kotlin ภายในไฟล์เลย์เอาต์
▢ ให้การเข้าถึงพร็อพเพอร์ตี้ที่ผูกข้อมูล
▢ ช่วยให้คุณสามารถอ้างอิงชั้นเรียนและสมาชิกในชั้นเรียนในนิพจน์การเชื่อมโยง
คำถามที่ 2
คุณจะเพิ่มตัวเลือกการค้นหาไปยังการเรียกใช้บริการเว็บ REST ใน Retrofit ได้อย่างไร
▢ ต่อท้ายการค้นหาต่อท้าย URL คําขอ
▢ เพิ่มพารามิเตอร์สําหรับการค้นหาไปยังฟังก์ชันที่ส่งคําขอ และใส่คําอธิบายประกอบในพารามิเตอร์นั้นด้วย @Query
▢ ใช้คลาส Query
เพื่อสร้างคําขอ
▢ ใช้วิธีการ addQuery()
ในเครื่องมือสร้าง Retrofit
เริ่มบทเรียนถัดไป:
สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals