これまで4回にわたって、Jetpack Composeで画像をズームする方法を説明してきましたが、ソースコードの量もそれなりに多くなりました。(その1・その2・その3・その4)
そこで、少ないコードで画像ズームを実現できるように、これまでに紹介した処理をさらにブラッシュアップして、「Zoomable」というライブラリとして公開しています。今回はそのライブラリの使い方の紹介です。
Zoomable
Zoomableは、Jetpack Composeで画像などのコンテンツのズームを簡単に実現するための小さなライブラリです。ピンチジェスチャーと、ダブルタップとドラッグを組み合わせた1本指ジェスチャーの2つに対応しています。
一番簡単な実装は、以下のようにModifier.zoomable()
を1行追加するだけです。
Image(
painter = painterResource(id = R.drawable.penguin),
contentDescription = null,
modifier = Modifier.zoomable(rememberZoomState()),
)
Zoomableは、Image
はもちろん、Text
などいろいろなコンポーザブルコンポーネントに使えます。CoilのAsyncImage
など、非同期で画像を読み込むコンポーネントでも使えます。
また、AccompanistのHorizontalPager
とVerticalPager
や、Compose 1.4で追加されたAndroidxのHorizontalPager
とVerticalPager
と組み合わせて使うこともできます。
準備
ZoomableはMaven Centralからダウンロードできます。
settings.gradleにmavenCentral()
が書かれていることを確認します。(Android Studioを使って通常の手順でプロジェクトを作成した場合は、初めから含まれていると思います。)
pluginManagement {
repositories {
mavenCentral()
}
}
アプリのbuild.gradleに依存関係を追加します。
dependencies {
implementation "net.engawapg.lib:zoomable:$version"
}
最新バージョンは下記です。
使い方
Zoomableは、コンポーネントをズーム可能にするModifier.zoomable()
関数と、ズーム状態を管理するZoomState
を提供しています。ZoomState
はrememberZoomable()
関数で取得します。
もっともシンプルな実装だと、下記のようにModifier.zoomable()
を1行追加するだけでOKです。
Image(
painter = painterResource(id = R.drawable.penguin),
contentDescription = null,
modifier = Modifier.zoomable(rememberZoomState())
)
画面にフィットさせる
先ほどの例だと、ズームしてドラッグした時に画像が画面外まで移動してしまいます。これを防ぐには、contentSize
に画像のサイズを指定します。
val painter = painterResource(id = R.drawable.penguin)
val zoomState = rememberZoomState(contentSize = painter.intrinsicSize)
Image(
painter = painter,
contentDescription = null,
modifier = Modifier.zoomable(zoomState)
)
これで、画像の端がImageコンポーネントの境界より内側に移動しないようになります。
非同期読み込みの画像で使う
Coilなどの非同期画像読み込みライブラリと組み合わせて使う場合は、画像読み込み完了時にsetContentSize()
で画像のサイズを設定することができます。Coil
のAsyncImage
の場合は、onSuccess
のAsyncImagePainter.State.Success
から画像サイズを取得できます。
val zoomState = rememberZoomState()
AsyncImage(
model = "https://github.com/usuiat.png",
contentDescription = null,
onSuccess = { state ->
zoomState.setContentSize(state.painter.intrinsicSize)
},
modifier = Modifier.zoomable(zoomState)
)
Pagerレイアウトと組み合わせて使う
AccompanistのHorizontalPagerやVerticalPagerと組み合わせて使うこともできます。Compose 1.4で追加された本家JetpackのHorizontalPagerとVerticalPagerでも使えます。ここでは、いずれスタンダードになるであろう本家JetpackのHorizontalPagerと組み合わせて使う方法を紹介します。
val resources = listOf(R.drawable.penguin, R.drawable.bird)
val pagerState = rememberPagerState()
HorizontalPager(
pageCount = resources.size,
state = pagerState,
) { page ->
val painter = painterResource(id = resources[page])
val zoomState = rememberZoomState(contentSize = painter.intrinsicSize)
Image(
painter = painter,
contentDescription = null,
modifier = Modifier
.fillMaxSize()
.zoomable(zoomState)
)
val isVisible = page == pagerState.settledPage
LaunchedEffect(isVisible) {
if (!isVisible) {
zoomState.reset()
}
}
}
Modifier.zoomable()
を指定する部分は、Image
単独で使う場合と変わりません。Zoomableは、画像がズームされていない場合や、ズームされた画像が端までスクロールしている場合に、Pagerにスクロールイベントを伝えます。これによって、ズームされた画像のスクロールと、Pagerのページ送りを両立させています。
サンプルコードの後半は、ページが画面外に移動した時にズーム状態をリセットする処理を記述しています。PagerState.settledPage
を使ってページが画面内に見えているかどうかを判定し、画面外に移動した時にZoomState.reset()
を呼び出しています。settledPage
はAccompanistのPagerには存在しなかったプロパティですが、「PagerのcurrentPage, settledPage, targetPageの挙動」で紹介していますので、詳しく知りたい方はご覧ください。
AccompanistのPagerを使ったサンプルは、GitHubのZoomableのサンプルにありますので、そちらをご覧ください。
1本指ジェスチャーについて
Zoomableは、2本の指を使ったピンチジェスチャーのほかに、ダブルタップに続けて縦方向にドラッグして拡大・縮小する1本指ジェスチャーにも対応しています。1本指ジェスチャーはデフォルトで有効になっていますが、無効にしたい場合はzoomable()
のenableOneFingerZoom
にfalse
を指定してください。
Image(
painter = painterResource(id = R.drawable.penguin),
contentDescription = null,
modifier = Modifier.zoomable(
zoomState = rememberZoomState(),
// enableOneFingerZoom = false,
)
)