2023/06/15開催、NIKKEI Tech Talk #8登壇資料です #nikkei_tech_talk タイトルは「Jetpack Composeにおける画像ライブラリ ~Coilに🤏だけ詳しくなる~」です https://nikkei.connpass.com/event/284090/
2023/6/15日本経済新聞社 Androidチーム小倉 悠里Jetpack Composeにおける画像ライブラリ~Coilに🤏だけ詳しくなる~NIKKEI TECH TALK #8
View Slide
ハッシュタグ #nikkei_tech_talk自己紹介名前:小倉 悠里(おぐら ゆり)経歴:・2020年7月 ◁⚪◻ボタンとの初めての出会い・2023年2月 日本経済新聞社に中途入社好きな食べ物:枝豆2小倉なのであんこの写真@姫路の御座候工場
ハッシュタグ #nikkei_tech_talk日経電子版アプリ ver.6.23.0 ~電子版タブに画像バナーが登場🎉本バナーから特集ページを閲覧したり、クロスワードパズルに挑戦したりできるように😉電子版タブのRecyclerViewにJetpack Composeを一部導入した流れで、本バナーもComposeで実装その際の検討内容などを共有していきます!何の話?3
ハッシュタグ #nikkei_tech_talkJetpack Compose における画像ライブラリは2択インターネットから画像を読み込みたい4ライブラリ Coil Glidegithub https://github.com/coil-kt/coil https://github.com/bumptech/glide最新 io.coil-kt:coil-compose:2.4.0com.github.bumptech.glide:compose:1.0.0-alpha.3特徴 高速、軽量、使いやすい、モダン滑らかなスクロールと速度を最重視して開発されている豆知識💡Coroutine Image Loader の頭文字から命名開発を行っていたBumpをGoogleが買収済み(READMEの免責事項には、”This is not an official Google product.” と記載...)
ハッシュタグ #nikkei_tech_talk日経電子版アプリではCoilを選択● GlideはJetpack Compose用にまだStable版が出ていない● Coilが提供するKotlin-firstなAPIは、Kotlinで記述するComposeと相性が良い● Now in Android でもCoilを使用● 日経電子版のWearアプリにて導入済み(右画像)Coil vs Glide5
ハッシュタグ #nikkei_tech_talkCoil使用するAPIは3種類● AsyncImage● SubcomposeAsyncImage● AsyncImagePainter○ 低レベルAPI○ AsyncImageもSubcomposeAsyncImageも内部的にはAsyncImagePainterを使用している6
ハッシュタグ #nikkei_tech_talkCoil 〜AsyncImage〜● 非同期的にリクエストと描画を行うAPI● foundationのImageコンポーザブルとほぼ似た使い勝手+ placeholderやエラー画像などをPainterで指定したり、読み込み状態の変更を受け取ったりできる7AsyncImage(model = ImageRequest.Builder(LocalContext.current).data("https://example.com/image.jpg").crossfade(true).build(),placeholder = painterResource(R.drawable.placeholder),contentDescription = stringResource(R.string.description),contentScale = ContentScale.Crop,modifier = Modifier.clip(CircleShape))
ハッシュタグ #nikkei_tech_talk【AsyncImageとの違いをざっくり理解】● AsyncImage:placeholder / error / fallbackをPainterでセット● SubcomposeAsyncImage:placeholder / error / fallbackをComposable関数でセット(Slot API)Coil 〜SubcomposeAsyncImage〜8SubcomposeAsyncImage(model = "https://example.com/image.jpg",contentDescription = stringResource(R.string.description)) {val state = painter.stateif (state is AsyncImagePainter.State.Loading || state is AsyncImagePainter.State.Error) {CircularProgressIndicator()} else {SubcomposeAsyncImageContent()}}
ハッシュタグ #nikkei_tech_talk【もう少し詳しく理解】● SubcomposeLayoutを利用したAsyncImageCoil 〜SubcomposeAsyncImage〜9● サイズの計算はLayoutステップの中で行われている● SubcomposeLayoutでは、Compositionステップを親のLayoutステップまで遅らせることで、constraints(制約≒サイズ)の取得を行えるhttps://developer.android.com/jetpack/compose/layouts/basics
ハッシュタグ #nikkei_tech_talkCoil〜SubcomposeAsyncImage〜10@Composablefun SubcomposeAsyncImage(model: Any?,contentDescription: String?,imageLoader: ImageLoader,modifier: Modifier = Modifier,transform: (State) -> State = DefaultTransform,onState: ((State) -> Unit)? = null,alignment: Alignment = Alignment.Center,contentScale: ContentScale = ContentScale.Fit,alpha: Float = DefaultAlpha,colorFilter: ColorFilter? = null,filterQuality: FilterQuality = DefaultFilterQuality,content: @Composable SubcomposeAsyncImageScope.() -> Unit,) {...if (sizeResolver !is ConstraintsSizeResolver) {Box(...) { ... } // ※2} else {BoxWithConstraints(modifier = modifier,contentAlignment = alignment,propagateMinConstraints = true) { ... }}}SubcomposeAsyncImageの中身を覗いてみると...👀BoxWithConstraints(※1)が使われている!※1:SubcomposeLayoutを利用したComposable※2:厳密にはSubcomposeAsyncImageでも、ImageRequestにサイズを明示的に指定した場合には、constraints取得が不要なためSubcompositionを使用しない
ハッシュタグ #nikkei_tech_talkAsyncImage vs SubcomposeAsyncImageQ. Subcomposition、強そうなのでとりあえず使っておけば良い?A. No 🥺Subcompositionは拡張性は高いが、パフォーマンスコスト大● painterで事足りるケースではAsyncImageを使用● Success, Loading, Errorといった描画状態ごとにComposableを用いた表現を行いたい場合は、SubcomposeAsyncImageを使用💯ただし、パフォーマンスに注意(リスト中に多用しないなど)11
ハッシュタグ #nikkei_tech_talk【仕様】- バナー画像:アスペクト比を保ったまま (paddingありで)画面横幅いっぱいに表示したい アスペクト比は、レスポンスで画像URLとともに取得される- placeholder:バナー画像のサイズで外枠をつけたい日経での導入談12
ハッシュタグ #nikkei_tech_talkplaceholder表示にあたり、端末の横幅(親レイアウトのサイズ)が必要 🤔日経での導入談13SubcomposeAsyncImage(modifier = modifier.padding( … ).aspectRatio((featuredContentsImage.width / featuredContentsImage.height).toFloat()),model = ImageRequest.Builder(LocalContext.current).data(featuredContentsImage.url).build(),contentDescription = featuredContentsBanner.alt,) {if (painter.state is AsyncImagePainter.State.Success) {SubcomposeAsyncImageContent()} else {Image(modifier = Modifier.border(BorderStroke(1.dp, colorResource(id = R.color.placeholder_border))),painter = painterResource(id = placeHolder),contentDescription = featuredContentsBanner.alt,)}}
ハッシュタグ #nikkei_tech_talkまとめ● 画像読み込みライブラリ for Compose:Coil vs Glide● CoilのAsyncImage vs SubcomposeAsyncImage● SubcomposeAsyncImageの使い所の見極めが大事👀● NIKKEIでは、新しいUIにはComposeを積極採用するなどモダンな技術を積極的に取り入れている14
15ありがとうございました