From 8c25bc6aa1ff19c19549b592b11b1b50bd03729c Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 29 May 2026 15:00:01 +0100 Subject: [PATCH 1/6] Add mesh gradient documentation snippets and update Compose BOM * Created `MeshGradientSnippets.kt` featuring examples for basic, complex, and animated mesh gradients using `MeshGradientPainter`. * Included demonstrations of vertex positioning, color mapping, and custom control points for manipulating mesh curves. --- .../snippets/graphics/MeshGradientSnippets.kt | 260 ++++++++++++++++++ gradle/libs.versions.toml | 4 +- 2 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt new file mode 100644 index 000000000..fb6ade95a --- /dev/null +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt @@ -0,0 +1,260 @@ +package com.example.compose.snippets.graphics + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContentPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.paint +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.MeshGradientPainter +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Preview +@Composable +fun MeshGradientBasic(modifier: Modifier = Modifier) { + // [START android_compose_graphics_mesh_gradient_basic] + val rows = 1 + val columns = 1 + + val gradientPainter = remember { + MeshGradientPainter(rows, columns) { + // Parameters: row, column, position, color + setVertex(0, 0, Offset(0f, 0f), Color.Red) // Top-Left + setVertex(0, 1, Offset(1f, 0f), Color.Blue) // Top-Right + setVertex(1, 0, Offset(0f, 1f), Color.Green) // Bottom-Left + setVertex(1, 1, Offset(1f, 1f), Color.Yellow) // Bottom-Right + } + } + + Box( + modifier = modifier + .aspectRatio(16/9f) + .fillMaxWidth() + .paint(gradientPainter) + ) + // [END android_compose_graphics_mesh_gradient_basic] +} + +@Preview +@Composable +fun MeshGradientControlPoints(modifier: Modifier = Modifier) { + // [START android_compose_graphics_mesh_gradient_control_points] + val customTangentPainter = remember { + MeshGradientPainter(rows = 1, columns = 1) { + // Tweak the top-left vertex to curve outwards to the right and bottom + setVertex( + row = 0, + column = 0, + position = Offset(0f, 0f), + color = Color.Magenta, + rightControlPoint = Offset(0.4f, 0.1f), + bottomControlPoint = Offset(0.1f, 0.4f) + ) + + // Other points can remain unspecified to use default inferred fallback tangents + setVertex(0, 1, Offset(1f, 0f), Color.Cyan) + setVertex(1, 0, Offset(0f, 1f), Color.Blue) + setVertex(1, 1, Offset(1f, 1f), Color.Black) + } + } + Box( + modifier = modifier + .aspectRatio(16/9f) + .fillMaxWidth() + .paint(customTangentPainter) + ) + // [END android_compose_graphics_mesh_gradient_control_points] +} + +@Preview +@Composable +fun MeshGradientComplex(modifier: Modifier = Modifier) { + val purple = Color(0xFFAF52DE) + val indigo = Color(0xFF5856D6) + val yellow = Color(0xFFFFCC00) + val pink = Color(0xFFFF2D55) + val orange = Color(0xFFFF9500) + // [START android_compose_graphics_mesh_gradient_complex] + val points = remember { + listOf( + Offset(0.0f, 0.0f), Offset(0.3f, 0.0f), Offset(0.7f, 0.0f), Offset(1.0f, 0.0f), + Offset(0.0f, 0.3f), Offset(0.2f, 0.4f), Offset(0.7f, 0.2f), Offset(1.0f, 0.3f), + Offset(0.0f, 0.7f), Offset(0.3f, 0.8f), Offset(0.7f, 0.6f), Offset(1.0f, 0.7f), + Offset(0.0f, 1.0f), Offset(0.3f, 1.0f), Offset(0.7f, 1.0f), Offset(1.0f, 1.0f) + ) + } + + val gradientPainter = remember { + MeshGradientPainter(rows = 3, columns = 3) { + // Row 0 + setVertex(0, 0, points[0], yellow) + setVertex(0, 1, points[1], orange) + setVertex(0, 2, points[2], yellow) + setVertex(0, 3, points[3], purple) + + // Row 1 + setVertex(1, 0, points[4], pink) + setVertex(1, 1, points[5], yellow) + setVertex(1, 2, points[6], pink) + setVertex(1, 3, points[7], purple) + + // Row 2 + setVertex(2, 0, points[8], indigo) + setVertex(2, 1, points[9], pink) + setVertex(2, 2, points[10], purple) + setVertex(2, 3, points[11], indigo) + + // Row 3 + setVertex(3, 0, points[12], purple) + setVertex(3, 1, points[13], indigo) + setVertex(3, 2, points[14], pink) + setVertex(3, 3, points[15], yellow) + } + } + + Box( + modifier = modifier.padding(32.dp) + .aspectRatio(16 / 9f) + .fillMaxWidth() + .paint(gradientPainter) + // [START_EXCLUDE] + /* .drawWithContent { + drawContent() + points.forEach { normalizedOffset -> + val pixelOffset = Offset( + x = normalizedOffset.x * size.width, + y = normalizedOffset.y * size.height + ) + // Draw a subtle black circle outline and white center for maximum visibility + drawCircle( + color = Color.Black, + radius = 10f, + center = pixelOffset + ) + drawCircle( + color = Color.White, + radius = 8f, + center = pixelOffset + ) + } + }*/ + // [END_EXCLUDE] + ) + // [END android_compose_graphics_mesh_gradient_complex] +} + +@Preview +@Composable +fun MeshGradientAnimation(modifier: Modifier = Modifier) { + // [START android_compose_graphics_mesh_gradient_animation] + val infiniteTransition = rememberInfiniteTransition(label = "meshMovement") + val animatedOffset by infiniteTransition.animateFloat( + initialValue = -0.1f, + targetValue = 0.1f, + animationSpec = infiniteRepeatable( + animation = tween(2500, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "offset" + ) + + val coral = Color(255, 90, 90) + val peach = Color(255, 139, 90) + val amber = Color(255, 169, 90) + val sunshine = Color(255, 212, 90) + val indigo = Color(0xFF5856D6) + val pink = Color(0xFFFF2D55) + + val points = listOf( + Offset(0.0f, 0.0f), + Offset(0.3f, 0.0f), + Offset(0.7f, 0.0f), + Offset(1.0f, 0.0f), + Offset(0.0f, 0.3f), + Offset(0.2f + animatedOffset, 0.4f + animatedOffset), + Offset(0.7f + animatedOffset, 0.2f + animatedOffset), + Offset(1.0f, 0.3f), + Offset(0.0f, 0.7f), + Offset(0.3f + animatedOffset, 0.8f), + Offset(0.7f + animatedOffset, 0.6f), + Offset(1.0f, 0.7f), + Offset(0.0f, 1.0f), + Offset(0.3f, 1.0f), + Offset(0.7f, 1.0f), + Offset(1.0f, 1.0f) + ) + + val gradientPainter = remember(animatedOffset) { + MeshGradientPainter(rows = 3, columns = 3) { + // Row 0 + setVertex(0, 0, points[0], indigo) + setVertex(0, 1, points[1], peach) + setVertex(0, 2, points[2], amber) + setVertex(0, 3, points[3], sunshine) + //[START_EXCLUDE] + // Row 1 + setVertex(1, 0, points[4], pink) + setVertex(1, 1, points[5], coral) + setVertex(1, 2, points[6], peach) + setVertex(1, 3, points[7], indigo) + + // Row 2 + setVertex(2, 0, points[8], coral) + setVertex(2, 1, points[9], pink) + setVertex(2, 2, points[10], sunshine) + setVertex(2, 3, points[11], amber) + + // Row 3 + setVertex(3, 0, points[12], sunshine) + setVertex(3, 1, points[13], amber) + setVertex(3, 2, points[14], pink) + setVertex(3, 3, points[15], indigo) + //[END_EXCLUDE] + } + } + + Box( + modifier = modifier.padding(32.dp) + .safeContentPadding() + .aspectRatio(16 / 9f) + .fillMaxWidth() + .paint(gradientPainter) + // [START_EXCLUDE] + .drawWithContent { + drawContent() + points.forEach { normalizedOffset -> + val pixelOffset = Offset( + x = normalizedOffset.x * size.width, + y = normalizedOffset.y * size.height + ) + // Draw a subtle black circle outline and white center for maximum visibility + drawCircle( + color = Color.Black, + radius = 10f, + center = pixelOffset + ) + drawCircle( + color = Color.White, + radius = 8f, + center = pixelOffset + ) + } + } + // [END_EXCLUDE] + ) + // [END android_compose_graphics_mesh_gradient_animation] +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d396d1cdc..c31bf1ec8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ androidx-appcompat = "1.7.1" androidx-appfunctions = "1.0.0-alpha08" androidx-cameraX = "1.6.0" androidx-car = "1.7.0" -androidx-compose-bom = "2026.05.00" +androidx-compose-bom = "2026.05.01" androidx-compose-ui-test = "1.7.0-alpha08" androidx-compose-ui-test-junit4-accessibility = "1.11.1" androidx-constraintlayout = "2.2.1" @@ -140,7 +140,7 @@ androidx-camera-viewfinder-compose = { module = "androidx.camera.viewfinder:view androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-cameraX" } androidx-car = { module = "androidx.car.app:app", version.ref = "androidx-car"} androidx-compose-animation-graphics = { module = "androidx.compose.animation:animation-graphics", version.ref = "compose-latest" } -androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "androidx-compose-bom" } +androidx-compose-bom = { module = "androidx.compose:compose-bom-alpha", version.ref = "androidx-compose-bom" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-latest" } androidx-compose-foundation-layout = { module = "androidx.compose.foundation:foundation-layout", version.ref = "compose-latest" } androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "compose-latest" } From 1ab00c485263a4eda40c396f1b2cf48e10657b3a Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 29 May 2026 15:10:10 +0100 Subject: [PATCH 2/6] Uncomment mesh gradient point illustration. --- .../example/compose/snippets/graphics/MeshGradientSnippets.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt index fb6ade95a..68a12f51a 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt @@ -132,7 +132,7 @@ fun MeshGradientComplex(modifier: Modifier = Modifier) { .fillMaxWidth() .paint(gradientPainter) // [START_EXCLUDE] - /* .drawWithContent { + .drawWithContent { drawContent() points.forEach { normalizedOffset -> val pixelOffset = Offset( @@ -151,7 +151,7 @@ fun MeshGradientComplex(modifier: Modifier = Modifier) { center = pixelOffset ) } - }*/ + } // [END_EXCLUDE] ) // [END android_compose_graphics_mesh_gradient_complex] From ac130d31ca2d58cbeb9b44983bda912c8bf55cf0 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Mon, 1 Jun 2026 08:51:48 +0100 Subject: [PATCH 3/6] Remove reallocation due to offset change. --- .../snippets/graphics/MeshGradientSnippets.kt | 77 +++++-------------- 1 file changed, 18 insertions(+), 59 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt index 68a12f51a..feec5d85b 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt @@ -179,82 +179,41 @@ fun MeshGradientAnimation(modifier: Modifier = Modifier) { val indigo = Color(0xFF5856D6) val pink = Color(0xFFFF2D55) - val points = listOf( - Offset(0.0f, 0.0f), - Offset(0.3f, 0.0f), - Offset(0.7f, 0.0f), - Offset(1.0f, 0.0f), - Offset(0.0f, 0.3f), - Offset(0.2f + animatedOffset, 0.4f + animatedOffset), - Offset(0.7f + animatedOffset, 0.2f + animatedOffset), - Offset(1.0f, 0.3f), - Offset(0.0f, 0.7f), - Offset(0.3f + animatedOffset, 0.8f), - Offset(0.7f + animatedOffset, 0.6f), - Offset(1.0f, 0.7f), - Offset(0.0f, 1.0f), - Offset(0.3f, 1.0f), - Offset(0.7f, 1.0f), - Offset(1.0f, 1.0f) - ) - val gradientPainter = remember(animatedOffset) { + val gradientPainter = remember { MeshGradientPainter(rows = 3, columns = 3) { // Row 0 - setVertex(0, 0, points[0], indigo) - setVertex(0, 1, points[1], peach) - setVertex(0, 2, points[2], amber) - setVertex(0, 3, points[3], sunshine) - //[START_EXCLUDE] + setVertex(0, 0, Offset(0.0f, 0.0f), indigo) + setVertex(0, 1, Offset(0.3f, 0.0f), peach) + setVertex(0, 2, Offset(0.7f, 0.0f), amber) + setVertex(0, 3, Offset(1.0f, 0.0f), sunshine) // Row 1 - setVertex(1, 0, points[4], pink) - setVertex(1, 1, points[5], coral) - setVertex(1, 2, points[6], peach) - setVertex(1, 3, points[7], indigo) + setVertex(1, 0, Offset(0.0f, 0.3f), pink) + setVertex(1, 1, Offset(0.2f, 0.4f) + Offset(animatedOffset, animatedOffset), coral) + setVertex(1, 2, Offset(0.7f, 0.2f) + Offset(animatedOffset, animatedOffset), peach) + setVertex(1, 3, Offset(1.0f, 0.3f), indigo) // Row 2 - setVertex(2, 0, points[8], coral) - setVertex(2, 1, points[9], pink) - setVertex(2, 2, points[10], sunshine) - setVertex(2, 3, points[11], amber) + setVertex(2, 0, Offset(0.0f, 0.7f), coral) + setVertex(2, 1, Offset(0.3f, 0.8f) + Offset(animatedOffset, 0f), pink) + setVertex(2, 2, Offset(0.7f, 0.6f) + Offset(animatedOffset, 0f), sunshine) + setVertex(2, 3, Offset(1.0f, 0.7f), amber) // Row 3 - setVertex(3, 0, points[12], sunshine) - setVertex(3, 1, points[13], amber) - setVertex(3, 2, points[14], pink) - setVertex(3, 3, points[15], indigo) - //[END_EXCLUDE] + setVertex(3, 0, Offset(0.0f, 1.0f), sunshine) + setVertex(3, 1, Offset(0.3f, 1.0f), amber) + setVertex(3, 2, Offset(0.7f, 1.0f), pink) + setVertex(3, 3, Offset(1.0f, 1.0f), indigo) } } + Box( modifier = modifier.padding(32.dp) .safeContentPadding() .aspectRatio(16 / 9f) .fillMaxWidth() .paint(gradientPainter) - // [START_EXCLUDE] - .drawWithContent { - drawContent() - points.forEach { normalizedOffset -> - val pixelOffset = Offset( - x = normalizedOffset.x * size.width, - y = normalizedOffset.y * size.height - ) - // Draw a subtle black circle outline and white center for maximum visibility - drawCircle( - color = Color.Black, - radius = 10f, - center = pixelOffset - ) - drawCircle( - color = Color.White, - radius = 8f, - center = pixelOffset - ) - } - } - // [END_EXCLUDE] ) // [END android_compose_graphics_mesh_gradient_animation] } \ No newline at end of file From 6e3754407a411513aae1674d85241fa6bd3bd4d7 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 4 Jun 2026 10:48:57 +0100 Subject: [PATCH 4/6] fix build. --- wearcompanion/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wearcompanion/build.gradle.kts b/wearcompanion/build.gradle.kts index cf841b743..6853dc413 100644 --- a/wearcompanion/build.gradle.kts +++ b/wearcompanion/build.gradle.kts @@ -5,7 +5,7 @@ plugins { android { namespace = "com.example.wear.snippets" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.example.wear.snippets" From e6af48c95f6be9b2a87231782e87fc6e81f11124 Mon Sep 17 00:00:00 2001 From: riggaroo <9973046+riggaroo@users.noreply.github.com> Date: Thu, 4 Jun 2026 09:52:19 +0000 Subject: [PATCH 5/6] Apply Spotless --- .../snippets/graphics/MeshGradientSnippets.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt index feec5d85b..c21c657b5 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/MeshGradientSnippets.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2026 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.compose.snippets.graphics import androidx.compose.animation.core.LinearEasing From eda3469e00edc0b6074779190a813a67f25234f3 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Thu, 4 Jun 2026 11:08:45 +0100 Subject: [PATCH 6/6] fix build. --- camerax/build.gradle.kts | 2 +- datastore/build.gradle.kts | 2 +- tv/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/camerax/build.gradle.kts b/camerax/build.gradle.kts index c27ca6ff3..0bc6ae776 100644 --- a/camerax/build.gradle.kts +++ b/camerax/build.gradle.kts @@ -29,7 +29,7 @@ kotlin { android { namespace = "com.example.camerax.snippets" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.example.camerax.snippets" diff --git a/datastore/build.gradle.kts b/datastore/build.gradle.kts index 0ad5e2f93..e83d7d662 100644 --- a/datastore/build.gradle.kts +++ b/datastore/build.gradle.kts @@ -20,7 +20,7 @@ kotlin { android { namespace = "com.example.datastore.snippets" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.example.datastore.snippets" diff --git a/tv/build.gradle.kts b/tv/build.gradle.kts index 269758e42..ef7c52b0c 100644 --- a/tv/build.gradle.kts +++ b/tv/build.gradle.kts @@ -5,7 +5,7 @@ plugins { android { namespace = "com.example.tv" - compileSdk = 36 + compileSdk = 37 defaultConfig { applicationId = "com.example.tv"