Codelab นี้เป็นส่วนหนึ่งของหลักสูตรพื้นฐานเกี่ยวกับ Kotlin ใน Android คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้ หากทํางานผ่าน Codelab ตามลําดับ Codelab ของหลักสูตรทั้งหมดจะแสดงอยู่ในหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals
บทนำ
ใน Codelab ก่อนหน้านี้ คุณได้เรียนรู้วิธีรับข้อมูลจากบริการเว็บและแยกวิเคราะห์การตอบกลับเป็นออบเจ็กต์ข้อมูล ใน Codelab นี้ คุณจะต้องสร้างความรู้นั้นในการโหลดและแสดงรูปภาพจาก URL ของเว็บ นอกจากนี้ ยังดูวิธีสร้าง RecyclerView
และใช้เพื่อแสดงตารางกริดของรูปภาพในหน้าภาพรวมได้ด้วย
สิ่งที่ควรทราบอยู่แล้ว
- วิธีสร้างและใช้ส่วนย่อย
- วิธีใช้คอมโพเนนต์สถาปัตยกรรมรวมถึงโมเดลมุมมอง ดูโรงงานโมเดล การเปลี่ยนรูปแบบ และ
LiveData
- วิธีเรียกข้อมูล JSON จากบริการเว็บ REST และแยกวิเคราะห์ข้อมูลดังกล่าวลงในออบเจ็กต์ Kotlin โดยใช้ไลบรารี Retrofit และ Moshi
- วิธีสร้างเลย์เอาต์ตารางกริดด้วย
RecyclerView
- วิธีการทํางานของ
Adapter
,ViewHolder
และDiffUtil
สิ่งที่จะได้เรียนรู้
- วิธีใช้ไลบรารี Glide เพื่อโหลดและแสดงรูปภาพจาก URL ของเว็บ
- วิธีใช้
RecyclerView
และอะแดปเตอร์ตารางกริดเพื่อแสดงตารางกริดรูปภาพ - วิธีจัดการข้อผิดพลาดที่อาจเกิดขึ้นเมื่อดาวน์โหลดและแสดงรูปภาพ
สิ่งที่คุณจะทํา
- แก้ไขแอป MarsRealEstate เพื่อรับ URL รูปภาพจากข้อมูลพร็อพเพอร์ตี้ดาวอังคาร และใช้ Glide เพื่อโหลดและแสดงรูปภาพนั้น
- เพิ่มภาพเคลื่อนไหวขณะโหลดและไอคอนข้อผิดพลาดลงในแอป
- ใช้
RecyclerView
เพื่อแสดงตารางกริดของรูปภาพดาวอังคาร - เพิ่มการจัดการสถานะและข้อผิดพลาดไปยัง
RecyclerView
ใน Codelab นี้ (และ Codelab ที่เกี่ยวข้อง) คุณใช้งานแอปที่ชื่อว่า MarsRealEstate ที่แสดงพร็อพเพอร์ตี้สําหรับขายบนดาวอังคาร แอปเชื่อมต่อกับเซิร์ฟเวอร์อินเทอร์เน็ตเพื่อดึงข้อมูลและแสดงข้อมูลที่พัก รวมถึงรายละเอียด เช่น ราคาและที่พักพร้อมให้ขายหรือให้เช่า รูปภาพที่เป็นตัวแทนของแต่ละสถานที่คือรูปภาพในชีวิตจริงจากดาวอังคารที่ถ่ายจากหุ่นยนต์สํารวจดาวอังคารของ NASA'
ระบบจะเติมเวอร์ชันของแอปที่คุณสร้างใน Codelab นี้ในหน้าภาพรวม ซึ่งจะแสดงตารางกริดของรูปภาพ รูปภาพเป็นส่วนหนึ่งของข้อมูลพร็อพเพอร์ตี้ที่แอปได้รับจากบริการเว็บอสังหาริมทรัพย์ของดาวอังคาร แอปจะใช้ไลบรารี Glide เพื่อโหลดและแสดงรูปภาพ และใช้ RecyclerView
เพื่อสร้างเลย์เอาต์แบบตารางกริดสําหรับรูปภาพ แอปจะจัดการข้อผิดพลาดเกี่ยวกับเครือข่ายอย่างราบรื่นด้วย
การแสดงรูปภาพจาก URL ของเว็บอาจดูเหมือนตรงไปตรงมา แต่มีวิศวกรรมเล็กน้อยในการทํางานให้ได้ผลดี ต้องดาวน์โหลด บัฟเฟอร์ และถอดรหัสดังกล่าวจากรูปแบบที่บีบอัดเป็นรูปภาพที่ Android ใช้ได้ รูปภาพควรแคชลงในแคชหน่วยความจํา แคชแบบอิงตามพื้นที่เก็บข้อมูล หรือทั้งสองอย่าง ทั้งหมดนี้ต้องทําในชุดข้อความที่มีลําดับความสําคัญต่ํา เพื่อให้ UI ยังคงตอบสนองได้ดี นอกจากนี้ คุณอาจต้องดึงข้อมูลและถอดรหัสรูปภาพมากกว่า 1 รูปพร้อมกันเพื่อประสิทธิภาพที่ดีที่สุดของเครือข่ายและ CPU การเรียนรู้วิธีโหลดรูปภาพจากเครือข่ายอย่างมีประสิทธิภาพอาจเป็นตัว Codelab เองอยู่แล้ว
โชคดีที่คุณสามารถใช้ไลบรารีที่พัฒนาโดยชุมชนชื่อว่า Glide เพื่อดาวน์โหลด บัฟเฟอร์ ถอดรหัส และแคชรูปภาพของคุณ การร่อนจะทําให้คุณทํางานได้น้อยลงกว่าการทําสิ่งต่างๆ นี้ตั้งแต่ต้น
การพิมพ์แบบเลื่อนผ่านจําเป็นต้องมี 2 อย่างดังนี้
- URL ของรูปภาพที่คุณต้องการโหลดและแสดง
- ออบเจ็กต์
ImageView
ที่จะแสดงรูปภาพนั้น
ในงานนี้ คุณจะได้ดูวิธีใช้ Glide เพื่อแสดงรูปภาพเดียวจากบริการเว็บอสังหาริมทรัพย์ คุณแสดงรูปภาพที่เป็นตัวแทนของพร็อพเพอร์ตี้ Mars แรกในรายการพร็อพเพอร์ตี้ที่บริการเว็บแสดงผล ภาพหน้าจอก่อนและหลังมีดังนี้
ขั้นตอนที่ 1: เพิ่มการอ้างอิงแบบเลื่อนผ่าน
- เปิดแอป MarsRealEstate จาก Codelab ล่าสุด (คุณสามารถดาวน์โหลด MarsRealEstateNetwork ได้ที่นี่หากไม่มีแอป)
- เรียกใช้แอปเพื่อดูการทํางาน (โดยจะแสดงข้อความข้อความของที่พักที่มีสมมติฐานบนดาวอังคาร)
- เปิด build.gradle (โมดูล: แอป)
- ในส่วน
dependencies
ให้เพิ่มบรรทัดนี้สําหรับไลบรารี Glide
implementation "com.github.bumptech.glide:glide:$version_glide"
โปรดสังเกตว่ามีการกําหนดหมายเลขเวอร์ชันแยกต่างหากในไฟล์ Gradle โปรเจ็กต์แล้ว
- คลิกซิงค์เลยเพื่อสร้างโปรเจ็กต์ใหม่ด้วยทรัพยากร Dependency ใหม่
ขั้นตอนที่ 2: อัปเดตโมเดลมุมมอง
จากนั้นให้อัปเดตชั้นเรียน OverviewViewModel
เพื่อรวมข้อมูลแบบสดสําหรับพร็อพเพอร์ตี้ Mars รายการเดียว
- เปิด
overview/OverviewViewModel.kt
ใต้LiveData
ของ_response
ให้เพิ่มข้อมูลสดทั้งภายใน (เปลี่ยนแปลงได้) และข้อมูลภายนอก (ที่เปลี่ยนแปลงไม่ได้) สําหรับออบเจ็กต์MarsProperty
รายการเดียว
นําเข้าชั้นเรียนMarsProperty
(com.example.android.marsrealestate.network.MarsProperty
) เมื่อมีการขอ
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
- ในเมธอด
getMarsRealEstateProperties()
ให้หาบรรทัดภายในบล็อกtry/catch {}
ที่กําหนด_response.value
เป็นจํานวนพร็อพเพอร์ตี้ เพิ่มการทดสอบที่แสดงด้านล่าง หากมีออบเจ็กต์MarsProperty
อยู่ การทดสอบนี้จะตั้งค่าของ_property
LiveData
เป็นพร็อพเพอร์ตี้แรกใน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
ของproperty
LiveData
android:text="@{viewModel.property.imgSrcUrl}"
- เรียกใช้แอป
TextView
จะแสดงเฉพาะ URL ของรูปภาพในพร็อพเพอร์ตี้ดาวอังคารแรกเท่านั้น เพียงเท่านี้ก็ตั้งค่าโมเดลข้อมูลพร็อพเพอร์ตี้และข้อมูลสดสําหรับ URL ดังกล่าวได้เลย
ขั้นตอนที่ 3: สร้างอะแดปเตอร์สําหรับผูกและโทรหา Glide
ตอนนี้คุณมี URL ของรูปภาพที่จะแสดงแล้ว และก็ถึงเวลาเริ่มใช้ Glide ในการโหลดรูปภาพนั้น ในขั้นตอนนี้ คุณจะใช้อะแดปเตอร์การเชื่อมโยงเพื่อนํา URL จากแอตทริบิวต์ XML ที่เชื่อมโยงกับ ImageView
และใช้ Glide เพื่อโหลดรูปภาพ อะแดปเตอร์ที่เชื่อมโยงเป็นวิธีส่วนขยายที่อยู่ระหว่างข้อมูลพร็อพเพอร์ตี้และการเชื่อมโยงเพื่อให้ลักษณะการทํางานที่กําหนดเองเมื่อข้อมูลมีการเปลี่ยนแปลง ในกรณีนี้ ลักษณะการทํางานที่กําหนดเองคือเรียกใช้ Glide เพื่อโหลดรูปภาพจาก URL ลงใน ImageView
- เปิด
BindingAdapters.kt
ไฟล์นี้จะเก็บอะแดปเตอร์การเชื่อมโยงที่คุณใช้ตลอดทั้งแอป - สร้างฟังก์ชัน
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 จากไลบรารีหลัก KTX ของ Android จึงดูเหมือนว่าจะเป็นส่วนหนึ่งของคลาส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: อัปเดตเลย์เอาต์และส่วนย่อย
แม้ว่า Glide โหลดรูปภาพแล้ว แต่ยังไม่มีอะไรให้แสดงเลย ขั้นตอนถัดไปคือการอัปเดตเลย์เอาต์และส่วนย่อยที่มี ImageView
เพื่อแสดงรูปภาพ
- เปิด
res/layout/gridview_item.xml
นี่คือไฟล์ทรัพยากรเลย์เอาต์ที่จะใช้กับแต่ละรายการในRecyclerView
ในภายหลังของ Codelab ซึ่งใช้ได้ชั่วคราวเพื่อแสดงภาพเดียว - ที่ด้านบนขององค์ประกอบ
<ImageView>
ให้เพิ่มองค์ประกอบ<data>
สําหรับการเชื่อมโยงข้อมูล และเชื่อมโยงกับคลาสOverviewViewModel
ดังนี้
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- เพิ่มแอตทริบิวต์
app:imageUrl
ลงในองค์ประกอบImageView
เพื่อใช้อะแดปเตอร์การเชื่อมโยงการโหลดรูปภาพใหม่
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 ช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีขึ้นได้ด้วยการแสดงรูปภาพตัวยึดตําแหน่งขณะที่โหลดรูปภาพแสดงข้อผิดพลาด และหากโหลดไม่สําเร็จ เช่น รูปภาพหายไปหรือเสียหาย ในขั้นตอนนี้ คุณจะต้องเพิ่มฟังก์ชันการทํางานนั้นลงในอะแดปเตอร์การเชื่อมโยงและในเลย์เอาต์
- เปิด
res/drawable/ic_broken_image.xml
แล้วคลิกแท็บแสดงตัวอย่างทางด้านขวา สําหรับภาพแสดงข้อผิดพลาด คุณกําลังใช้ไอคอนรูปภาพเสียซึ่งอยู่ในไลบรารีไอคอนในตัว เวกเตอร์ที่วาดได้นี้ใช้แอตทริบิวต์android:tint
เพื่อเติมสีให้ไอคอนเป็นสีเทา
- เปิด
res/drawable/loading_animation.xml
โฆษณาที่วาดได้นี้เป็นภาพเคลื่อนไหวที่กําหนดโดยแท็ก<animate-rotate>
ภาพเคลื่อนไหวจะหมุนรูปภาพloading_img.xml
ที่วาดได้รอบจุดศูนย์กลาง (คุณไม่เห็นภาพเคลื่อนไหวในตัวอย่าง)
- กลับไปที่ไฟล์
BindingAdapters.kt
ในเมธอดbindImage()
ให้อัปเดตการเรียกGlide.with()
ให้เรียกใช้ฟังก์ชันapply()
ระหว่างload()
ถึงinto()
นําเข้าcom.bumptech.glide.request.RequestOptions
เมื่อมีการขอ
โค้ดนี้ตั้งค่ารูปภาพที่โหลดของตัวยึดตําแหน่งให้ใช้ขณะที่โหลด (loading_animation
ที่วาดได้) โค้ดจะตั้งค่ารูปภาพที่จะใช้ในกรณีที่โหลดรูปภาพไม่สําเร็จ (วาดเขียนbroken_image
ได้ด้วย) เมธอด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)
}
}
- เรียกใช้แอป ทั้งนี้ขึ้นอยู่กับความเร็วของการเชื่อมต่อเครือข่าย คุณอาจเห็นว่ารูปภาพกําลังโหลดอยู่ชั่วครู่เมื่อเลื่อนผ่านและแสดงรูปภาพที่พัก แต่คุณจะยังไม่เห็นไอคอนรูปภาพเสีย แม้ว่าคุณจะปิดเครือข่ายก็ตาม ให้คุณแก้ไขในส่วนสุดท้ายของ Codelab
ตอนนี้แอปโหลดข้อมูลที่พักจากอินเทอร์เน็ตแล้ว โดยใช้ข้อมูลจากรายการ MarsProperty
แรก คุณได้สร้างพร็อพเพอร์ตี้ LiveData
ในรูปแบบข้อมูลพร็อพเพอร์ตี้ และคุณได้ใช้ URL รูปภาพจากข้อมูลพร็อพเพอร์ตี้ดังกล่าวเพื่อป้อนข้อมูลImageView
แต่เป้าหมายของคุณคือการให้แอปแสดงตารางกริดของรูปภาพ ดังนั้นจึงควรใช้ RecyclerView
ที่มี GridLayoutManager
ขั้นตอนที่ 1: อัปเดตโมเดลมุมมอง
ขณะนี้โมเดลข้อมูลพร็อพเพอร์ตี้มี _property
LiveData
ที่มีออบเจ็กต์ MarsProperty
จํานวน 1 รายการ ซึ่งเป็นออบเจ็กต์แรกในรายการคําตอบจากบริการเว็บ ในขั้นตอนนี้ คุณสามารถเปลี่ยน 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: อัปเดตเลย์เอาต์และส่วนย่อย
ขั้นตอนต่อไปคือการเปลี่ยนเลย์เอาต์และส่วนย่อยของแอปเพื่อใช้มุมมองรีไซเคิลและเลย์เอาต์ตารางกริดแทนมุมมองรูปภาพเดียว
- เปิด
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
สําหรับสินค้ารายการเดียว
<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()
ให้นําสิ่งที่ต้องทําออก ใช้โอเปอเรเตอร์ความเท่าเทียมกันในการอ้างอิงของ 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
ต้องมีมุมมองในตัวสร้าง คุณจึงส่งมุมมองรากของการเชื่อมโยงไปได้
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()
ให้นําสิ่งที่ต้องทําออกและเพิ่มบรรทัดที่แสดงด้านล่าง นําเข้าandroid.view.LayoutInflater
ตามคําขอ
เมธอดonCreateViewHolder()
ต้องแสดงผลMarsPropertyViewHolder
ใหม่ที่สร้างขึ้นด้วยการขยายGridViewItemBinding
และใช้LayoutInflater
จากบริบทViewGroup
ระดับบนสุด
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
- ในวิธี
onBindViewHolder()
ให้นําสิ่งที่ต้องทําออกและเพิ่มบรรทัดที่แสดงด้านล่าง คุณจะเรียกgetItem()
เพื่อรับออบเจ็กต์MarsProperty
ที่เชื่อมโยงกับตําแหน่งRecyclerView
ปัจจุบัน แล้วส่งต่อพร็อพเพอร์ตี้นั้นไปยังเมธอดbind()
ในMarsPropertyViewHolder
val marsProperty = getItem(position)
holder.bind(marsProperty)
ขั้นตอนที่ 4: เพิ่มอะแดปเตอร์การเชื่อมโยงและเชื่อมต่อชิ้นส่วน
และสุดท้าย ให้ใช้ BindingAdapter
เพื่อเริ่มต้นใช้งาน PhotoGridAdapter
ด้วยรายการออบเจ็กต์ MarsProperty
การใช้ BindingAdapter
เพื่อตั้งค่าข้อมูล RecyclerView
จะทําให้การเชื่อมโยงข้อมูลสังเกตเห็น LiveData
โดยอัตโนมัติสําหรับรายการออบเจ็กต์ MarsProperty
จากนั้นระบบจะเรียกอะแดปเตอร์การเชื่อมโยงโดยอัตโนมัติเมื่อรายการ 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: เพิ่มสถานะให้กับโมเดลข้อมูลพร็อพเพอร์ตี้
หากต้องการเริ่มต้น คุณจะต้องสร้าง LiveData
ในโมเดลข้อมูลพร็อพเพอร์ตี้เพื่อแสดงสถานะของคําขอเว็บ มี 3 สถานะที่ต้องพิจารณา ได้แก่ การโหลด ความสําเร็จ และความล้มเหลว สถานะการโหลดจะเกิดขึ้นขณะที่คุณรอข้อมูลในการโทรหา await()
- เปิด
overview/OverviewViewModel.kt
ที่ด้านบนของไฟล์ (หลังการนําเข้า ก่อนคําจํากัดความของชั้นเรียน) ให้เพิ่มenum
เพื่อแสดงสถานะที่ใช้ได้ทั้งหมดดังนี้
enum class MarsApiStatus { LOADING, ERROR, DONE }
- เปลี่ยนชื่อคําจํากัดความของข้อมูลสดทั้ง
_response
และภายในและภายนอกทั่วทั้งคลาสOverviewViewModel
เป็น_status
เนื่องจากคุณได้เพิ่มการรองรับ_properties
LiveData
ก่อนหน้านี้ใน 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 {}
แล้ว ให้กําหนด_properties
LiveData
เป็นรายการที่ว่างเปล่า การดําเนินการนี้จะล้างRecyclerView
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
ขั้นตอนที่ 2: เพิ่มอะแดปเตอร์การเชื่อมโยงสําหรับสถานะ ImageView
ตอนนี้คุณมีสถานะในรูปแบบข้อมูลพร็อพเพอร์ตี้ แต่มีเพียงชุดรัฐเท่านั้น คุณจะแสดงแอปของตัวเองได้อย่างไร ในขั้นตอนนี้ คุณจะใช้ ImageView
ที่เชื่อมต่อกับการเชื่อมโยงข้อมูลเพื่อแสดงไอคอนสําหรับสถานะการโหลดและข้อผิดพลาด เมื่อแอปอยู่ในสถานะการโหลดหรือสถานะข้อผิดพลาด คุณควรเห็น ImageView
เมื่อแอปโหลดเสร็จแล้ว ระบบจะไม่แสดง ImageView
- เปิด
BindingAdapters.kt
เพิ่มอะแดปเตอร์การเชื่อมโยงใหม่ที่ชื่อ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 ต้องมี 2 อย่างสําหรับโหลดรูปภาพจากอินเทอร์เน็ต ซึ่งก็คือ URL ของรูปภาพและออบเจ็กต์
ImageView
ที่จะแทรกรูปภาพ หากต้องการระบุตัวเลือกเหล่านี้ ให้ใช้เมธอดload()
และinto()
กับ Glide - อะแดปเตอร์สําหรับเชื่อมโยงคือวิธีส่วนขยายที่อยู่ระหว่างข้อมูลพร็อพเพอร์ตี้กับข้อมูลที่เชื่อมโยงของมุมมองนั้น อะแดปเตอร์ที่เชื่อมโยงมีลักษณะการทํางานที่กําหนดเองเมื่อข้อมูลมีการเปลี่ยนแปลง เช่น เรียกใช้ Glide เพื่อโหลดรูปภาพจาก URL ลงใน
ImageView
- อะแดปเตอร์สําหรับเชื่อมโยง คือวิธีใช้งานส่วนขยายที่มีคําอธิบายประกอบ
@BindingAdapter
- หากต้องการเพิ่มตัวเลือกให้กับคําขอแบบเลื่อนผ่าน ให้ใช้เมธอด
apply()
เช่น ใช้apply()
ร่วมกับplaceholder()
เพื่อระบุเนื้อหาที่โหลดได้ และใช้apply()
กับerror()
เพื่อระบุข้อผิดพลาดในการวาดได้ - หากต้องการสร้างตารางกริดรูปภาพ ให้ใช้
RecyclerView
ที่มีGridLayoutManager
- หากต้องการอัปเดตรายการคุณสมบัติเมื่อมีการเปลี่ยนแปลง ให้ใช้อะแดปเตอร์การเชื่อมโยงระหว่าง
RecyclerView
และรูปแบบ
หลักสูตร Udacity:
เอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์ Android
อื่นๆ:
ส่วนนี้จะอธิบายการบ้านและรายงานสําหรับนักเรียนที่ทํางานผ่าน Codelab นี้ซึ่งเป็นส่วนหนึ่งของหลักสูตรที่นําโดยผู้สอน สิ่งที่ผู้สอนต้องทํามีดังนี้
- มอบหมายการบ้านหากจําเป็น
- สื่อสารกับนักเรียนเกี่ยวกับวิธีส่งงานทําการบ้าน
- ตัดเกรดการบ้าน
ผู้สอนจะใช้คําแนะนําเหล่านี้เท่าใดก็ได้หรือตามที่ต้องการก็ได้ และสามารถกําหนดให้การบ้านอื่นๆ ที่ตนคิดว่าเหมาะสมได้
หากคุณใช้ Codelab ด้วยตัวเอง ก็ให้ใช้การบ้านเพื่อทดสอบความรู้ของคุณได้
ตอบคําถามเหล่านี้
คำถามที่ 1
คุณใช้วิธีเลื่อนผ่านใดเพื่อระบุ ImageView
ที่จะมีรูปภาพที่โหลด
▢ into()
▢ with()
▢ imageview()
▢ apply()
คำถามที่ 2
คุณจะระบุรูปภาพตัวยึดตําแหน่งเพื่อแสดงในขณะที่กําลังเลื่อนผ่านได้อย่างไร
▢ ใช้วิธีการ into()
ด้วยภาพวาด
▢ ใช้ RequestOptions()
และเรียกเมธอด placeholder()
ด้วยวาดเขียน
▢ กําหนดพร็อพเพอร์ตี้ Glide.placeholder
เป็นวาดเขียน
▢ ใช้ RequestOptions()
และเรียกเมธอด loadingImage()
ด้วยวาดเขียน
คำถามที่ 3
หากต้องการระบุว่าเมธอดเป็นอะแดปเตอร์การเชื่อมโยง
▢ เรียกเมธอด setBindingAdapter()
บน LiveData
▢ วางเมธอดลงในไฟล์ Kotlin ที่ชื่อ BindingAdapters.kt
▢ ใช้แอตทริบิวต์ android:adapter
ในเลย์เอาต์ XML
▢ เพิ่มคําอธิบายประกอบเมธอดด้วย @BindingAdapter
เริ่มบทเรียนถัดไป:
สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals