Codelab นี้เป็นส่วนหนึ่งของหลักสูตรหลักพื้นฐานของ Android Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำตาม Codelab ตามลำดับ Codelab ของหลักสูตรทั้งหมดแสดงอยู่ในหน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin
บทนำ
ใน Codelab ก่อนหน้า คุณได้เรียนรู้วิธีรับข้อมูลจากเว็บเซอร์วิสและแยกวิเคราะห์การตอบกลับเป็นออบเจ็กต์ข้อมูล ในโค้ดแล็บนี้ คุณจะได้ใช้ความรู้ดังกล่าวเพื่อโหลดและแสดงรูปภาพจาก URL ของเว็บ นอกจากนี้ คุณยังได้ทบทวนวิธีสร้าง RecyclerView และใช้เพื่อแสดงตารางกริดของรูปภาพในหน้าภาพรวม
สิ่งที่คุณควรทราบอยู่แล้ว
- วิธีสร้างและใช้ Fragment
- วิธีใช้คอมโพเนนต์สถาปัตยกรรม รวมถึงโมเดลมุมมอง โรงงานโมเดลมุมมอง การแปลง และ
LiveData - วิธีดึงข้อมูล JSON จากเว็บเซอร์วิส REST และแยกวิเคราะห์ข้อมูลดังกล่าวเป็นออบเจ็กต์ Kotlin โดยใช้ไลบรารี Retrofit และ Moshi
- วิธีสร้างเลย์เอาต์ตารางกริดด้วย
RecyclerView - วิธีการทำงานของ
Adapter,ViewHolderและDiffUtil
สิ่งที่คุณจะได้เรียนรู้
- วิธีใช้ไลบรารี Glide เพื่อโหลดและแสดงรูปภาพจาก URL ของเว็บ
- วิธีใช้
RecyclerViewและอะแดปเตอร์กริดเพื่อแสดงตารางกริดของรูปภาพ - วิธีจัดการข้อผิดพลาดที่อาจเกิดขึ้นขณะดาวน์โหลดและแสดงรูปภาพ
สิ่งที่คุณต้องทำ
- แก้ไขแอป MarsRealEstate เพื่อรับ URL ของรูปภาพจากข้อมูลพร็อพเพอร์ตี้ของ Mars และใช้ Glide เพื่อโหลดและแสดงรูปภาพนั้น
- เพิ่มภาพเคลื่อนไหวการโหลดและไอคอนข้อผิดพลาดลงในแอป
- ใช้
RecyclerViewเพื่อแสดงตารางรูปภาพที่พักในดาวอังคาร - เพิ่มการจัดการสถานะและข้อผิดพลาดลงใน
RecyclerView
ในโค้ดแล็บนี้ (และโค้ดแล็บที่เกี่ยวข้อง) คุณจะได้ทำงานกับแอปชื่อ MarsRealEstate ซึ่งแสดงอสังหาริมทรัพย์ที่ขายบนดาวอังคาร แอปจะเชื่อมต่อกับเซิร์ฟเวอร์อินเทอร์เน็ตเพื่อดึงและแสดงข้อมูลที่พัก รวมถึงรายละเอียดต่างๆ เช่น ราคา และสถานะของที่พักว่าพร้อมขายหรือให้เช่าหรือไม่ รูปภาพที่แสดงถึงที่พักแต่ละแห่งเป็นรูปภาพจากดาวอังคารที่ถ่ายจากยานสำรวจดาวอังคารของ NASA

แอปเวอร์ชันที่คุณสร้างใน Codelab นี้จะเติมข้อมูลในหน้าภาพรวม ซึ่งจะแสดงตารางกริดของรูปภาพ รูปภาพเป็นส่วนหนึ่งของข้อมูลพร็อพเพอร์ตี้ที่แอปได้รับจากเว็บเซอร์วิสอสังหาริมทรัพย์ของ Mars แอปจะใช้ไลบรารี Glide เพื่อโหลดและแสดงรูปภาพ และใช้ RecyclerView เพื่อสร้างเลย์เอาต์ตารางกริดสำหรับรูปภาพ นอกจากนี้ แอปของคุณจะจัดการข้อผิดพลาดของเครือข่ายได้อย่างราบรื่น
การแสดงรูปภาพจาก URL ของเว็บอาจดูเหมือนเป็นเรื่องง่าย แต่ต้องมีการออกแบบทางวิศวกรรมพอสมควรเพื่อให้ทำงานได้ดี ระบบต้องดาวน์โหลด บัฟเฟอร์ และถอดรหัสรูปภาพจากรูปแบบที่บีบอัดเป็นรูปภาพที่ Android ใช้ได้ ควรแคชรูปภาพไปยังแคชในหน่วยความจำ แคชที่อิงตามพื้นที่เก็บข้อมูล หรือทั้ง 2 อย่าง การดำเนินการทั้งหมดนี้ต้องเกิดขึ้นในเธรดเบื้องหลังที่มีลำดับความสำคัญต่ำเพื่อให้ UI ตอบสนองได้ นอกจากนี้ เพื่อให้เครือข่ายและ CPU ทำงานได้ดีที่สุด คุณอาจต้องดึงข้อมูลและถอดรหัสรูปภาพมากกว่า 1 รูปพร้อมกัน การเรียนรู้วิธีโหลดรูปภาพจากเครือข่ายอย่างมีประสิทธิภาพอาจเป็น Codelab ในตัว
โชคดีที่คุณสามารถใช้ไลบรารีที่พัฒนาโดยชุมชนที่ชื่อ Glide เพื่อดาวน์โหลด บัฟเฟอร์ ถอดรหัส และแคชรูปภาพได้ Glide ช่วยให้คุณทำงานน้อยลงมากเมื่อเทียบกับการสร้างแอปทั้งหมดตั้งแต่ต้น
โดยพื้นฐานแล้ว Glide ต้องการ 2 สิ่งต่อไปนี้
- URL ของรูปภาพที่คุณต้องการโหลดและแสดง
- ออบเจ็กต์
ImageViewเพื่อแสดงรูปภาพนั้น
ในงานนี้ คุณจะได้เรียนรู้วิธีใช้ Glide เพื่อแสดงรูปภาพเดียวจากเว็บเซอร์วิสอสังหาริมทรัพย์ คุณแสดงรูปภาพที่แสดงถึงพร็อพเพอร์ตี้แรกของ Mars ในรายการพร็อพเพอร์ตี้ที่เว็บเซอร์วิสแสดงผล ภาพหน้าจอก่อนและหลังมีดังนี้


ขั้นตอนที่ 1: เพิ่มการอ้างอิง Glide
- เปิดแอป MarsRealEstate จากโค้ดแล็บล่าสุด (คุณดาวน์โหลด MarsRealEstateNetwork ได้ที่นี่หากไม่มีแอป)
- เรียกใช้แอปเพื่อดูว่าแอปทำอะไรได้บ้าง (แสดงรายละเอียดข้อความของพร็อพเพอร์ตี้ที่สมมติว่ามีอยู่บนดาวอังคาร)
- เปิด build.gradle (Module: app)
- ในส่วน
dependenciesให้เพิ่มบรรทัดนี้สำหรับไลบรารี Glide
implementation "com.github.bumptech.glide:glide:$version_glide"
โปรดทราบว่าหมายเลขเวอร์ชันมีการกำหนดแยกต่างหากในไฟล์ Gradle ของโปรเจ็กต์อยู่แล้ว
- คลิกซิงค์เลยเพื่อสร้างโปรเจ็กต์ใหม่โดยใช้การอ้างอิงใหม่
ขั้นตอนที่ 2: อัปเดตโมเดลมุมมอง
จากนั้นอัปเดตคลาส OverviewViewModel เพื่อรวมข้อมูลแบบเรียลไทม์สำหรับที่พักของ Mars รายการเดียว
- เปิด
overview/OverviewViewModel.ktเพิ่มทั้งข้อมูลสดภายใน (เปลี่ยนแปลงได้) และภายนอก (เปลี่ยนแปลงไม่ได้) สำหรับออบเจ็กต์MarsPropertyรายการเดียวที่อยู่ใต้LiveDataสำหรับ_response
นำเข้าMarsPropertyชั้นเรียน (com.example.android.marsrealestate.network.MarsProperty) เมื่อได้รับคำขอ
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property- ในเมธอด
getMarsRealEstateProperties()ให้ค้นหาบรรทัดภายในบล็อกtry/catch {}ที่ตั้งค่า_response.valueเป็นจำนวนพร็อพเพอร์ตี้ เพิ่มการทดสอบที่แสดงด้านล่าง หากมีออบเจ็กต์MarsPropertyการทดสอบนี้จะตั้งค่า_propertyLiveDataเป็นพร็อพเพอร์ตี้แรกในlistResult
if (listResult.size > 0) {
_property.value = listResult[0]
}ตอนนี้บล็อก try/catch {} ที่สมบูรณ์จะมีลักษณะดังนี้
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}- เปิดไฟล์
res/layout/fragment_overview.xmlในองค์ประกอบ<TextView>ให้เปลี่ยนandroid:textเพื่อเชื่อมโยงกับคอมโพเนนต์imgSrcUrlของpropertyLiveDataดังนี้
android:text="@{viewModel.property.imgSrcUrl}"- เรียกใช้แอป
TextViewจะแสดงเฉพาะ URL ของรูปภาพในพร็อพเพอร์ตี้ Mars แรก สิ่งที่คุณทำไปแล้วจนถึงตอนนี้คือการตั้งค่า ViewModel และข้อมูลแบบเรียลไทม์สำหรับ URL นั้น

ขั้นตอนที่ 3: สร้าง Binding Adapter และเรียกใช้ Glide
ตอนนี้คุณมี URL ของรูปภาพที่จะแสดงแล้ว และถึงเวลาเริ่มทำงานกับ Glide เพื่อโหลดรูปภาพนั้น ในขั้นตอนนี้ คุณจะใช้ตัวดัดแปลงการเชื่อมโยงเพื่อนำ URL จากแอตทริบิวต์ XML ที่เชื่อมโยงกับ ImageView และใช้ Glide เพื่อโหลดรูปภาพ Binding Adapter เป็นเมธอดส่วนขยายที่อยู่ระหว่างมุมมองกับข้อมูลที่เชื่อมโยงเพื่อกำหนดลักษณะการทำงานที่กำหนดเองเมื่อข้อมูลเปลี่ยนแปลง ในกรณีนี้ ลักษณะการทำงานที่กำหนดเองคือการเรียก Glide เพื่อโหลดรูปภาพจาก URL ลงใน ImageView
- เปิด
BindingAdapters.ktไฟล์นี้จะมี Binding Adapter ที่คุณใช้ทั่วทั้งแอป - สร้างฟังก์ชัน
bindImage()ที่ใช้ImageViewและStringเป็นพารามิเตอร์ ใส่คำอธิบายประกอบฟังก์ชันด้วย@BindingAdapterคำอธิบายประกอบ@BindingAdapterจะบอกการเชื่อมโยงข้อมูลว่าคุณต้องการให้เรียกใช้อะแดปเตอร์การเชื่อมโยงนี้เมื่อรายการ XML มีแอตทริบิวต์imageUrl
นำเข้าandroidx.databinding.BindingAdapterและandroid.widget.ImageViewเมื่อได้รับคำขอ
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}- ภายในฟังก์ชัน
bindImage()ให้เพิ่มบล็อกlet {}สำหรับอาร์กิวเมนต์imgUrlดังนี้
imgUrl?.let {
}- ภายในบล็อก
let {}ให้เพิ่มบรรทัดที่แสดงด้านล่างเพื่อแปลงสตริง URL (จาก XML) เป็นออบเจ็กต์Uriนำเข้าandroidx.core.net.toUriเมื่อได้รับคำขอ
คุณต้องการให้ออบเจ็กต์Uriสุดท้ายใช้รูปแบบ HTTPS เนื่องจากเซิร์ฟเวอร์ที่คุณดึงรูปภาพมาต้องใช้รูปแบบดังกล่าว หากต้องการใช้รูปแบบ HTTPS ให้ต่อท้ายbuildUpon.scheme("https")ในเครื่องมือสร้างtoUriเมธอดtoUri()เป็นฟังก์ชันส่วนขยาย Kotlin จากไลบรารีหลักของ Android KTX จึงดูเหมือนเป็นส่วนหนึ่งของคลาสString
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()- ยังคงอยู่ภายใน
let {}ให้เรียกใช้Glide.with()เพื่อโหลดรูปภาพจากออบเจ็กต์UriลงในImageViewนำเข้าcom.bumptech.glide.Glideเมื่อได้รับคำขอ
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)ขั้นตอนที่ 4: อัปเดตเลย์เอาต์และ Fragment
แม้ว่า Glide จะโหลดรูปภาพแล้ว แต่ก็ยังไม่มีอะไรให้เห็น ขั้นตอนถัดไปคือการอัปเดตเลย์เอาต์และ Fragment ด้วย ImageView เพื่อแสดงรูปภาพ
- เปิด
res/layout/gridview_item.xmlนี่คือไฟล์ทรัพยากรเลย์เอาต์ที่คุณจะใช้สำหรับแต่ละรายการในRecyclerViewในภายหลังในโค้ดแล็บ คุณใช้ที่นี่ชั่วคราวเพื่อแสดงรูปภาพเดียวเท่านั้น - เหนือองค์ประกอบ
<ImageView>ให้เพิ่มองค์ประกอบ<data>สำหรับการเชื่อมโยงข้อมูล และเชื่อมโยงกับคลาสOverviewViewModelดังนี้
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>- เพิ่มแอตทริบิวต์
app:imageUrlลงในองค์ประกอบImageViewเพื่อใช้ Binding Adapter การโหลดรูปภาพใหม่
app:imageUrl="@{viewModel.property.imgSrcUrl}"- เปิด
overview/OverviewFragment.ktในonCreateView()ให้แสดงความคิดเห็นในบรรทัดที่ขยายคลาสFragmentOverviewBindingและกำหนดให้กับตัวแปรการเชื่อมโยง ซึ่งเป็นการดำเนินการชั่วคราวเท่านั้น และคุณจะกลับไปใช้บัญชีเดิมได้ในภายหลัง
//val binding = FragmentOverviewBinding.inflate(inflater)- โปรดเพิ่มบรรทัดเพื่อขยายคลาส
GridViewItemBindingแทน นำเข้าcom.example.android.marsrealestate. databinding.GridViewItemBindingเมื่อได้รับคำขอ
val binding = GridViewItemBinding.inflate(inflater)- เรียกใช้แอป ตอนนี้คุณควรเห็นรูปภาพของรูปภาพจาก
MarsPropertyแรกในรายการผลการค้นหา
ขั้นตอนที่ 5: เพิ่มรูปภาพการโหลดและข้อผิดพลาดอย่างง่าย
Glide สามารถปรับปรุงประสบการณ์ของผู้ใช้ได้โดยการแสดงรูปภาพตัวยึดตำแหน่งขณะโหลดรูปภาพ และแสดงรูปภาพข้อผิดพลาดหากโหลดไม่สำเร็จ เช่น หากไม่มีรูปภาพหรือรูปภาพเสียหาย ในขั้นตอนนี้ คุณจะเพิ่มฟังก์ชันการทำงานดังกล่าวลงใน Binding Adapter และเลย์เอาต์
- เปิด
res/drawable/ic_broken_image.xmlแล้วคลิกแท็บแสดงตัวอย่างทางด้านขวา สำหรับรูปภาพข้อผิดพลาด คุณกำลังใช้ไอคอนรูปภาพเสียซึ่งมีอยู่ในคลังไอคอนในตัว Vector Drawable นี้ใช้แอตทริบิวต์android:tintเพื่อเปลี่ยนสีไอคอนเป็นสีเทา

- เปิด
res/drawable/loading_animation.xmlDrawable นี้คือภาพเคลื่อนไหวที่กำหนดด้วยแท็ก<animate-rotate>ภาพเคลื่อนไหวจะหมุน Drawable ของรูปภาพloading_img.xmlรอบจุดกึ่งกลาง (คุณจะไม่เห็นภาพเคลื่อนไหวในตัวอย่าง)

- กลับไปที่ไฟล์
BindingAdapters.ktในbindImage()ให้อัปเดตการเรียกใช้Glide.with()เพื่อเรียกใช้ฟังก์ชันapply()ระหว่างload()และinto()นำเข้าcom.bumptech.glide.request.RequestOptionsเมื่อมีการร้องขอ
โค้ดนี้จะตั้งค่ารูปภาพการโหลดตัวยึดตำแหน่งที่จะใช้ขณะโหลด (loading_animationdrawable) โค้ดยังตั้งค่ารูปภาพที่จะใช้หากโหลดรูปภาพไม่สำเร็จ (broken_imagedrawable) ตอนนี้bindImage()ที่สมบูรณ์จะมีลักษณะดังนี้
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- เรียกใช้แอป คุณอาจเห็นรูปภาพการโหลดชั่วครู่ขณะที่ Glide ดาวน์โหลดและแสดงรูปภาพพร็อพเพอร์ตี้ ทั้งนี้ขึ้นอยู่กับความเร็วของการเชื่อมต่อเครือข่าย แต่คุณจะยังไม่เห็นไอคอนรูปภาพเสีย แม้ว่าจะปิดเครือข่ายก็ตาม โดยคุณจะแก้ไขปัญหานี้ได้ในส่วนสุดท้ายของ Codelab
ตอนนี้แอปจะโหลดข้อมูลพร็อพเพอร์ตี้จากอินเทอร์เน็ต คุณใช้ข้อมูลจากรายการแรกในรายการ MarsProperty เพื่อสร้างพร็อพเพอร์ตี้ LiveData ใน View Model และใช้ URL ของรูปภาพจากข้อมูลพร็อพเพอร์ตี้นั้นเพื่อป้อนข้อมูลใน ImageView แต่เป้าหมายคือให้แอปแสดงตารางกริดของรูปภาพ ดังนั้นคุณจึงต้องการใช้ RecyclerView กับ GridLayoutManager
ขั้นตอนที่ 1: อัปเดตโมเดลมุมมอง
ตอนนี้โมเดลมุมมองมี _property LiveData ที่มีออบเจ็กต์ MarsProperty หนึ่งรายการ ซึ่งเป็นรายการแรกในรายการการตอบกลับจากบริการเว็บ ในขั้นตอนนี้ คุณจะเปลี่ยน LiveData เพื่อเก็บออบเจ็กต์ MarsProperty ทั้งหมด
- เปิด
overview/OverviewViewModel.kt - เปลี่ยนตัวแปรส่วนตัว
_propertyเป็น_propertiesเปลี่ยนประเภทเป็นรายการของออบเจ็กต์MarsProperty
private val _properties = MutableLiveData<List<MarsProperty>>()- แทนที่
propertyข้อมูลสดภายนอกด้วยpropertiesเพิ่มรายการลงในประเภทLiveDataที่นี่ด้วย
val properties: LiveData<List<MarsProperty>>
get() = _properties- เลื่อนลงไปที่วิธีการ
getMarsRealEstateProperties()ในtry {}บล็อก ให้แทนที่การทดสอบทั้งหมดที่คุณเพิ่มในงานก่อนหน้าด้วยบรรทัดที่แสดงด้านล่าง เนื่องจากตัวแปรlistResultมีรายการออบเจ็กต์MarsPropertyคุณจึงกำหนดตัวแปรนี้ให้กับ_properties.valueได้เลยแทนที่จะทดสอบการตอบกลับที่สำเร็จ
_properties.value = listResultตอนนี้try/catch บล็อกทั้งหมดจะมีลักษณะดังนี้
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}ขั้นตอนที่ 2: อัปเดตเลย์เอาต์และ Fragment
ขั้นตอนถัดไปคือการเปลี่ยนเลย์เอาต์และ Fragment ของแอปให้ใช้ RecyclerView และ GridLayout แทนที่จะใช้ SingleImageView
- เปิด
res/layout/gridview_item.xmlเปลี่ยนการเชื่อมโยงข้อมูลจากOverviewViewModelเป็นMarsPropertyและเปลี่ยนชื่อตัวแปรเป็น"property"
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />- ใน
<ImageView>ให้เปลี่ยนแอตทริบิวต์app:imageUrlเพื่ออ้างอิงถึง URL ของรูปภาพในออบเจ็กต์MarsProperty
app:imageUrl="@{property.imgSrcUrl}"- เปิด
overview/OverviewFragment.ktในonCreateview()ให้ยกเลิกการแสดงความคิดเห็นในบรรทัดที่ขยายFragmentOverviewBindingลบหรือแสดงความคิดเห็นในบรรทัดที่เพิ่มGridViewBindingการเปลี่ยนแปลงเหล่านี้จะยกเลิกการเปลี่ยนแปลงชั่วคราวที่คุณทำในงานล่าสุด
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)- เปิด
res/layout/fragment_overview.xmlลบองค์ประกอบ<TextView>ทั้งหมด - ให้เพิ่มองค์ประกอบ
<RecyclerView>นี้แทน ซึ่งใช้GridLayoutManagerและเลย์เอาต์grid_view_itemสำหรับสินค้า 1 รายการ
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />ขั้นตอนที่ 3: เพิ่มอแดปเตอร์ตารางกริดรูปภาพ
ตอนนี้เลย์เอาต์ fragment_overview มี RecyclerView ในขณะที่เลย์เอาต์ grid_view_item มี ImageView เดียว ในขั้นตอนนี้ คุณจะเชื่อมโยงข้อมูลกับ RecyclerView ผ่านอะแดปเตอร์ RecyclerView
- เปิด
overview/PhotoGridAdapter.kt - สร้างคลาส
PhotoGridAdapterโดยใช้พารามิเตอร์ของตัวสร้างที่แสดงด้านล่าง คลาสPhotoGridAdapterขยายListAdapterซึ่งตัวสร้างต้องมีประเภทรายการลิสต์ ตัวยึดมุมมอง และการติดตั้งใช้งานDiffUtil.ItemCallback
นำเข้าชั้นเรียนandroidx.recyclerview.widget.ListAdapterและcom.example.android.marsrealestate.network.MarsPropertyเมื่อได้รับคำขอ ในขั้นตอนต่อไปนี้ คุณจะใช้ส่วนอื่นๆ ที่ขาดหายไปของตัวสร้างนี้ซึ่งทำให้เกิดข้อผิดพลาด
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}- คลิกที่ใดก็ได้ใน
PhotoGridAdapterคลาส แล้วกดControl+iเพื่อใช้เมธอดListAdapterซึ่งได้แก่onCreateViewHolder()และonBindViewHolder()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}- ที่ส่วนท้ายของคำจำกัดความคลาส
PhotoGridAdapterหลังจากเมธอดที่คุณเพิ่งเพิ่ม ให้เพิ่มคำจำกัดความออบเจ็กต์คู่สำหรับDiffCallbackดังที่แสดงด้านล่าง
นำเข้าandroidx.recyclerview.widget.DiffUtilเมื่อได้รับคำขอ
ออบเจ็กต์DiffCallbackขยายDiffUtil.ItemCallbackด้วยประเภทออบเจ็กต์ที่คุณต้องการเปรียบเทียบ ซึ่งก็คือMarsProperty
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}- กด
Control+iเพื่อใช้เมธอดตัวเปรียบเทียบสำหรับออบเจ็กต์นี้ ซึ่งได้แก่areItemsTheSame()และareContentsTheSame()
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }- สำหรับ
areItemsTheSame()ให้นำ TODO ออก ใช้ตัวดำเนินการความเท่าเทียมกันโดยอ้างอิงของ Kotlin (===) ซึ่งจะแสดงผลtrueหากการอ้างอิงออบเจ็กต์สำหรับoldItemและnewItemเหมือนกัน
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}- สําหรับ
areContentsTheSame()ให้ใช้ตัวดำเนินการความเท่ากันมาตรฐานกับรหัสของoldItemและnewItemเท่านั้น
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}- ยังคงอยู่ในคลาส
PhotoGridAdapterให้เพิ่มคำจำกัดความคลาสภายในสำหรับMarsPropertyViewHolderซึ่งขยายRecyclerView.ViewHolder
นำเข้าandroidx.recyclerview.widget.RecyclerViewและcom.example.android.marsrealestate.databinding.GridViewItemBindingเมื่อระบบขอ
คุณต้องมีตัวแปรGridViewItemBindingสำหรับการเชื่อมโยงMarsPropertyกับเลย์เอาต์ ดังนั้นให้ส่งตัวแปรไปยังMarsPropertyViewHolderเนื่องจากคลาสพื้นฐานViewHolderต้องมี View ในตัวสร้าง คุณจึงส่ง View รูทของการเชื่อมโยงไปยังคลาส
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}- ใน
MarsPropertyViewHolderให้สร้างเมธอดbind()ที่รับออบเจ็กต์MarsPropertyเป็นอาร์กิวเมนต์และตั้งค่าbinding.propertyเป็นออบเจ็กต์นั้น เรียกใช้executePendingBindings()หลังจากตั้งค่าพร็อพเพอร์ตี้ ซึ่งจะทําให้อัปเดตทํางานทันที
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}- ใน
onCreateViewHolder()ให้นำ TODO ออกแล้วเพิ่มบรรทัดที่แสดงด้านล่าง นำเข้าandroid.view.LayoutInflaterเมื่อได้รับคำขอ
เมธอดonCreateViewHolder()ต้องส่งกลับMarsPropertyViewHolderใหม่ที่สร้างขึ้นโดยการขยายGridViewItemBindingและใช้LayoutInflaterจากบริบทViewGroupขององค์ประกอบระดับบนสุด
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))- ใน
onBindViewHolder()ให้นำ TODO ออกและเพิ่มบรรทัดที่แสดงด้านล่าง ในที่นี้ คุณเรียกใช้getItem()เพื่อรับออบเจ็กต์MarsPropertyที่เชื่อมโยงกับตำแหน่งRecyclerViewปัจจุบัน จากนั้นส่งพร็อพเพอร์ตี้นั้นไปยังเมธอดbind()ในMarsPropertyViewHolder
val marsProperty = getItem(position)
holder.bind(marsProperty)ขั้นตอนที่ 4: ติดตั้ง Binding Adapter และเชื่อมต่อชิ้นส่วน
สุดท้าย ให้ใช้ BindingAdapter เพื่อเริ่มต้น PhotoGridAdapter ด้วยรายการออบเจ็กต์ MarsProperty การใช้ BindingAdapter เพื่อตั้งค่าข้อมูล RecyclerView จะทําให้การเชื่อมโยงข้อมูลสังเกต LiveData โดยอัตโนมัติสําหรับรายการออบเจ็กต์ MarsProperty จากนั้นระบบจะเรียกใช้ Binding Adapter โดยอัตโนมัติเมื่อMarsPropertyมีการเปลี่ยนแปลงรายการ
- เปิด
BindingAdapters.kt - ที่ท้ายไฟล์ ให้เพิ่มเมธอด
bindRecyclerView()ที่รับRecyclerViewและรายการออบเจ็กต์MarsPropertyเป็นอาร์กิวเมนต์ ใส่คำอธิบายประกอบในวิธีการนั้นด้วย@BindingAdapter
นำเข้าandroidx.recyclerview.widget.RecyclerViewและcom.example.android.marsrealestate.network.MarsPropertyเมื่อได้รับคำขอ
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}- ภายใน
bindRecyclerView()ฟังก์ชัน ให้แคสต์recyclerView.adapterเป็นPhotoGridAdapterแล้วเรียกใช้adapter.submitList()ด้วยข้อมูล ซึ่งจะแจ้งให้RecyclerViewทราบเมื่อมีรายการใหม่พร้อมใช้งาน
นำเข้า com.example.android.marsrealestate.overview.PhotoGridAdapter เมื่อได้รับคำขอ
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)- เปิด
res/layout/fragment_overview.xmlเพิ่มแอตทริบิวต์app:listDataลงในองค์ประกอบRecyclerViewแล้วตั้งค่าเป็นviewmodel.propertiesโดยใช้การเชื่อมโยงข้อมูล
app:listData="@{viewModel.properties}"- เปิด
overview/OverviewFragment.ktในonCreateView()ก่อนที่จะเรียกใช้setHasOptionsMenu()ให้เริ่มต้นใช้งานอแดปเตอร์RecyclerViewในbinding.photosGridเป็นออบเจ็กต์PhotoGridAdapterใหม่
binding.photosGrid.adapter = PhotoGridAdapter()- เรียกใช้แอป คุณควรเห็นตารางกริดของรูปภาพ
MarsPropertyขณะที่คุณเลื่อนเพื่อดูรูปภาพใหม่ แอปจะแสดงไอคอนความคืบหน้าในการโหลดก่อนที่จะแสดงรูปภาพ หากเปิดโหมดเครื่องบิน รูปภาพที่ยังโหลดไม่เสร็จจะปรากฏเป็นไอคอนรูปภาพเสีย

แอป MarsRealEstate จะแสดงไอคอนรูปภาพเสียเมื่อดึงข้อมูลรูปภาพไม่ได้ แต่เมื่อไม่มีเครือข่าย แอปจะแสดงหน้าจอว่างเปล่า

ซึ่งไม่ก่อให้เกิดประสบการณ์ที่ดีแก่ผู้ใช้ ในงานนี้ คุณจะเพิ่มการจัดการข้อผิดพลาดขั้นพื้นฐานเพื่อให้ผู้ใช้ทราบถึงสิ่งที่เกิดขึ้นได้ดียิ่งขึ้น หากไม่มีอินเทอร์เน็ต แอปจะแสดงไอคอนข้อผิดพลาดในการเชื่อมต่อ ขณะที่แอปดึงข้อมูลMarsProperty รายการ แอปจะแสดงภาพเคลื่อนไหวการโหลด
ขั้นตอนที่ 1: เพิ่มสถานะไปยัง ViewModel
ในการเริ่มต้น ให้สร้าง LiveData ใน ViewModel เพื่อแสดงสถานะของคำขอเว็บ โดยมี 3 สถานะที่ต้องพิจารณา ได้แก่ กำลังโหลด สำเร็จ และไม่สำเร็จ สถานะการโหลดจะเกิดขึ้นขณะที่คุณรอข้อมูลในการเรียกไปยัง await()
- เปิด
overview/OverviewViewModel.ktที่ด้านบนของไฟล์ (หลังการนำเข้า ก่อนคำจำกัดความของคลาส) ให้เพิ่มenumเพื่อแสดงสถานะทั้งหมดที่ใช้ได้
enum class MarsApiStatus { LOADING, ERROR, DONE }- เปลี่ยนชื่อทั้ง
_responseคำจำกัดความของข้อมูลแบบเรียลไทม์ทั้งภายในและภายนอกตลอดทั้งคลาสOverviewViewModelเป็น_statusเนื่องจากคุณได้เพิ่มการรองรับ_propertiesLiveDataใน Codelab นี้ไปก่อนหน้านี้แล้ว การตอบสนองของบริการเว็บที่สมบูรณ์จึงไม่ได้ใช้ คุณต้องมีLiveDataที่นี่เพื่อติดตามสถานะปัจจุบัน จึงเปลี่ยนชื่อตัวแปรที่มีอยู่ได้เลย
นอกจากนี้ ให้เปลี่ยนประเภทจาก String เป็น MarsApiStatus.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status- เลื่อนลงไปที่
getMarsRealEstateProperties()วิธีการ_responseและอัปเดตเป็น_statusที่นี่ด้วย เปลี่ยนสตริง"Success"เป็นสถานะMarsApiStatus.DONEและสตริง"Failure"เป็นMarsApiStatus.ERROR - เพิ่ม
MarsApiStatus.LOADINGสถานะที่ด้านบนของtry {}บล็อกก่อนการเรียกใช้await()นี่คือสถานะเริ่มต้นขณะที่โครูทีนทํางานและคุณกําลังรอข้อมูล ตอนนี้บล็อกtry/catch {}ที่สมบูรณ์จะมีลักษณะดังนี้
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}- หลังจากสถานะข้อผิดพลาดในบล็อก
catch {}ให้ตั้งค่า_propertiesLiveDataเป็นรายการที่ว่างเปล่า การดำเนินการนี้จะล้างRecyclerView
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}ขั้นตอนที่ 2: เพิ่ม Binding Adapter สำหรับ ImageView ของสถานะ
ตอนนี้คุณมีสถานะใน ViewModel แต่เป็นเพียงชุดสถานะ คุณจะทำให้แอปปรากฏในแอปเองได้อย่างไร ในขั้นตอนนี้ คุณจะใช้ ImageView ที่เชื่อมต่อกับการเชื่อมโยงข้อมูลเพื่อแสดงไอคอนสำหรับสถานะการโหลดและข้อผิดพลาด เมื่อแอปอยู่ในสถานะกำลังโหลดหรือสถานะข้อผิดพลาด ImageView ควรแสดง เมื่อแอปโหลดเสร็จแล้ว ImageView จะต้องมองไม่เห็น
- เปิด
BindingAdapters.ktเพิ่ม Binding Adapter ใหม่ชื่อbindStatus()ซึ่งรับค่าImageViewและMarsApiStatusเป็นอาร์กิวเมนต์ นำเข้าcom.example.android.marsrealestate.overview.MarsApiStatusเมื่อได้รับคำขอ
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}- เพิ่ม
when {}ภายในเมธอดbindStatus()เพื่อสลับระหว่างสถานะต่างๆ
when (status) {
}- ภายใน
when {}ให้เพิ่มเคสสำหรับสถานะการโหลด (MarsApiStatus.LOADING) สำหรับสถานะนี้ ให้ตั้งค่าImageViewเป็น "มองเห็นได้" และกำหนดภาพเคลื่อนไหวการโหลดให้ ซึ่งเป็นภาพเคลื่อนไหวที่วาดได้แบบเดียวกับที่คุณใช้สำหรับ Glide ในงานก่อนหน้า นำเข้าandroid.view.Viewเมื่อได้รับคำขอ
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}- เพิ่มเคสสำหรับสถานะข้อผิดพลาด ซึ่งคือ
MarsApiStatus.ERRORเช่นเดียวกับที่คุณทำสำหรับสถานะLOADINGให้ตั้งค่าสถานะImageViewเป็น "มองเห็นได้" และใช้ภาพวาดข้อผิดพลาดในการเชื่อมต่อซ้ำ
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}- เพิ่มเคสสำหรับสถานะเสร็จสิ้น ซึ่งก็คือ
MarsApiStatus.DONEคุณได้รับการตอบกลับที่สำเร็จแล้ว ดังนั้นให้ปิดระดับการแชร์ของสถานะImageViewเพื่อซ่อน
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}ขั้นตอนที่ 3: เพิ่ม ImageView สถานะลงในเลย์เอาต์
- เปิด
res/layout/fragment_overview.xmlใต้องค์ประกอบRecyclerViewภายในConstraintLayoutให้เพิ่มImageViewที่แสดงด้านล่าง
ImageViewนี้มีข้อจำกัดเดียวกันกับRecyclerViewอย่างไรก็ตาม ความกว้างและความสูงจะใช้wrap_contentเพื่อจัดกึ่งกลางรูปภาพแทนที่จะยืดรูปภาพให้เต็มมุมมอง นอกจากนี้ ให้สังเกตแอตทริบิวต์app:marsApiStatusซึ่งมีการเรียกดูBindingAdapterเมื่อพร็อพเพอร์ตี้สถานะในโมเดลมุมมองเปลี่ยนแปลง
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />- เปิดโหมดบนเครื่องบินในโปรแกรมจำลองหรืออุปกรณ์เพื่อจำลองการเชื่อมต่อเครือข่ายที่ขาดหายไป คอมไพล์และเรียกใช้แอป แล้วสังเกตว่ารูปภาพข้อผิดพลาดปรากฏขึ้น

- แตะปุ่มย้อนกลับเพื่อปิดแอป แล้วปิดโหมดบนเครื่องบิน ใช้หน้าจอแอปที่ใช้ล่าสุดเพื่อกลับไปที่แอป คุณอาจเห็นวงกลมโหลดสั้นๆ เมื่อแอปค้นหาบริการบนเว็บก่อนที่รูปภาพจะเริ่มโหลด ทั้งนี้ขึ้นอยู่กับความเร็วของการเชื่อมต่อเครือข่าย
โปรเจ็กต์ Android Studio: MarsRealEstateGrid
- หากต้องการลดความซับซ้อนของกระบวนการจัดการรูปภาพ ให้ใช้ไลบรารี Glide เพื่อดาวน์โหลด บัฟเฟอร์ ถอดรหัส และแคชรูปภาพในแอป
- Glide ต้องมี 2 สิ่งในการโหลดรูปภาพจากอินเทอร์เน็ต ได้แก่ URL ของรูปภาพ และ
ImageViewออบเจ็กต์ที่จะใส่รูปภาพ หากต้องการระบุตัวเลือกเหล่านี้ ให้ใช้วิธีload()และinto()กับ Glide - Binding Adapters คือเมธอดส่วนขยายที่อยู่ระหว่าง View กับข้อมูลที่ผูกไว้ของ View นั้น Binding Adapter จะให้ลักษณะการทำงานที่กำหนดเองเมื่อข้อมูลเปลี่ยนแปลง เช่น เรียกใช้ Glide เพื่อโหลดรูปภาพจาก URL ลงใน
ImageView - Binding Adapter เป็นเมธอดส่วนขยายที่มีคำอธิบายประกอบด้วยคำอธิบายประกอบ
@BindingAdapter - หากต้องการเพิ่มตัวเลือกในคำขอ Glide ให้ใช้วิธี
apply()เช่น ใช้apply()กับplaceholder()เพื่อระบุ Drawable ที่โหลด และใช้apply()กับerror()เพื่อระบุ Drawable ข้อผิดพลาด - หากต้องการสร้างตารางกริดของรูปภาพ ให้ใช้
RecyclerViewที่มีGridLayoutManager - หากต้องการอัปเดตรายการพร็อพเพอร์ตี้เมื่อมีการเปลี่ยนแปลง ให้ใช้ Binding Adapter ระหว่าง
RecyclerViewกับเลย์เอาต์
หลักสูตร Udacity:
เอกสารประกอบสำหรับนักพัฒนาแอป Android
อื่นๆ:
ส่วนนี้แสดงรายการการบ้านที่เป็นไปได้สำหรับนักเรียน/นักศึกษาที่กำลังทำ Codelab นี้เป็นส่วนหนึ่งของหลักสูตรที่สอนโดยผู้สอน ผู้สอนมีหน้าที่ดำเนินการต่อไปนี้
- มอบหมายการบ้านหากจำเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานที่ได้รับมอบหมาย
- ให้คะแนนงานการบ้าน
ผู้สอนสามารถใช้คำแนะนำเหล่านี้ได้มากน้อยตามที่ต้องการ และควรมีอิสระในการมอบหมายการบ้านอื่นๆ ที่เห็นว่าเหมาะสม
หากคุณกำลังทำ Codelab นี้ด้วยตนเอง โปรดใช้แบบฝึกหัดเหล่านี้เพื่อทดสอบความรู้ของคุณ
ตอบคำถามต่อไปนี้
คำถามที่ 1
คุณใช้วิธีการใดของ Glide เพื่อระบุ ImageView ที่จะมีรูปภาพที่โหลด
▢ into()
▢ with()
▢ imageview()
▢ apply()
คำถามที่ 2
คุณจะระบุรูปภาพตัวยึดตำแหน่งที่จะแสดงเมื่อ Glide กำลังโหลดได้อย่างไร
▢ ใช้เมธอด into() กับ Drawable
▢ ใช้ RequestOptions() และเรียกใช้เมธอด placeholder() ด้วย Drawable
▢ กำหนดพร็อพเพอร์ตี้ Glide.placeholder ให้กับ Drawable
▢ ใช้ RequestOptions() และเรียกใช้เมธอด loadingImage() ด้วย Drawable
คำถามที่ 3
คุณจะระบุว่าเมธอดเป็น BindingAdapter ได้อย่างไร
▢ เรียกใช้เมธอด setBindingAdapter() ใน LiveData
▢ ใส่เมธอดลงในไฟล์ Kotlin ชื่อ BindingAdapters.kt
▢ ใช้แอตทริบิวต์ android:adapter ในเลย์เอาต์ XML
▢ ใส่คำอธิบายประกอบในเมธอดด้วย @BindingAdapter
เริ่มบทเรียนถัดไป:
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab หลักพื้นฐานของ Android Kotlin