2023年10月30日 星期一

【Android Studio】 下載與顯示網際網路上的圖片——使用 Coil 與 Binding Adapter (資料繫結器) #ImageView


為何要使用 Coil

想要顯示圖片,需先將圖片下載、儲存、解碼等等,為獲取最佳效能,建議一次擷取多張圖片並解碼。工作繁雜,所以建議使用 Coil 幫忙處理。

Coil 使用的預備工作

Coil 需要 :
  • 圖片網址
  • 顯示圖片的 ImageView 物件

添加依附元件

implementation "io.coil-kt:coil:1.1.1"


為何要使用  Binding Adapter (資料繫結器)

舉例 :
使用 android:text="Sample Text" , Android 系統會自動尋找 test 的 settter 方法 setText(String: text) (尋找傳入參數是 String 的是因為傳給屬性的是 String)。若找不到則會發生錯誤。
若在 ImageView 元件使用自訂的屬性 app:imageUrl="@{product.imageUrl}" ,系統會在 ImageView 中尋找 setImageUrl(String) 方法。為了讓系統可以找到,所以要使用繫結轉接器 (註解方法) ,將 app:imageUrl 屬性設為 ImageView。

例子 : 用BindingAdapter 搭配 Coil 的 load 根據 URI 載入圖片

//寫做頂層函式
@BindingAdapter("imageUrl") //傳入屬性名稱,當view有此屬應時會自動執行此 binding adapter
fun bindImage(imageView: ImageView, imageUrl: String?){ //第一個參數是方法的接收者(view),第二個是傳給屬性的參數
    imageUrl?.let {
        val imgUri = imageUrl.toUri() //將字串轉成 Uri 物件
            .buildUpon().scheme("https") //使用 HTTPS 配置
            .build()

        //使用 Coil 的 load(){} 將 imgUri 物件的圖片載入 imgView。
        imageView.load(imgUri)
    }
}

<ImageView
        ...
        app:imageUrl="@{viewModel.photos.imgSrcUrl}"
        ... />

設置 loading 與載入錯誤的圖片

使用 Coil 不僅可以顯示預留位置圖片,還能載入圖片,如果因圖片缺失或損毀等原因造成載入失敗,也可以載入錯誤圖片,進而改善使用者體驗。
使用方式 : 將前面程式碼中的 load 添加載入中與載入錯誤的圖片
@BindingAdapter("imageUrl") //傳入屬性名稱,當view有此屬應時會自動執行此 binding adapter
fun bindImage(imageView: ImageView, imageUrl: String?){ //第一個參數是方法的接收者(view),第二個是傳給屬性的參數
    imageUrl?.let {
        val imgUri = imageUrl.toUri() //將字串轉成 Uri 物件
            .buildUpon().scheme("https") //使用 HTTPS 配置
            .build()

        //使用 Coil 的 load(){} 將 imgUri 物件的圖片載入 imgView。
        //imageView.load(imgUri)
        imageView.load(imgUri) {
            placeholder(R.drawable.loading_animation) //loading 時顯示 loading 圖片
            error(R.drawable.ic_broken_image) //圖片載入失敗則顯示錯誤圖片
        }
    }
}
但添加後只會顯示每張圖片的 loading 或正常顯示圖片,並不會在沒有網路的時候顯示錯誤。

若要顯示錯誤,可在添加一個專門顯示錯誤的 ImageView,正常顯示時才隱藏此 view。如下方例子(此例除了顯示錯誤,也會在 loading 時顯示 loading 圖片):
<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}" />

@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
               status: MarsApiStatus?) {
    when (status) {
        MarsApiStatus.LOADING -> {
            statusImageView.visibility = View.VISIBLE
            statusImageView.setImageResource(R.drawable.loading_animation)
        }
        MarsApiStatus.DONE -> {
            statusImageView.visibility = View.GONE
        }
        else -> {
            statusImageView.visibility = View.VISIBLE
            statusImageView.setImageResource(R.drawable.ic_connection_error)
        }
    }
}

enum class MarsApiStatus { LOADING, ERROR, DONE }
...
private fun getMarsPhotos() {
    _status.value = MarsApiStatus.LOADING
    viewModelScope.launch {
        try{
            _photos.value = MarsApi.retrofitService.getPhotos()
            _status.value = MarsApiStatus.DONE
        } catch (e: Exception) {
            _status.value = MarsApiStatus.ERROR
            _photos.value = listOf()
        }
    }
}

資料來源

0 comments:

張貼留言