Android Jetpack Compose - Implement Zoomable View
Last Updated :
24 Apr, 2025
Jetpack Compose is a new UI toolkit from Google used to create native Android UI. It speeds up and simplifies UI development using less code, Kotlin APIs, and powerful tools.
Prerequisites:
- Familiar with Kotlin and OOP Concepts as well
- Basic understanding of Jetpack Compose
- Android Studio Canary Version
A sample video is given below to get an idea about what we are going to do in this article.
Note: This operation is comparable to the zooming capability in the gallery, where we zoom in and out without returning to the original perspective until we pinch out.
Step-by-Step Implementation
Step 1: Create a New Project in Android Studio
To create a new project in Android Studio using Jetpack Compose please refer to:- How to Create a New Project in Android Studio Canary Version with Jetpack Compose.
Step 2: Let’s first review the build.gradle(module level)
Now go to the module-level build.gradle file & check on dependencies. If any of the dependencies are missing add them from the below snippet. Remember to double-check this file that everything is included. If something is missing just add those blocks from the below snippets.
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.example.zoomableview'
compileSdk 33
defaultConfig {
applicationId "com.example.zoomableview"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.1'
implementation 'androidx.appcompat:appcompat:1.5.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0-RC"
implementation'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0'
}
Step 3: Now let's review the build.gradle(project level)
Now go to the project-level build.gradle file & check on the buildscript code block. If any of the requirements are missing then add them from the below snippet.
buildscript {
ext {
kotlin_version = '1.0.1-2'
compose_version = '1.1.0-rc01'
}
// Requirements
repositories {
google()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" //dependency
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.3.0-alpha01' apply false
id 'com.android.library' version '7.3.0-alpha01' apply false
id 'org.jetbrains.kotlin.android' version '1.6.0' apply false
}
Step 4: Rename MainActivity.kt to ZoomableActivity.kt
We can put the same code to MainActivity.kt as well, but it's a good idea to create or rename the file to reflect its role. Once you change this we also need to modify the AndroidManifest.xml activity tag to the renamed file since the default is MainActivity. You can refer to the below snippet of AndroidManifest.xml.
XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat"
tools:targetApi="31">
<activity
android:name=".ZoomableActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Step 5: Importing necessary modules
It's good practice to import only the necessary modules rather than importing all the modules and using only a few.
Kotlin
// Code for making a view zoomable
// in android jetpack compose
// Please replace the name of package
// with your project name
package com.example.zoomableview
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.painterResource
import com.example.zoomableview.R
Step 6: Implement AppCompatActivity() to class ZoomableActivity
SetContent() block will set the ZoomableComposable component function as the root view of the activity. The ZoomableComposable() will have the logic for the background color transition effect.
Kotlin
class ZoomableActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This sets the @Composable function as
// the root view of the activity.
// This is meant to replace the .xml
// file that we would typically
// set using the setContent(R.id.xml_file) method.
setContent {
ZoomableComposable()
}
}
}
Step 7: Create a composable function for making a view zoomable
Before we go, there are a few things you should be aware of:
- Composable annotations: The @Composable annotation is used to indicate a Composable function. Composable functions can only be invoked from other composable functions. Consider composable functions to be comparable to Lego blocks in that each composable function is constructed up of smaller composable functions.
- Reacting to state changes is the core behavior of Compose. We use the state composable that is used for holding a state value in this composable for representing the current value scale(for zooming in the image) & translation(for panning across the image). Any composable that reads the value of the counter will be recomposed any time the value changes.
- Column: The column is composable and places its children vertically. It is comparable to a LinearLayout in that it is vertically oriented. Additionally, we add a modifier to the column.
- Modifier: Modifiers serve as examples of the decorator pattern and are used to alter the composable to which they are applied. In this case, we use the Modifier to set the Column up to take up the whole available width and height using the Modifier.fillMaxSize() modifier.
- painterResource: There are multiple methods available to load an image resource in Compose. However, it would be advisable to use the painterResource method as it loads an image resource asynchronously.
- Image: Image is a pre-defined composable that lays out and draws a given [ImageBitmap]. We use the graphicsLayer modifier to modify the scale & translation of the image. This is read from the state properties that we created above.
Kotlin
@Composable
fun ZoomableComposable() {
// Reacting to state changes is the core behavior of Compose.
// We use the state composable that is used for holding a
// state value in this composable for representing the current
// value scale(for zooming in the image)
// & translation(for panning across the image).
// Any composable that reads the value of counter will
// be recomposed any time the value changes.
var scale by remember { mutableStateOf(1f) }
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
// In the example below, we make the Column composable zoomable
// by leveraging the Modifier.pointerInput modifier
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown()
do {
val event = awaitPointerEvent()
scale *= event.calculateZoom()
val offset = event.calculatePan()
offsetX += offset.x
offsetY += offset.y
} while (event.changes.any { it.pressed })
}
}
}
) {
// painterResource method loads an image resource asynchronously
val imagepainter = painterResource(id = R.drawable.ic_launcher_background)
// We use the graphicsLayer modifier to modify the scale & translation
// of the image.
// This is read from the state properties that we created above.
Image(
modifier = Modifier.fillMaxSize().graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offsetX,
translationY = offsetY
),
painter = imagepainter,
contentDescription = "androids launcher default launcher background image"
)
}
}
Step 7: If you want to preview your ZoomableComposable then continue else you can skip it
Significance of @preview and composable annotations:
- Instead of needing to download the app to an Android device or emulator, Android Studio allows you to preview your composable functions within the IDE itself. This is an excellent feature since it allows you to preview every one of your components—or composable functions—right inside the IDE
- The composable function cannot accept any parameters, which is the fundamental constraint. You may just include your component within another composable function that doesn't take any arguments and calls your composable function with the necessary parameters
- Also, don't forget to annotate it with @Preview & @Composable annotations
Kotlin
@Preview
@Composable
fun ZoomableComposablePreview() {
ZoomableComposable()
}
Step 8: Complete Code Snippet
Kotlin
// Code for making a view zoomable
// in android jetpack compose
// Please replace the name of package
// with your project name
package com.example.zoomableview
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.example.zoomableview.R
class ZoomableActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This sets the @Composable function as
// the root view of the activity.
// This is meant to replace the .xml
// file that we would typically
// set using the setContent(R.id.xml_file) method.
setContent {
ZoomableComposable()
}
}
}
@Composable
fun ZoomableComposable() {
// Reacting to state changes is the core behavior of Compose.
// We use the state composable that is used for holding a
// state value in this composable for representing the current
// value scale(for zooming in the image)
// & translation(for panning across the image).
// Any composable that reads the value of counter will
// be recomposed any time the value changes.
var scale by remember { mutableStateOf(1f) }
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
// In the example below, we make the Column composable zoomable
// by leveraging the Modifier.pointerInput modifier
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.pointerInput(Unit) {
forEachGesture {
awaitPointerEventScope {
awaitFirstDown()
do {
val event = awaitPointerEvent()
scale *= event.calculateZoom()
val offset = event.calculatePan()
offsetX += offset.x
offsetY += offset.y
} while (event.changes.any { it.pressed })
}
}
}
) {
// painterResource method loads an image resource asynchronously
val imagepainter = painterResource(id = R.drawable.ic_launcher_background)
// We use the graphicsLayer modifier to modify the scale & translation
// of the image.
// This is read from the state properties that we created above.
Image(
modifier = Modifier.fillMaxSize().graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offsetX,
translationY = offsetY
),
painter = imagepainter,
contentDescription = "androids launcher default launcher background image"
)
}
}
@Preview
@Composable
fun ZoomableComposablePreview() {
ZoomableComposable()
}
Output:
As we can see with the help of jetpack compose we can zoom an image in and out without returning to the original perspective until we zoom out.
Similar Reads
Android Jetpack Compose - Implement Dark Mode
Jetpack Compose is a new UI toolkit from Google that is used to create native Android UI. It speeds up and simplifies UI development by using less code, Kotlin APIs, and powerful tools. Fortunately, Android 10 and later enable automatically "dark-theming" your app by forcing it to utilize certain da
3 min read
Android Jetpack Compose - Implement Navigation Drawer
Jetpack Compose is a new UI toolkit from Google used to create native Android UI. It speeds up and simplifies UI development using less code, Kotlin APIs, and powerful tools. Prerequisites Familiar with Kotlin and OOP ConceptsBasic understanding of Jetpack ComposeThe navigation drawer is the most us
3 min read
ListView in Android using Jetpack Compose
A ListView is a UI component that displays data in a vertically scrollable list, where each item is placed one below the other. It is widely used in Android applications to showcase categorized data in an organized manner. In Jetpack Compose, the traditional ListView from XML-based layouts is replac
2 min read
Toast in Android Jetpack Compose
In Android, a Toast is a message or a pop-up message that generally appears at the bottom of the screen for a short span. A Toast is used to deliver simple feedback about any function or operation the application is running on the device. In simpler words, it displays the status of any running or fi
3 min read
Spacer in Android Jetpack Compose
In Jetpack Compose, a Spacer is a blank element that is used to create a Space between two UI elements. Suppose, we have created Element 1 and we want to place Element 2 below Element 1 but with a top margin, we can declare a Spacer between the two elements. So in this article, we will show you how
2 min read
Implement Markdown Text in Android using Jetpack Compose
Markdown is a third-party git library that can be used to identify particular types of regex (Regular Expressions) that specifies a search pattern in the text. For example, if you write a passage and mention a website and a telephone number in it, Markdown Text would detect these patterns and create
3 min read
Android Jetpack Compose - Implement Easy Rating Dialog
Many times in android applications we can get to see that they ask for users to rate their application and share their reviews about the application. In this article, we will take a look at How to implement an Easy Rating dialog in android applications using Jetpack Compose. Using the Easy Rating Di
4 min read
WebView in Android using Jetpack Compose
In Android, a WebView is an extension of View Class that allows us to display webpages. In simpler words, if you want to display a webpage in Activity Layout, then you can implement a WebView to display it. It is very similar to a browser but isn't one. A WebView can rather be referred to as a show
3 min read
Android Jetpack Compose Internal Storage
Many times in the android application we have to save the data from our android application within the device storage. For storing the data we can store it in shared preferences, internal as well as external storage. In this article, we will take a look at How to save data to Internal storage in And
6 min read
ImageView in Android using Jetpack Compose
Images, graphics, and vectors attract many users as they provide a lot of information in a very informative way. ImageView in Android is used to display different types of images, from drawables to Bitmaps. In this article, we will take a look at the implementation of an ImageView in Android using J
4 min read