Jetpack ComposeはGoogle IO 2019で発表され、AndroidでのUI開発方法を変えていますす。 Coilを開発している間にAndroidイメージAPIで多くの作業をしており、Coilが画像をどのように処理し、イメージローダーの概念がどのように適用するのか興味がありました。
現在のAndroid UIフレームワークでは、ImageViewが画像を表示する主な方法です。 画像はDrawableとして表され、多くの場合(常にではありませんが)Bitmaps
をラップします。 Bitmaps
は、メモリに保存された生の非圧縮ピクセルデータです。
Jetpack Composeには、ビューがないためImageViewがありません。 代わりに、ビューは、ビュー階層に追加する構成可能なUIを定義するComposablesに置き換えられます。 同じく、Drawableはありません。 代わりに、NativeImageをラップする最小限のインターフェースであるImageに置き換えられます。 現時点では、NativeImageはBitmapのtypealiasとして定義されています。 興味深いことに、NativeImageの先頭にはコメントアウトされたexpect宣言があり、これはKotlin Multiplatformのキーワードです🤔。 現在、アニメーション画像は支援されていませんが、AnimatedImageは後で追加されると思っています。 APIの大まかな類似点は次のとおりです。
-
View
->Composable
-
ImageView
->DrawImage
-
Drawable
->Image
-
Bitmap
->NativeImage
Creating Images画像を作成する
現時点では、imageResourceからリソースを同期的にロードするImage作成関数は1つしかありません。 このAPIは一時的なものとして記載されており、将来的には非同期API(おそらくCoroutinesを使います)に置き換えられます。 ただし、URL、ファイル、URI、または別のデータソースから画像を作成する場合は、今のところ自分で作成する必要があります。 幸いなことに、重い負荷を画像読み込みライブラリにオフロードできます。
これを達成する最も簡単な方法は、effect(効果)を書くことです。 Effect(効果)は、戻り値を持つコードの位置的にメモされたブロックです。 これらはComposableから呼び出すことができ、出力値が更新されるとコンポジション(基本的にはビュー階層(view hierarchy))を再構築します。 Coilが支援する画像効果の実装は次のとおりです。
/** * A simple [image] effect, which loads [data] with the default options. */ @CheckResult(suggest = "+") fun image(data: Any) = effectOf<Image?> { // Positionally memoize the request creation so // it will only be recreated if data changes. val request = +memo(data) { Coil.loader().newGetBuilder().data(data).build() } +image(request) } /** * A configurable [image] effect, which accepts a [request] value object. */ @CheckResult(suggest = "+") fun image(request: GetRequest) = effectOf<Image?> { val image = +state<Image?> { null } // Execute the following code whenever the request changes. +onCommit(request) { val job = CoroutineScope(Dispatchers.Main.immediate).launch { // Start loading the image and await the result. val drawable = Coil.loader().get(request) image.value = AndroidImage(drawable.toBitmap()) } // Cancel the request if the input to onCommit changes or // the Composition is removed from the composition tree. onDispose { job.cancel() } } // Emit a null Image to start with. image.value }
これらの機能は何をしますか?
- image(data:Any)は、デフォルトのオプションを利用してイメージリクエストを起動するimage(request:GetRequest)のシンプルバージョンです。
- イメージがComposableの一部として呼び出されると、nullイメージを発行し、指定されたデータの非同期ロードを始まります。
- 成功すると画像の状態を更新し、Jetpack Composeは更新された画像でComposableを再レンダリングします。
- リクエストが処理中で、
Composable
がコンポジションから削除された場合、リクエストは自動的にキャンセルされます。
では、JetNewsサンプルアプリを見てみましょう。 現時点では、すべてのリソースをMainActivity.onCreateに積極的にロードしています。 imageを利用して、すべてのロードを、遅延のない非ブロッキングの非同期呼び出しに置き換えることができます。 さらに、ハードコーディングされたすべてのリソースをURLに置き換えることができます! 変換後のPostImageは次のようになります。
@Composable fun PostImage(post: Post) { val image = +image(post.imageThumbUrl) ?: +imageResource(R.drawable.placeholder_1_1) Container(width = 40.dp, height = 40.dp) { DrawImage(image) } }
すごい! 終わりましたよね? これは理論的には機能しますが(現在、CoroutinesはJetpack Composeコンパイラーでは機能しません)、画像関数には多くの機能が欠けており、最適化できます:
- 自動でサイズ変更する:現時点では、親コンテナーのサイズを解決する方法がないため、Coilは元のサイズ(ディスプレイのサイズで制限されます)で画像を読み込みます。 これを解決する1つの方法は、独自のComposableを作成して画像をレンダリングすることです。 ただし、これは、APIコンシューマーに対してより制限的なカスタムImageViewを記述することに似ています。
-
Bitmap pooling: Coil.getは、いつ
Coil
をプールに戻すことが安全であるかをコイルがわからないため、返されたドロアブルのBitmapのリサイクルを防ぎます。 View.onViewDetachedFromWindowが発生したとき、Lifecycle.onDestroyが発生したとき、またはそのImageViewで別の画像読み込み要求がはじまたときに、Bitmapをリサイクルしても安全であることがわかっているImageView Coilに画像を読み込みます。 Jetpack Composeは、コンポーネントをクリーンアップするライフサイクルコールバックとしてCommitScope.onDisposeを提供します。Coil(および他のイメージローダー)は、それを有効なリクエスト破棄コールバックとして扱う必要があります。
これらの問題のほとんどは、Composeと、ViewやDrawableなどの従来のUIフレームワーククラスとの明確な分離に起因しています。 ただし、これらのクラスはプラットフォームに関連付けられ、インヘリタンスに依存し、多くの内部状態を保持するため、これらのクラスから分離することは絶対に正しい考えです(ビューは3万行くらいあります!)。 コンポーザブルと画像はプラットフォームに結び付けられておらず、インヘリタンスよりもComposable
を優先し、内部状態を最小限に抑えます。
全体として、Jetpack Composeの進歩に非常に興奮しており、CoilがComposeで簡単に動作することを保証します(準備ができている場合)。 また、遅延読み込みの変更を加えたJetNewsアプリを見たい場合は、ここで見つけることができます。
原文タイトル:Exploring Images in Jetpack Compose
原文作者:Colin White
原文リンク先:https://tech.instacart.com/exploring-images-in-jetpack-compose-c8ba87089c92