Android Kotlin Fundamentals 08.2: การโหลดและการแสดงภาพจากอินเทอร์เน็ต

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&#39

ระบบจะเติมเวอร์ชันของแอปที่คุณสร้างใน Codelab นี้ในหน้าภาพรวม ซึ่งจะแสดงตารางกริดของรูปภาพ รูปภาพเป็นส่วนหนึ่งของข้อมูลพร็อพเพอร์ตี้ที่แอปได้รับจากบริการเว็บอสังหาริมทรัพย์ของดาวอังคาร แอปจะใช้ไลบรารี Glide เพื่อโหลดและแสดงรูปภาพ และใช้ RecyclerView เพื่อสร้างเลย์เอาต์แบบตารางกริดสําหรับรูปภาพ แอปจะจัดการข้อผิดพลาดเกี่ยวกับเครือข่ายอย่างราบรื่นด้วย

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

โชคดีที่คุณสามารถใช้ไลบรารีที่พัฒนาโดยชุมชนชื่อว่า Glide เพื่อดาวน์โหลด บัฟเฟอร์ ถอดรหัส และแคชรูปภาพของคุณ การร่อนจะทําให้คุณทํางานได้น้อยลงกว่าการทําสิ่งต่างๆ นี้ตั้งแต่ต้น

การพิมพ์แบบเลื่อนผ่านจําเป็นต้องมี 2 อย่างดังนี้

  • URL ของรูปภาพที่คุณต้องการโหลดและแสดง
  • ออบเจ็กต์ ImageView ที่จะแสดงรูปภาพนั้น

ในงานนี้ คุณจะได้ดูวิธีใช้ Glide เพื่อแสดงรูปภาพเดียวจากบริการเว็บอสังหาริมทรัพย์ คุณแสดงรูปภาพที่เป็นตัวแทนของพร็อพเพอร์ตี้ Mars แรกในรายการพร็อพเพอร์ตี้ที่บริการเว็บแสดงผล ภาพหน้าจอก่อนและหลังมีดังนี้

ขั้นตอนที่ 1: เพิ่มการอ้างอิงแบบเลื่อนผ่าน

  1. เปิดแอป MarsRealEstate จาก Codelab ล่าสุด (คุณสามารถดาวน์โหลด MarsRealEstateNetwork ได้ที่นี่หากไม่มีแอป)
  2. เรียกใช้แอปเพื่อดูการทํางาน (โดยจะแสดงข้อความข้อความของที่พักที่มีสมมติฐานบนดาวอังคาร)
  3. เปิด build.gradle (โมดูล: แอป)
  4. ในส่วน dependencies ให้เพิ่มบรรทัดนี้สําหรับไลบรารี Glide
implementation "com.github.bumptech.glide:glide:$version_glide"


โปรดสังเกตว่ามีการกําหนดหมายเลขเวอร์ชันแยกต่างหากในไฟล์ Gradle โปรเจ็กต์แล้ว

  1. คลิกซิงค์เลยเพื่อสร้างโปรเจ็กต์ใหม่ด้วยทรัพยากร Dependency ใหม่

ขั้นตอนที่ 2: อัปเดตโมเดลมุมมอง

จากนั้นให้อัปเดตชั้นเรียน OverviewViewModel เพื่อรวมข้อมูลแบบสดสําหรับพร็อพเพอร์ตี้ Mars รายการเดียว

  1. เปิด overview/OverviewViewModel.kt ใต้ LiveData ของ _response ให้เพิ่มข้อมูลสดทั้งภายใน (เปลี่ยนแปลงได้) และข้อมูลภายนอก (ที่เปลี่ยนแปลงไม่ได้) สําหรับออบเจ็กต์ MarsProperty รายการเดียว

    นําเข้าชั้นเรียน MarsProperty (com.example.android.marsrealestate.network.MarsProperty) เมื่อมีการขอ
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. ในเมธอด 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}"
 }
  1. เปิดไฟล์ res/layout/fragment_overview.xml ในองค์ประกอบ <TextView> ให้เปลี่ยน android:text เพื่อเชื่อมโยงกับคอมโพเนนต์ imgSrcUrl ของ property LiveData
android:text="@{viewModel.property.imgSrcUrl}"
  1. เรียกใช้แอป TextView จะแสดงเฉพาะ URL ของรูปภาพในพร็อพเพอร์ตี้ดาวอังคารแรกเท่านั้น เพียงเท่านี้ก็ตั้งค่าโมเดลข้อมูลพร็อพเพอร์ตี้และข้อมูลสดสําหรับ URL ดังกล่าวได้เลย

ขั้นตอนที่ 3: สร้างอะแดปเตอร์สําหรับผูกและโทรหา Glide

ตอนนี้คุณมี URL ของรูปภาพที่จะแสดงแล้ว และก็ถึงเวลาเริ่มใช้ Glide ในการโหลดรูปภาพนั้น ในขั้นตอนนี้ คุณจะใช้อะแดปเตอร์การเชื่อมโยงเพื่อนํา URL จากแอตทริบิวต์ XML ที่เชื่อมโยงกับ ImageView และใช้ Glide เพื่อโหลดรูปภาพ อะแดปเตอร์ที่เชื่อมโยงเป็นวิธีส่วนขยายที่อยู่ระหว่างข้อมูลพร็อพเพอร์ตี้และการเชื่อมโยงเพื่อให้ลักษณะการทํางานที่กําหนดเองเมื่อข้อมูลมีการเปลี่ยนแปลง ในกรณีนี้ ลักษณะการทํางานที่กําหนดเองคือเรียกใช้ Glide เพื่อโหลดรูปภาพจาก URL ลงใน ImageView

  1. เปิด BindingAdapters.kt ไฟล์นี้จะเก็บอะแดปเตอร์การเชื่อมโยงที่คุณใช้ตลอดทั้งแอป
  2. สร้างฟังก์ชัน bindImage() ที่ใช้ ImageView และ String เป็นพารามิเตอร์ เพิ่มคําอธิบายประกอบให้กับฟังก์ชันด้วย @BindingAdapter คําอธิบายประกอบ @BindingAdapter จะบอกการเชื่อมโยงข้อมูลที่คุณต้องการให้อะแดปเตอร์การเชื่อมโยงนี้ดําเนินการเมื่อรายการ XML มีแอตทริบิวต์ imageUrl

    นําเข้า androidx.databinding.BindingAdapter และ android.widget.ImageView ตามคําขอ
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. ภายในฟังก์ชัน bindImage() ให้เพิ่มบล็อก let {} สําหรับอาร์กิวเมนต์ imgUrl ดังนี้
imgUrl?.let { 
}
  1. ภายในบล็อก 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()
  1. ภายใน let {} ให้เรียก Glide.with() เพื่อโหลดรูปภาพจากออบเจ็กต์ Uri ลงใน ImageView นําเข้า com.bumptech.glide.Glide เมื่อระบบขอ
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

ขั้นตอนที่ 4: อัปเดตเลย์เอาต์และส่วนย่อย

แม้ว่า Glide โหลดรูปภาพแล้ว แต่ยังไม่มีอะไรให้แสดงเลย ขั้นตอนถัดไปคือการอัปเดตเลย์เอาต์และส่วนย่อยที่มี ImageView เพื่อแสดงรูปภาพ

  1. เปิด res/layout/gridview_item.xml นี่คือไฟล์ทรัพยากรเลย์เอาต์ที่จะใช้กับแต่ละรายการใน RecyclerView ในภายหลังของ Codelab ซึ่งใช้ได้ชั่วคราวเพื่อแสดงภาพเดียว
  2. ที่ด้านบนขององค์ประกอบ <ImageView> ให้เพิ่มองค์ประกอบ <data> สําหรับการเชื่อมโยงข้อมูล และเชื่อมโยงกับคลาส OverviewViewModel ดังนี้
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. เพิ่มแอตทริบิวต์ app:imageUrl ลงในองค์ประกอบ ImageView เพื่อใช้อะแดปเตอร์การเชื่อมโยงการโหลดรูปภาพใหม่
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. เปิด overview/OverviewFragment.kt ในเมธอด onCreateView() ให้สังเกตบรรทัดที่สูงเกินจริงของคลาส FragmentOverviewBinding และกําหนดตัวแปรดังกล่าวให้กับตัวแปรการเชื่อมโยง นี่เป็นเพียงชั่วคราวเท่านั้น คุณจะกลับไปดูภายหลังได้
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. เพิ่มบรรทัดเพื่อเพิ่มคลาส GridViewItemBinding ให้สูงเกินจริง นําเข้า com.example.android.marsrealestate. databinding.GridViewItemBinding ตามคําขอ
val binding = GridViewItemBinding.inflate(inflater)
  1. เรียกใช้แอป คุณจะเห็นรูปภาพรูปภาพจาก MarsProperty แรกในรายการผลการค้นหา

ขั้นตอนที่ 5: เพิ่มรูปภาพการโหลดและข้อผิดพลาดแบบง่ายๆ

Glide ช่วยให้ผู้ใช้ได้รับประสบการณ์การใช้งานที่ดีขึ้นได้ด้วยการแสดงรูปภาพตัวยึดตําแหน่งขณะที่โหลดรูปภาพแสดงข้อผิดพลาด และหากโหลดไม่สําเร็จ เช่น รูปภาพหายไปหรือเสียหาย ในขั้นตอนนี้ คุณจะต้องเพิ่มฟังก์ชันการทํางานนั้นลงในอะแดปเตอร์การเชื่อมโยงและในเลย์เอาต์

  1. เปิด res/drawable/ic_broken_image.xml แล้วคลิกแท็บแสดงตัวอย่างทางด้านขวา สําหรับภาพแสดงข้อผิดพลาด คุณกําลังใช้ไอคอนรูปภาพเสียซึ่งอยู่ในไลบรารีไอคอนในตัว เวกเตอร์ที่วาดได้นี้ใช้แอตทริบิวต์ android:tint เพื่อเติมสีให้ไอคอนเป็นสีเทา

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

  1. กลับไปที่ไฟล์ 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)
    }
}
  1. เรียกใช้แอป ทั้งนี้ขึ้นอยู่กับความเร็วของการเชื่อมต่อเครือข่าย คุณอาจเห็นว่ารูปภาพกําลังโหลดอยู่ชั่วครู่เมื่อเลื่อนผ่านและแสดงรูปภาพที่พัก แต่คุณจะยังไม่เห็นไอคอนรูปภาพเสีย แม้ว่าคุณจะปิดเครือข่ายก็ตาม ให้คุณแก้ไขในส่วนสุดท้ายของ Codelab

ตอนนี้แอปโหลดข้อมูลที่พักจากอินเทอร์เน็ตแล้ว โดยใช้ข้อมูลจากรายการ MarsProperty แรก คุณได้สร้างพร็อพเพอร์ตี้ LiveData ในรูปแบบข้อมูลพร็อพเพอร์ตี้ และคุณได้ใช้ URL รูปภาพจากข้อมูลพร็อพเพอร์ตี้ดังกล่าวเพื่อป้อนข้อมูลImageView แต่เป้าหมายของคุณคือการให้แอปแสดงตารางกริดของรูปภาพ ดังนั้นจึงควรใช้ RecyclerView ที่มี GridLayoutManager

ขั้นตอนที่ 1: อัปเดตโมเดลมุมมอง

ขณะนี้โมเดลข้อมูลพร็อพเพอร์ตี้มี _property LiveData ที่มีออบเจ็กต์ MarsProperty จํานวน 1 รายการ ซึ่งเป็นออบเจ็กต์แรกในรายการคําตอบจากบริการเว็บ ในขั้นตอนนี้ คุณสามารถเปลี่ยน LiveData เพื่อคงรายการออบเจ็กต์ MarsProperty ทั้งหมดไว้

  1. เปิด overview/OverviewViewModel.kt
  2. เปลี่ยนตัวแปร _property ส่วนตัวเป็น _properties เปลี่ยนประเภทเป็นรายการของออบเจ็กต์ MarsProperty
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. แทนที่ข้อมูลสดของ property ภายนอกด้วย properties เพิ่มรายการประเภท LiveData ที่นี่ด้วย
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. เลื่อนลงไปที่เมธอด 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: อัปเดตเลย์เอาต์และส่วนย่อย

ขั้นตอนต่อไปคือการเปลี่ยนเลย์เอาต์และส่วนย่อยของแอปเพื่อใช้มุมมองรีไซเคิลและเลย์เอาต์ตารางกริดแทนมุมมองรูปภาพเดียว

  1. เปิด res/layout/gridview_item.xml เปลี่ยนการเชื่อมโยงข้อมูลจาก OverviewViewModel เป็น MarsProperty และเปลี่ยนชื่อตัวแปรเป็น "property"
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. ใน <ImageView> ให้เปลี่ยนแอตทริบิวต์ app:imageUrl เพื่ออ้างถึง URL ของรูปภาพในออบเจ็กต์ MarsProperty
app:imageUrl="@{property.imgSrcUrl}"
  1. เปิด overview/OverviewFragment.kt ใน onCreateview() ให้เลิกแสดงความเห็นในบรรทัดที่เติมสูงกว่า FragmentOverviewBinding ลบหรือแสดงความคิดเห็นในบรรทัดที่สูงเกินจริง GridViewBinding การเปลี่ยนแปลงเหล่านี้จะยกเลิกการเปลี่ยนแปลงชั่วคราวที่คุณทําในงานล่าสุด
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. เปิด res/layout/fragment_overview.xml ลบองค์ประกอบ <TextView> ทั้งหมด
  2. ให้เพิ่มองค์ประกอบ <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

  1. เปิด overview/PhotoGridAdapter.kt
  2. สร้างคลาส PhotoGridAdapter ด้วยพารามิเตอร์เครื่องมือสร้างที่แสดงด้านล่าง คลาส PhotoGridAdapter จะขยายเวลา ListAdapter ซึ่งเครื่องมือสร้างต้องการประเภทรายการ ผู้ถือมุมมอง และการติดตั้งใช้งาน DiffUtil.ItemCallback

    นําเข้าชั้นเรียน androidx.recyclerview.widget.ListAdapter และ com.example.android.marsrealestate.network.MarsProperty ตามคําขอ ในขั้นตอนต่อไปนี้ คุณจะได้ใช้ส่วนที่หายไปอื่นๆ ของตัวสร้างนี้ที่ทําให้เกิดข้อผิดพลาด
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. คลิกที่ใดก็ได้ในชั้นเรียน 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") 
}
  1. ที่ส่วนท้ายของคําจํากัดความชั้นเรียน PhotoGridAdapter หลังวิธีการที่เพิ่งเพิ่ม ให้เพิ่มคําจํากัดความออบเจ็กต์ที่แสดงร่วมกันสําหรับ DiffCallback ดังที่แสดงด้านล่าง

    นําเข้า androidx.recyclerview.widget.DiffUtil ตามคําขอ

    ออบเจ็กต์ DiffCallback จะขยาย DiffUtil.ItemCallback ตามประเภทของออบเจ็กต์ที่คุณต้องการเปรียบเทียบ - MarsProperty
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. กด 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") }
  1. สําหรับวิธีการ areItemsTheSame() ให้นําสิ่งที่ต้องทําออก ใช้โอเปอเรเตอร์ความเท่าเทียมกันในการอ้างอิงของ Kotlin&#39 (===) ซึ่งจะแสดงผล true หากออบเจ็กต์อ้างอิงสําหรับ oldItem และ newItem เหมือนกัน
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. สําหรับ areContentsTheSame() ให้ใช้โอเปอเรเตอร์ความเท่าเทียมมาตรฐานโดยใช้รหัสของ oldItem และ newItem
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. ยังอยู่ในคลาส 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) {

}
  1. ใน MarsPropertyViewHolder ให้สร้างเมธอด bind() ที่ใช้ออบเจ็กต์ MarsProperty เป็นอาร์กิวเมนต์ และตั้งค่า binding.property เป็นออบเจ็กต์นั้น เรียก executePendingBindings() หลังการตั้งค่าพร็อพเพอร์ตี้ ซึ่งจะทําให้การอัปเดตดําเนินการทันที
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. ใน onCreateViewHolder() ให้นําสิ่งที่ต้องทําออกและเพิ่มบรรทัดที่แสดงด้านล่าง นําเข้า android.view.LayoutInflater ตามคําขอ

    เมธอด onCreateViewHolder() ต้องแสดงผล MarsPropertyViewHolder ใหม่ที่สร้างขึ้นด้วยการขยาย GridViewItemBinding และใช้ LayoutInflater จากบริบท ViewGroup ระดับบนสุด
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. ในวิธี onBindViewHolder() ให้นําสิ่งที่ต้องทําออกและเพิ่มบรรทัดที่แสดงด้านล่าง คุณจะเรียก getItem() เพื่อรับออบเจ็กต์ MarsProperty ที่เชื่อมโยงกับตําแหน่ง RecyclerView ปัจจุบัน แล้วส่งต่อพร็อพเพอร์ตี้นั้นไปยังเมธอด bind() ใน MarsPropertyViewHolder
val marsProperty = getItem(position)
holder.bind(marsProperty)

ขั้นตอนที่ 4: เพิ่มอะแดปเตอร์การเชื่อมโยงและเชื่อมต่อชิ้นส่วน

และสุดท้าย ให้ใช้ BindingAdapter เพื่อเริ่มต้นใช้งาน PhotoGridAdapter ด้วยรายการออบเจ็กต์ MarsProperty การใช้ BindingAdapter เพื่อตั้งค่าข้อมูล RecyclerView จะทําให้การเชื่อมโยงข้อมูลสังเกตเห็น LiveData โดยอัตโนมัติสําหรับรายการออบเจ็กต์ MarsProperty จากนั้นระบบจะเรียกอะแดปเตอร์การเชื่อมโยงโดยอัตโนมัติเมื่อรายการ MarsProperty มีการเปลี่ยนแปลง

  1. เปิด BindingAdapters.kt
  2. ในตอนท้ายของไฟล์ ให้เพิ่มเมธอด bindRecyclerView() ที่ใช้ RecyclerView และรายการออบเจ็กต์ MarsProperty เป็นอาร์กิวเมนต์ เพิ่มคําอธิบายประกอบด้วย @BindingAdapter

    นําเข้า androidx.recyclerview.widget.RecyclerView และ com.example.android.marsrealestate.network.MarsProperty ตามคําขอ
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. ภายในฟังก์ชัน bindRecyclerView() ให้แคสต์ recyclerView.adapter ไปยัง PhotoGridAdapter และเรียก adapter.submitList() โดยใช้ข้อมูล การดําเนินการนี้จะแจ้งให้ RecyclerView ทราบเมื่อมีรายการใหม่

นําเข้า com.example.android.marsrealestate.overview.PhotoGridAdapter ตามคําขอ

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. เปิด res/layout/fragment_overview.xml เพิ่มแอตทริบิวต์ app:listData ในองค์ประกอบ RecyclerView และตั้งค่าเป็น viewmodel.properties โดยใช้การเชื่อมโยงข้อมูล
app:listData="@{viewModel.properties}"
  1. เปิด overview/OverviewFragment.kt ใน onCreateView() ก่อนการเริ่มต้นกับ setHasOptionsMenu() ให้เริ่มต้นอะแดปเตอร์ RecyclerView ใน binding.photosGrid เป็นออบเจ็กต์ PhotoGridAdapter ใหม่
binding.photosGrid.adapter = PhotoGridAdapter()
  1. เรียกใช้แอป คุณจะเห็นตารางกริดของรูปภาพ MarsProperty ขณะที่คุณเลื่อนดูรูปภาพใหม่ แอปจะแสดงไอคอนความคืบหน้าของการโหลดก่อนที่จะแสดงรูปภาพนั้น หากเปิดใช้โหมดบนเครื่องบิน รูปภาพที่ยังไม่ได้โหลดจะปรากฏเป็นไอคอนรูปภาพเสีย

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

ซึ่งนี่ไม่ใช่ประสบการณ์อันยอดเยี่ยมของผู้ใช้ ในงานนี้ คุณจะได้เพิ่มการจัดการข้อผิดพลาดขั้นพื้นฐานเพื่อช่วยให้ผู้ใช้เข้าใจได้ดีขึ้นว่าเกิดอะไรขึ้น หากอินเทอร์เน็ตไม่พร้อมใช้งาน แอปจะแสดงไอคอนข้อผิดพลาดในการเชื่อมต่อ เมื่อแอปดึงข้อมูลรายการ MarsProperty แอปจะแสดงภาพเคลื่อนไหวในการโหลด

ขั้นตอนที่ 1: เพิ่มสถานะให้กับโมเดลข้อมูลพร็อพเพอร์ตี้

หากต้องการเริ่มต้น คุณจะต้องสร้าง LiveData ในโมเดลข้อมูลพร็อพเพอร์ตี้เพื่อแสดงสถานะของคําขอเว็บ มี 3 สถานะที่ต้องพิจารณา ได้แก่ การโหลด ความสําเร็จ และความล้มเหลว สถานะการโหลดจะเกิดขึ้นขณะที่คุณรอข้อมูลในการโทรหา await()

  1. เปิด overview/OverviewViewModel.kt ที่ด้านบนของไฟล์ (หลังการนําเข้า ก่อนคําจํากัดความของชั้นเรียน) ให้เพิ่ม enum เพื่อแสดงสถานะที่ใช้ได้ทั้งหมดดังนี้
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. เปลี่ยนชื่อคําจํากัดความของข้อมูลสดทั้ง _response และภายในและภายนอกทั่วทั้งคลาส OverviewViewModel เป็น _status เนื่องจากคุณได้เพิ่มการรองรับ _properties LiveData ก่อนหน้านี้ใน Codelab ดังกล่าว ระบบจึงยังไม่ใช้การตอบกลับบริการบนเว็บที่สมบูรณ์ คุณต้องมี LiveData เพื่อติดตามสถานะปัจจุบัน คุณเพียงแค่เปลี่ยนชื่อตัวแปรที่มีอยู่ได้

และเปลี่ยนประเภทจาก String เป็น MarsApiStatus.

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. เลื่อนลงไปที่เมธอด getMarsRealEstateProperties() และอัปเดต _response เป็น _status ที่นี่ด้วย เปลี่ยนสตริง "Success" เป็นสถานะ MarsApiStatus.DONE และสตริง "Failure" เป็น MarsApiStatus.ERROR
  2. เพิ่มสถานะ 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
}
  1. หลังจากสถานะข้อผิดพลาดในบล็อก catch {} แล้ว ให้กําหนด _properties LiveData เป็นรายการที่ว่างเปล่า การดําเนินการนี้จะล้าง RecyclerView
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

ขั้นตอนที่ 2: เพิ่มอะแดปเตอร์การเชื่อมโยงสําหรับสถานะ ImageView

ตอนนี้คุณมีสถานะในรูปแบบข้อมูลพร็อพเพอร์ตี้ แต่มีเพียงชุดรัฐเท่านั้น คุณจะแสดงแอปของตัวเองได้อย่างไร ในขั้นตอนนี้ คุณจะใช้ ImageView ที่เชื่อมต่อกับการเชื่อมโยงข้อมูลเพื่อแสดงไอคอนสําหรับสถานะการโหลดและข้อผิดพลาด เมื่อแอปอยู่ในสถานะการโหลดหรือสถานะข้อผิดพลาด คุณควรเห็น ImageView เมื่อแอปโหลดเสร็จแล้ว ระบบจะไม่แสดง ImageView

  1. เปิด BindingAdapters.kt เพิ่มอะแดปเตอร์การเชื่อมโยงใหม่ที่ชื่อ bindStatus() ซึ่งใช้ค่า ImageView และค่า MarsApiStatus เป็นอาร์กิวเมนต์ นําเข้า com.example.android.marsrealestate.overview.MarsApiStatus ตามคําขอ
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. เพิ่ม when {} ภายในเมธอด bindStatus() เพื่อสลับระหว่างสถานะต่างๆ
when (status) {

}
  1. ใน when {} ให้เพิ่มเคสสําหรับสถานะการโหลด (MarsApiStatus.LOADING) สําหรับสถานะนี้ ให้ตั้งค่า ImageView ที่มองเห็นได้ และกําหนดภาพเคลื่อนไหวการโหลด นี่คือภาพเคลื่อนไหวแบบเดียวกันกับที่วาดให้กับ Glide ในงานก่อนหน้า นําเข้า android.view.View ตามคําขอ
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. เพิ่มเคสสําหรับสถานะข้อผิดพลาด ซึ่งเป็น MarsApiStatus.ERROR เช่นเดียวกับสถานะ LOADING ของคุณ ให้ตั้งสถานะ ImageView เป็นแสดงและใช้ข้อผิดพลาดในการเชื่อมต่อซ้ํา
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. เพิ่มเคสสําหรับสถานะที่เสร็จแล้ว ซึ่งเป็น MarsApiStatus.DONE ในส่วนนี้ คุณจะได้การตอบสนองที่สําเร็จ ดังนั้นให้ปิดระดับการเข้าถึงของสถานะ ImageView เพื่อซ่อนสถานะนั้น
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

ขั้นตอนที่ 3: เพิ่มสถานะ ImageView ไปยังเลย์เอาต์

  1. เปิด 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}" />
  1. เปิดโหมดบนเครื่องบินในโปรแกรมจําลองหรืออุปกรณ์เพื่อจําลองการเชื่อมต่อเครือข่ายที่ขาดหายไป คอมไพล์และเรียกใช้แอป แล้วสังเกตว่ารูปภาพแสดงข้อผิดพลาดปรากฏขึ้นดังนี้

  1. แตะปุ่ม "ย้อนกลับ" เพื่อปิดแอป และปิดโหมดบนเครื่องบิน ใช้หน้าจอล่าสุดเพื่อคืนแอป คุณอาจเห็นไอคอนหมุนที่สั้นมากเมื่อแอปค้นหาบริการบนเว็บก่อนที่รูปภาพจะเริ่มโหลด ทั้งนี้ขึ้นอยู่กับความเร็วของการเชื่อมต่อเครือข่าย

โปรเจ็กต์ 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

เริ่มบทเรียนถัดไป: 8.3 การกรองและดูรายละเอียดด้วยข้อมูลอินเทอร์เน็ต

สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page ของ Codelab ของ Android Kotlin Fundamentals