Jetpack Composeで画像をズームする(5) ライブラリの紹介

この記事をシェア

これまで4回にわたって、Jetpack Composeで画像をズームする方法を説明してきましたが、ソースコードの量もそれなりに多くなりました。(その1その2その3その4

そこで、少ないコードで画像ズームを実現できるように、これまでに紹介した処理をさらにブラッシュアップして、「Zoomable」というライブラリとして公開しています。今回はそのライブラリの使い方の紹介です。

Zoomable

🔎APIリファレンス

Zoomableは、Jetpack Composeで画像などのコンテンツのズームを簡単に実現するための小さなライブラリです。ピンチジェスチャーと、ダブルタップとドラッグを組み合わせた1本指ジェスチャーの2つに対応しています。

ピンチジェスチャー
1本指ジェスチャー

一番簡単な実装は、以下のようにModifier.zoomable()を1行追加するだけです。

Image(
    painter = painterResource(id = R.drawable.penguin),
    contentDescription = null,
    modifier = Modifier.zoomable(rememberZoomState()),
)

Zoomableは、Imageはもちろん、Textなどいろいろなコンポーザブルコンポーネントに使えます。CoilのAsyncImageなど、非同期で画像を読み込むコンポーネントでも使えます。

また、AccompanistのHorizontalPagerVerticalPagerや、Compose 1.4で追加されたAndroidxのHorizontalPagerVerticalPagerと組み合わせて使うこともできます。

準備

ZoomableはMaven Centralからダウンロードできます。

settings.gradleにmavenCentral()が書かれていることを確認します。(Android Studioを使って通常の手順でプロジェクトを作成した場合は、初めから含まれていると思います。)

pluginManagement {
    repositories {
        mavenCentral()
    }
}

アプリのbuild.gradleに依存関係を追加します。

dependencies {
    implementation "net.engawapg.lib:zoomable:$version"
}

最新バージョンは下記です。

Maven Central

使い方

Zoomableは、コンポーネントをズーム可能にするModifier.zoomable()関数と、ズーム状態を管理するZoomStateを提供しています。ZoomStaterememberZoomable()関数で取得します。

もっともシンプルな実装だと、下記のように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()で画像のサイズを設定することができます。CoilAsyncImageの場合は、onSuccessAsyncImagePainter.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()enableOneFingerZoomfalseを指定してください。

Image(
    painter = painterResource(id = R.drawable.penguin),
    contentDescription = null,
    modifier = Modifier.zoomable(
        zoomState = rememberZoomState(),
        // enableOneFingerZoom = false,
    )
)
この記事をシェア