From 65046d3d84d65eb47d34d830ae02293d529fc555 Mon Sep 17 00:00:00 2001 From: cartland Date: Fri, 22 Aug 2025 10:23:13 -0700 Subject: [PATCH 1/6] Adds DrawArea sample This commit introduces a new code snippet demonstrating a `DrawArea` composable. This composable provides a canvas for drawing and processes touch input to draw lines. The drawing state is managed by a `DrawAreaViewModel`. The sample is also integrated into the main snippets navigation. --- .../android/snippets/DrawAreaSample.kt | 135 ++++++++++++++++++ .../compose/snippets/SnippetsActivity.kt | 2 + .../snippets/navigation/Destination.kt | 3 +- 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt diff --git a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt b/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt new file mode 100644 index 000000000..5b6c6d2ac --- /dev/null +++ b/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt @@ -0,0 +1,135 @@ +package com.example.android.snippets + +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel + +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This sample demonstrates a Composable function that provides a canvas for drawing and + * processes touch input events to draw lines. It uses a ViewModel to manage the drawing state. + * + * Gradle Dependencies: + * implementation "androidx.compose.ui:ui:1.x.x" + * implementation "androidx.compose.foundation:foundation:1.x.x" + * implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.x.x" + * + * Manifest Permissions: + * None + */ + +const val TAG = "DrawAreaSample" + +class DrawAreaSampleActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + Column(Modifier.fillMaxSize().background(Color.LightGray)) { + DrawArea(modifier = Modifier.weight(1f)) + } + } + } +} + +// [START draw_area_sample] +@Composable +@OptIn(ExperimentalComposeUiApi::class) +fun DrawArea(modifier: Modifier = Modifier, viewModel: DrawAreaViewModel = viewModel()) { + Canvas( + modifier = modifier + .clipToBounds() + .pointerInteropFilter { motionEvent -> + viewModel.processMotionEvent(motionEvent) + true + } + ) { + viewModel.paths.forEach { path -> + drawPath( + path = path, + color = Color.Black, + style = Stroke(width = 8f, cap = StrokeCap.Round) + ) + } + } +} +// [END draw_area_sample] + +class DrawAreaViewModel : ViewModel() { + private var currentPath = Path() + val paths: SnapshotStateList = mutableStateListOf() + + @OptIn(ExperimentalComposeUiApi::class) + fun processMotionEvent(motionEvent: android.view.MotionEvent) { + when (motionEvent.actionMasked) { + android.view.MotionEvent.ACTION_DOWN -> { + Log.d(TAG, "ACTION_DOWN at (${motionEvent.x}, ${motionEvent.y})") + currentPath = Path().apply { + moveTo(motionEvent.x, motionEvent.y) + } + paths.add(currentPath) + } + android.view.MotionEvent.ACTION_MOVE -> { + Log.d(TAG, "ACTION_MOVE at (${motionEvent.x}, ${motionEvent.y})") + // To trigger recomposition, we need to replace the path object + // in the list with a new one. + val newPath = Path().apply { + addPath(currentPath) + lineTo(motionEvent.x, motionEvent.y) + } + + // Replace the last path with the updated one. + if (paths.isNotEmpty()) { + paths[paths.size - 1] = newPath + } + currentPath = newPath + } + android.view.MotionEvent.ACTION_UP, + android.view.MotionEvent.ACTION_CANCEL -> { + Log.d(TAG, "ACTION_UP/CANCEL") + // Path is complete, no further action needed + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewDrawArea() { + Column(Modifier.fillMaxSize().background(Color.LightGray)) { + DrawArea(modifier = Modifier.weight(1f)) + } +} diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 48f2a1109..579abe310 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -62,6 +62,7 @@ import com.example.compose.snippets.layouts.PagerExamples import com.example.compose.snippets.navigation.Destination import com.example.compose.snippets.navigation.TopComponentsDestination import com.example.compose.snippets.ui.theme.SnippetsTheme +import com.example.android.snippets.DrawArea class SnippetsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -94,6 +95,7 @@ class SnippetsActivity : ComponentActivity() { Destination.ShapesExamples -> ApplyPolygonAsClipImage() Destination.SharedElementExamples -> PlaceholderSizeAnimated_Demo() Destination.PagerExamples -> PagerExamples() + Destination.DrawAreaSample -> DrawArea() } } } diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt index 8d86b28b4..6107d4e12 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/navigation/Destination.kt @@ -24,7 +24,8 @@ enum class Destination(val route: String, val title: String) { ScreenshotExample("screenshotExample", "Screenshot Examples"), ShapesExamples("shapesExamples", "Shapes Examples"), SharedElementExamples("sharedElement", "Shared elements"), - PagerExamples("pagerExamples", "Pager examples") + PagerExamples("pagerExamples", "Pager examples"), + DrawAreaSample("drawAreaSample", "Draw Area Sample") } // Enum class for compose components navigation screen. From 219c611ede2186d4b6b51f554ecbfe7dd24fc4fd Mon Sep 17 00:00:00 2001 From: cartland <846051+cartland@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:40:45 +0000 Subject: [PATCH 2/6] Apply Spotless --- .../example/android/snippets/DrawAreaSample.kt | 16 ++++++++++++++++ .../example/compose/snippets/SnippetsActivity.kt | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt b/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt index 5b6c6d2ac..1f91eede8 100644 --- a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt +++ b/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.example.android.snippets import android.os.Bundle diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 579abe310..a634b244c 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.example.android.snippets.DrawArea import com.example.compose.snippets.animations.AnimationExamplesScreen import com.example.compose.snippets.animations.sharedelement.PlaceholderSizeAnimated_Demo import com.example.compose.snippets.components.AppBarExamples @@ -62,7 +63,6 @@ import com.example.compose.snippets.layouts.PagerExamples import com.example.compose.snippets.navigation.Destination import com.example.compose.snippets.navigation.TopComponentsDestination import com.example.compose.snippets.ui.theme.SnippetsTheme -import com.example.android.snippets.DrawArea class SnippetsActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { From 4cfa03bd94de6bf824f4977dc61cacb8ed857f2b Mon Sep 17 00:00:00 2001 From: cartland Date: Fri, 22 Aug 2025 10:46:03 -0700 Subject: [PATCH 3/6] fix(compose): Correct snippet region tags for DrawAreaSample Updates the region tags in DrawAreaSample.kt to conform to the `android_compose_` prefix requirement. --- .../main/java/com/example/android/snippets/DrawAreaSample.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt b/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt index 1f91eede8..b80322fe3 100644 --- a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt +++ b/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt @@ -81,7 +81,7 @@ class DrawAreaSampleActivity : ComponentActivity() { } } -// [START draw_area_sample] +// [START android_compose_draw_area_sample] @Composable @OptIn(ExperimentalComposeUiApi::class) fun DrawArea(modifier: Modifier = Modifier, viewModel: DrawAreaViewModel = viewModel()) { @@ -102,7 +102,7 @@ fun DrawArea(modifier: Modifier = Modifier, viewModel: DrawAreaViewModel = viewM } } } -// [END draw_area_sample] +// [END android_compose_draw_area_sample] class DrawAreaViewModel : ViewModel() { private var currentPath = Path() From be782750bf7acaa8bb3bd53ed2188318e77acb3e Mon Sep 17 00:00:00 2001 From: cartland Date: Fri, 22 Aug 2025 11:25:26 -0700 Subject: [PATCH 4/6] Refactor: Move DrawAreaSample to pointerinput package The `DrawAreaSample.kt` file was moved to a new package `com.example.compose.snippets.touchinput.pointerinput`. The import for `DrawArea` in `SnippetsActivity.kt` was updated to reflect this change. --- .../main/java/com/example/compose/snippets/SnippetsActivity.kt | 2 +- .../snippets/touchinput/pointerinput}/DrawAreaSample.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename compose/snippets/src/main/java/com/example/{android/snippets => compose/snippets/touchinput/pointerinput}/DrawAreaSample.kt (98%) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index a634b244c..32b6cb5d1 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -28,7 +28,7 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.example.android.snippets.DrawArea +import com.example.compose.snippets.touchinput.pointerinput.DrawArea import com.example.compose.snippets.animations.AnimationExamplesScreen import com.example.compose.snippets.animations.sharedelement.PlaceholderSizeAnimated_Demo import com.example.compose.snippets.components.AppBarExamples diff --git a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt similarity index 98% rename from compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt rename to compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt index b80322fe3..32d3be0a8 100644 --- a/compose/snippets/src/main/java/com/example/android/snippets/DrawAreaSample.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.android.snippets +package com.example.compose.snippets.touchinput.pointerinput import android.os.Bundle import android.util.Log From ad8868609cd1c42872988709ca01957b4db7408d Mon Sep 17 00:00:00 2001 From: cartland <846051+cartland@users.noreply.github.com> Date: Fri, 22 Aug 2025 18:27:24 +0000 Subject: [PATCH 5/6] Apply Spotless --- .../main/java/com/example/compose/snippets/SnippetsActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt index 32b6cb5d1..710b32d5d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/SnippetsActivity.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.Modifier import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.example.compose.snippets.touchinput.pointerinput.DrawArea import com.example.compose.snippets.animations.AnimationExamplesScreen import com.example.compose.snippets.animations.sharedelement.PlaceholderSizeAnimated_Demo import com.example.compose.snippets.components.AppBarExamples @@ -62,6 +61,7 @@ import com.example.compose.snippets.landing.LandingScreen import com.example.compose.snippets.layouts.PagerExamples import com.example.compose.snippets.navigation.Destination import com.example.compose.snippets.navigation.TopComponentsDestination +import com.example.compose.snippets.touchinput.pointerinput.DrawArea import com.example.compose.snippets.ui.theme.SnippetsTheme class SnippetsActivity : ComponentActivity() { From 13698d72964aa8886513208e905679e4f2471fff Mon Sep 17 00:00:00 2001 From: cartland Date: Fri, 22 Aug 2025 12:13:26 -0700 Subject: [PATCH 6/6] fix(compose): Correct snippet region tags for DrawAreaSample Updates the region tags in DrawAreaSample.kt to conform to the `android_snippets_compose_` prefix requirement. --- .../snippets/touchinput/pointerinput/DrawAreaSample.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt index 32d3be0a8..b45c197f5 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/touchinput/pointerinput/DrawAreaSample.kt @@ -81,7 +81,7 @@ class DrawAreaSampleActivity : ComponentActivity() { } } -// [START android_compose_draw_area_sample] +// [START android_snippets_compose_draw_area_sample] @Composable @OptIn(ExperimentalComposeUiApi::class) fun DrawArea(modifier: Modifier = Modifier, viewModel: DrawAreaViewModel = viewModel()) { @@ -102,7 +102,7 @@ fun DrawArea(modifier: Modifier = Modifier, viewModel: DrawAreaViewModel = viewM } } } -// [END android_compose_draw_area_sample] +// [END android_snippets_compose_draw_area_sample] class DrawAreaViewModel : ViewModel() { private var currentPath = Path()