From f875462df462447b2d4e8f76b2c3c771d78f290b Mon Sep 17 00:00:00 2001 From: dmail Date: Mon, 16 Sep 2024 14:24:53 -0700 Subject: [PATCH 01/10] Code snippet for Compose doc at https://developer.android.com/quick-guides/content/animate-text?hl=en (Animate text character-by-character). This commit slightly modifies (makes buildable in our repo) the existing code on the current DAC page. That code, in turn, was BNR's simplified version of Xoogler astamato's Medium article at https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5 --- .../compose/snippets/text/TextSnippets.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index 75321bb80..61c974ccc 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -38,6 +38,7 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -839,3 +840,56 @@ private val firaSansFamily = FontFamily() val LightBlue = Color(0xFF0066FF) val Purple = Color(0xFF800080) + +// [START android_compose_text_auto_format_phone_number_validatetext] +@Composable +fun ValidateInput () { + class EmailViewModel : ViewModel() { + var email by mutableStateOf("") + private set + + val emailHasErrors by derivedStateOf { + if (email.isNotEmpty()) { + // Email is considered erroneous until it completely matches EMAIL_ADDRESS. + !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() + } else { + false + } + } + + fun updateEmail(input: String) { + email = input + } + } + + @Composable + fun ValidatingInputTextField( + email: String, + updateState: (String) -> Unit, + validatorHasErrors: Boolean + ) { + val viewModel = EmailViewModel() + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = email, + onValueChange = updateState, + label = { Text("Email") }, + isError = validatorHasErrors, + supportingText = { + if (validatorHasErrors) { + Text("Incorrect email format.") + } + } + ) + + + ValidatingInputTextField( + email = viewModel.email, + updateState = { input -> viewModel.updateEmail(input) }, + validatorHasErrors = viewModel.emailHasErrors + ) + } +} +// [END android_compose_text_auto_format_phone_number_validatetext] \ No newline at end of file From cca43411a65098ea28dd41f37e528ac71c305f4c Mon Sep 17 00:00:00 2001 From: dmail Date: Mon, 16 Sep 2024 14:27:01 -0700 Subject: [PATCH 02/10] Code snippet for Compose doc at https://developer.android.com/quick-guides/content/animate-text?hl=en (Animate text character-by-character). This commit slightly modifies (makes buildable in our repo) the existing code on the current DAC page. That code, in turn, was BNR's simplified version of Xoogler astamato's Medium article at https://medium.com/androiddevelopers/effective-state-management-for-textfield-in-compose-d6e5b070fbe5 --- .../com/example/compose/snippets/text/TextSnippets.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index 61c974ccc..ee89b7a22 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -868,7 +868,7 @@ fun ValidateInput () { updateState: (String) -> Unit, validatorHasErrors: Boolean ) { - val viewModel = EmailViewModel() + val emailViewModel = EmailViewModel() OutlinedTextField( modifier = Modifier .fillMaxWidth() @@ -886,9 +886,9 @@ fun ValidateInput () { ValidatingInputTextField( - email = viewModel.email, - updateState = { input -> viewModel.updateEmail(input) }, - validatorHasErrors = viewModel.emailHasErrors + email = emailViewModel.email, + updateState = { input -> emailViewModel.updateEmail(input) }, + validatorHasErrors = emailViewModel.emailHasErrors ) } } From 638b01a69afecd53bb1150d7d3919fcae29a9725 Mon Sep 17 00:00:00 2001 From: thedmail Date: Mon, 16 Sep 2024 22:00:30 +0000 Subject: [PATCH 03/10] Apply Spotless --- .../java/com/example/compose/snippets/text/TextSnippets.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index ee89b7a22..d8859ca7d 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -843,7 +843,7 @@ val Purple = Color(0xFF800080) // [START android_compose_text_auto_format_phone_number_validatetext] @Composable -fun ValidateInput () { +fun ValidateInput() { class EmailViewModel : ViewModel() { var email by mutableStateOf("") private set @@ -884,7 +884,6 @@ fun ValidateInput () { } ) - ValidatingInputTextField( email = emailViewModel.email, updateState = { input -> emailViewModel.updateEmail(input) }, @@ -892,4 +891,4 @@ fun ValidateInput () { ) } } -// [END android_compose_text_auto_format_phone_number_validatetext] \ No newline at end of file +// [END android_compose_text_auto_format_phone_number_validatetext] From abb4277056acb9409d7221b72e3a25552860dd3d Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 27 Sep 2024 12:47:20 +0100 Subject: [PATCH 04/10] Fix email input snippet --- .../compose/snippets/text/TextSnippets.kt | 92 ++++++++++--------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index d8859ca7d..dfb167f92 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -18,6 +18,8 @@ package com.example.compose.snippets.text +import android.graphics.Typeface +import android.provider.ContactsContract.CommonDataKinds.Email import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee @@ -82,6 +84,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel /** * This file lets DevRel track changes to snippets present in @@ -519,7 +522,7 @@ private object TextEffectiveStateManagement1 { private object TextEffectiveStateManagement2 { class UserRepository - val viewModel = SignUpViewModel(UserRepository()) + private val viewModel = SignUpViewModel(UserRepository()) // [START android_compose_text_state_management] // SignUpViewModel.kt @@ -836,59 +839,60 @@ class NanpVisualTransformation() : VisualTransformation { } // [END android_compose_text_auto_format_phone_number_transformtext] -private val firaSansFamily = FontFamily() +private val firaSansFamily = FontFamily(typeface = Typeface.DEFAULT) val LightBlue = Color(0xFF0066FF) val Purple = Color(0xFF800080) // [START android_compose_text_auto_format_phone_number_validatetext] -@Composable -fun ValidateInput() { - class EmailViewModel : ViewModel() { - var email by mutableStateOf("") - private set - - val emailHasErrors by derivedStateOf { - if (email.isNotEmpty()) { - // Email is considered erroneous until it completely matches EMAIL_ADDRESS. - !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() - } else { - false - } +class EmailViewModel : ViewModel() { + var email by mutableStateOf("") + private set + + val emailHasErrors by derivedStateOf { + if (email.isNotEmpty()) { + // Email is considered erroneous until it completely matches EMAIL_ADDRESS. + !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() + } else { + false } + } - fun updateEmail(input: String) { - email = input - } + fun updateEmail(input: String) { + email = input } +} - @Composable - fun ValidatingInputTextField( - email: String, - updateState: (String) -> Unit, - validatorHasErrors: Boolean - ) { - val emailViewModel = EmailViewModel() - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp), - value = email, - onValueChange = updateState, - label = { Text("Email") }, - isError = validatorHasErrors, - supportingText = { - if (validatorHasErrors) { - Text("Incorrect email format.") - } +@Composable +fun ValidatingInputTextField( + email: String, + updateState: (String) -> Unit, + validatorHasErrors: Boolean +) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + value = email, + onValueChange = updateState, + label = { Text("Email") }, + isError = validatorHasErrors, + supportingText = { + if (validatorHasErrors) { + Text("Incorrect email format.") } - ) + } + ) +} - ValidatingInputTextField( - email = emailViewModel.email, - updateState = { input -> emailViewModel.updateEmail(input) }, - validatorHasErrors = emailViewModel.emailHasErrors - ) - } +@Preview +@Composable +fun ValidateInput() { + val emailViewModel : EmailViewModel = viewModel() + ValidatingInputTextField( + email = emailViewModel.email, + updateState = { input -> emailViewModel.updateEmail(input) }, + validatorHasErrors = emailViewModel.emailHasErrors + ) } // [END android_compose_text_auto_format_phone_number_validatetext] From 1cefa757def1948a471d49725934fae3b3d248af Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 27 Sep 2024 13:06:13 +0100 Subject: [PATCH 05/10] Migrate to use BasicSecureTextField. --- .../compose/snippets/text/TextSnippets.kt | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index aa554d173..4737d002f 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -32,7 +32,12 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicSecureTextField import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.TextFieldDecorator +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.TextObfuscationMode import androidx.compose.foundation.text.selection.DisableSelection import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons @@ -43,6 +48,7 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -861,24 +867,24 @@ val LightBlue = Color(0xFF0066FF) val Purple = Color(0xFF800080) // [START android_compose_text_auto_format_phone_number_showhidepassword] +@Preview @Composable fun PasswordTextField() { - var password by rememberSaveable { mutableStateOf("") } + val state = remember { TextFieldState() } var showPassword by remember { mutableStateOf(false) } - val passwordVisualTransformation = remember { PasswordVisualTransformation() } - - TextField( - value = password, - onValueChange = { password = it }, - label = { Text("Enter password") }, - visualTransformation = if (showPassword) { - VisualTransformation.None + BasicSecureTextField( + state = state, + textObfuscationMode = + if (showPassword) { + TextObfuscationMode.Visible } else { - passwordVisualTransformation + TextObfuscationMode.RevealLastTyped }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), - modifier = Modifier.fillMaxWidth(), - trailingIcon = { + modifier = Modifier.fillMaxWidth() + .padding(6.dp) + .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp)) + .padding(6.dp), + decorator = { Icon( if (showPassword) { Icons.Filled.Visibility From 774667fcdb98567fdd6ef8acd3f5f7cb1a894f74 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Fri, 27 Sep 2024 13:35:05 +0100 Subject: [PATCH 06/10] Updated to use BasicSecureTextField. --- .../compose/snippets/text/TextSnippets.kt | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index 4737d002f..4ef9ef7b5 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -29,8 +29,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicSecureTextField @@ -648,9 +650,9 @@ private fun TextSample(samples: Map Unit>) { private const val SAMPLE_LONG_TEXT = "Jetpack Compose is Android’s modern toolkit for building native UI. " + - "It simplifies and accelerates UI development on Android bringing your apps " + - "to life with less code, powerful tools, and intuitive Kotlin APIs. " + - "It makes building Android UI faster and easier." + "It simplifies and accelerates UI development on Android bringing your apps " + + "to life with less code, powerful tools, and intuitive Kotlin APIs. " + + "It makes building Android UI faster and easier." @Composable @Preview @@ -819,7 +821,7 @@ fun PhoneNumber() { // [END android_compose_text_auto_format_phone_number_textfieldconfig] // [START android_compose_text_auto_format_phone_number_transformtext] -class NanpVisualTransformation() : VisualTransformation { +class NanpVisualTransformation : VisualTransformation { override fun filter(text: AnnotatedString): TransformedText { val trimmed = if (text.text.length >= 10) text.text.substring(0..9) else text.text @@ -880,27 +882,37 @@ fun PasswordTextField() { } else { TextObfuscationMode.RevealLastTyped }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .padding(6.dp) .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp)) .padding(6.dp), - decorator = { - Icon( - if (showPassword) { - Icons.Filled.Visibility - } else { - Icons.Filled.VisibilityOff - }, - contentDescription = "Toggle password visibility", - modifier = Modifier.clickable { showPassword = !showPassword } - ) + decorator = { innerTextField -> + Box(modifier = Modifier.fillMaxWidth()) { + Box(modifier = Modifier + .align(Alignment.CenterStart) + .padding(start = 16.dp,end = 48.dp)) { + innerTextField() + } + Icon( + if (showPassword) { + Icons.Filled.Visibility + } else { + Icons.Filled.VisibilityOff + }, + contentDescription = "Toggle password visibility", + modifier = Modifier + .align(Alignment.CenterEnd) + .requiredSize(48.dp).padding(16.dp) + .clickable { showPassword = !showPassword } + ) + } } ) } // [END android_compose_text_auto_format_phone_number_showhidepassword] - // [START android_compose_text_auto_format_phone_number_validatetext] class EmailViewModel : ViewModel() { var email by mutableStateOf("") @@ -945,7 +957,7 @@ fun ValidatingInputTextField( @Preview @Composable fun ValidateInput() { - val emailViewModel : EmailViewModel = viewModel() + val emailViewModel: EmailViewModel = viewModel() ValidatingInputTextField( email = emailViewModel.email, updateState = { input -> emailViewModel.updateEmail(input) }, From 8f2116abc64858779b63adeeee9b01aafc3768cc Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 1 Oct 2024 12:49:39 +0100 Subject: [PATCH 07/10] Added clipping and faded edge examples --- .../graphics/GraphicsModifiersSnippets.kt | 465 ++++++++++++------ 1 file changed, 321 insertions(+), 144 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt index ef00faecc..4f94c9074 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt @@ -16,15 +16,26 @@ package com.example.compose.snippets.graphics +import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text @@ -42,21 +53,33 @@ import androidx.compose.ui.draw.drawWithCache import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.Path import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.drawscope.clipPath import androidx.compose.ui.graphics.drawscope.scale import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.compose.snippets.R +import kotlin.random.Random /* * Copyright 2022 The Android Open Source Project @@ -296,171 +319,167 @@ fun ModifierGraphicsLayerAlpha() { @Preview @Composable fun ModifierGraphicsLayerCompositingStrategy() { - /* Commented out until compositing Strategy is rolled out to production - // [START android_compose_graphics_modifiers_graphicsLayer_compositing_strategy] - - Image(painter = painterResource(id = R.drawable.dog), - contentDescription = "Dog", - contentScale = ContentScale.Crop, - modifier = Modifier - .size(120.dp) - .aspectRatio(1f) - .background( - Brush.linearGradient( - listOf( - Color(0xFFC5E1A5), - Color(0xFF80DEEA) - ) - ) - ) - .padding(8.dp) - .graphicsLayer { - compositingStrategy = CompositingStrategy.Offscreen - } - .drawWithCache { - val path = Path() - path.addOval( - Rect( - topLeft = Offset.Zero, - bottomRight = Offset(size.width, size.height) - ) - ) - onDrawWithContent { - clipPath(path) { - // this draws the actual image - if you don't call drawContent, it wont - // render anything - this@onDrawWithContent.drawContent() - } - val dotSize = size.width / 8f - // Clip a white border for the content - drawCircle( - Color.Black, - radius = dotSize, - center = Offset( - x = size.width - dotSize, - y = size.height - dotSize - ), - blendMode = BlendMode.Clear - ) - // draw the red circle indication - drawCircle( - Color(0xFFEF5350), radius = dotSize * 0.8f, - center = Offset( - x = size.width - dotSize, - y = size.height - dotSize - ) - ) - } - - } - ) - // [END android_compose_graphics_modifiers_graphicsLayer_compositing_strategy] - */ + // [START android_compose_graphics_modifiers_graphicsLayer_compositing_strategy] + + Image(painter = painterResource(id = R.drawable.dog), + contentDescription = "Dog", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(120.dp) + .aspectRatio(1f) + .background( + Brush.linearGradient( + listOf( + Color(0xFFC5E1A5), + Color(0xFF80DEEA) + ) + ) + ) + .padding(8.dp) + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithCache { + val path = Path() + path.addOval( + Rect( + topLeft = Offset.Zero, + bottomRight = Offset(size.width, size.height) + ) + ) + onDrawWithContent { + clipPath(path) { + // this draws the actual image - if you don't call drawContent, it wont + // render anything + this@onDrawWithContent.drawContent() + } + val dotSize = size.width / 8f + // Clip a white border for the content + drawCircle( + Color.Black, + radius = dotSize, + center = Offset( + x = size.width - dotSize, + y = size.height - dotSize + ), + blendMode = BlendMode.Clear + ) + // draw the red circle indication + drawCircle( + Color(0xFFEF5350), radius = dotSize * 0.8f, + center = Offset( + x = size.width - dotSize, + y = size.height - dotSize + ) + ) + } + + } + ) + // [END android_compose_graphics_modifiers_graphicsLayer_compositing_strategy] + } -/* Commented out until compositing Strategy is rolled out to production + @Preview // [START android_compose_graphics_modifier_compositing_strategy_differences] @Composable fun CompositingStrategyExamples() { - Column( - modifier = Modifier - .fillMaxSize() - .wrapContentSize(Alignment.Center) - ) { - /** Does not clip content even with a graphics layer usage here. By default, graphicsLayer - does not allocate + rasterize content into a separate layer but instead is used - for isolation. That is draw invalidations made outside of this graphicsLayer will not - re-record the drawing instructions in this composable as they have not changed **/ - Canvas( - modifier = Modifier - .graphicsLayer() - .size(100.dp) // Note size of 100 dp here - .border(2.dp, color = Color.Blue) - ) { - // ... and drawing a size of 200 dp here outside the bounds - drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx())) - } - - Spacer(modifier = Modifier.size(300.dp)) - - /** Clips content as alpha usage here creates an offscreen buffer to rasterize content - into first then draws to the original destination **/ - Canvas( - modifier = Modifier - // force to an offscreen buffer - .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) - .size(100.dp) // Note size of 100 dp here - .border(2.dp, color = Color.Blue) - ) { - /** ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the - content gets clipped **/ - drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx())) - } - } + Column( + modifier = Modifier + .fillMaxSize() + .wrapContentSize(Alignment.Center) + ) { + // Does not clip content even with a graphics layer usage here. By default, graphicsLayer + //does not allocate + rasterize content into a separate layer but instead is used + //for isolation. That is draw invalidations made outside of this graphicsLayer will not + // re-record the drawing instructions in this composable as they have not changed * + Canvas( + modifier = Modifier + .graphicsLayer() + .size(100.dp) // Note size of 100 dp here + .border(2.dp, color = Color.Blue) + ) { + // ... and drawing a size of 200 dp here outside the bounds + drawRect(color = Color.Magenta, size = Size(200.dp.toPx(), 200.dp.toPx())) + } + + Spacer(modifier = Modifier.size(300.dp)) + + /* Clips content as alpha usage here creates an offscreen buffer to rasterize content + into first then draws to the original destination */ + Canvas( + modifier = Modifier + // force to an offscreen buffer + .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) + .size(100.dp) // Note size of 100 dp here + .border(2.dp, color = Color.Blue) + ) { + /* ... and drawing a size of 200 dp. However, because of the CompositingStrategy.Offscreen usage above, the + content gets clipped */ + drawRect(color = Color.Red, size = Size(200.dp.toPx(), 200.dp.toPx())) + } + } } // [END android_compose_graphics_modifier_compositing_strategy_differences] - */ -/* Commented out until compositing Strategy is rolled out to production // [START android_compose_graphics_modifier_compositing_strategy_modulate_alpha] @Preview @Composable -fun CompositingStratgey_ModulateAlpha() { - Column( - modifier = Modifier - .fillMaxSize() - .padding(32.dp) - ) { - // Base drawing, no alpha applied - Canvas( - modifier = Modifier.size(200.dp) - ) { - drawSquares() - } - - Spacer(modifier = Modifier.size(36.dp)) - - // Alpha 0.5f applied to whole composable - Canvas(modifier = Modifier - .size(200.dp) - .graphicsLayer { - alpha = 0.5f - }) { - drawSquares() - } - Spacer(modifier = Modifier.size(36.dp)) - - // 0.75f alpha applied to each draw call when using ModulateAlpha - Canvas(modifier = Modifier - .size(200.dp) - .graphicsLayer { - compositingStrategy = CompositingStrategy.ModulateAlpha - alpha = 0.75f - }) { - drawSquares() - } - } +fun CompositingStrategy_ModulateAlpha() { + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp) + ) { + // Base drawing, no alpha applied + Canvas( + modifier = Modifier.size(200.dp) + ) { + drawSquares() + } + + Spacer(modifier = Modifier.size(36.dp)) + + // Alpha 0.5f applied to whole composable + Canvas(modifier = Modifier + .size(200.dp) + .graphicsLayer { + alpha = 0.5f + }) { + drawSquares() + } + Spacer(modifier = Modifier.size(36.dp)) + + // 0.75f alpha applied to each draw call when using ModulateAlpha + Canvas(modifier = Modifier + .size(200.dp) + .graphicsLayer { + compositingStrategy = CompositingStrategy.ModulateAlpha + alpha = 0.75f + }) { + drawSquares() + } + } } private fun DrawScope.drawSquares() { - val size = Size(100.dp.toPx(), 100.dp.toPx()) - drawRect(color = Red, size = size) - drawRect( - color = Purple, size = size, - topLeft = Offset(size.width / 4f, size.height / 4f) - ) - drawRect( - color = Yellow, size = size, - topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f) - ) + val size = Size(100.dp.toPx(), 100.dp.toPx()) + drawRect(color = Red, size = size) + drawRect( + color = Purple, size = size, + topLeft = Offset(size.width / 4f, size.height / 4f) + ) + drawRect( + color = Yellow, size = size, + topLeft = Offset(size.width / 4f * 2f, size.height / 4f * 2f) + ) } val Purple = Color(0xFF7E57C2) val Yellow = Color(0xFFFFCA28) val Red = Color(0xFFEF5350) // [END android_compose_graphics_modifier_compositing_strategy_modulate_alpha] -*/ // [START android_compose_graphics_modifier_flipped] class FlippedModifier : DrawModifier { @@ -485,3 +504,161 @@ fun ModifierGraphicsFlippedUsage() { ) // [END android_compose_graphics_modifier_flipped_usage] } + +// [START android_compose_graphics_faded_edge_example] +@Composable +fun FadedEdgeBox(modifier: Modifier = Modifier, content: @Composable () -> Unit) { + Box(modifier = modifier + .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) + .drawWithContent { + drawContent() + drawRect( + brush = Brush.verticalGradient( + listOf(Color.Black, Color.Transparent) + ), + blendMode = BlendMode.DstIn + ) + }) { + content() + } +} + +@Preview +@Composable +private fun FadingLazyComments() { + FadedEdgeBox( + modifier = Modifier + .padding(32.dp) + .height(300.dp) + .fillMaxWidth() + ) { + LazyColumn { + items(listComments, key = { it.key }) { + Row(modifier = Modifier.padding(bottom = 8.dp)) { + val strokeWidthPx = with(LocalDensity.current) { + 2.dp.toPx() + } + Avatar(strokeWidth = strokeWidthPx, modifier = Modifier.size(48.dp)) { + Image( + painter = painterResource(id = it.avatar), + contentScale = ContentScale.Crop, + contentDescription = null + ) + } + Spacer(Modifier.width(6.dp)) + Text( + it.text, + fontSize = 20.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + item { + Spacer(Modifier.height(100.dp)) + } + } + } +} + +// [END android_compose_graphics_faded_edge_example] +data class Comment( + val avatar: Int, + val text: String, + val key: Int = Random.nextInt() +) + +val listComments = listOf( + Comment(R.drawable.dog, "Woof 🐶"), + Comment(R.drawable.froyo, "I love ice cream..."), + Comment(R.drawable.donut, "Mmmm delicious"), + Comment(R.drawable.cupcake, "I love cupcakes"), + Comment(R.drawable.gingerbread, "🍪🍪❤️"), + Comment(R.drawable.eclair, "Where do I get the recipe?"), + Comment(R.drawable.froyo, "🍦The ice cream is BEST"), +) + +// [START android_compose_graphics_stacked_clipped_avatars] +@Composable +fun Avatar( + strokeWidth: Float, + modifier: Modifier = Modifier, + content: @Composable () -> Unit +) { + val stroke = remember(strokeWidth) { + Stroke(width = strokeWidth) + } + Box(modifier = modifier + .drawWithContent { + drawContent() + drawCircle( + Color.Black, + size.minDimension / 2, + size.center, + style = stroke, + blendMode = BlendMode.Clear + ) + } + .clip(CircleShape) + ) { + content() + } +} + +@Preview +@Composable +private fun StackedAvatars() { + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.linearGradient( + colors = listOf( + Color.Magenta.copy(alpha = 0.5f), + Color.Blue.copy(alpha = 0.5f) + ) + ) + ) + ) { + val size = 80.dp + val strokeWidth = 2.dp + val strokeWidthPx = with(LocalDensity.current) { + strokeWidth.toPx() + } + val sizeModifier = Modifier.size(size) + val avatars = listOf( + R.drawable.cupcake, + R.drawable.donut, + R.drawable.eclair, + R.drawable.froyo, + R.drawable.gingerbread, + R.drawable.dog + ) + val width = ((size / 2) + strokeWidth * 2) * (avatars.size + 1) + Box( + modifier = Modifier + .size(width, size) + .graphicsLayer { + // Use an offscreen buffer as underdraw protection when + // using blendmodes that clear destination pixels + compositingStrategy = CompositingStrategy.Offscreen + } + .align(Alignment.Center), + ) { + var offset = 0.dp + for (avatar in avatars) { + Avatar( + strokeWidth = strokeWidthPx, + modifier = sizeModifier.offset(offset) + ) { + Image( + painter = painterResource(id = avatar), + contentScale = ContentScale.Crop, + contentDescription = null + ) + } + offset += size / 2 + } + } + } +} +// [END android_compose_graphics_stacked_clipped_avatars] \ No newline at end of file From 962f3e99ddc2fe75e17675699d892507be679379 Mon Sep 17 00:00:00 2001 From: riggaroo Date: Tue, 1 Oct 2024 12:02:44 +0000 Subject: [PATCH 08/10] Apply Spotless --- .../graphics/GraphicsModifiersSnippets.kt | 86 ++++++++++--------- .../compose/snippets/text/TextSnippets.kt | 21 ++--- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt index 4f94c9074..eb08352fe 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt @@ -321,7 +321,8 @@ fun ModifierGraphicsLayerAlpha() { fun ModifierGraphicsLayerCompositingStrategy() { // [START android_compose_graphics_modifiers_graphicsLayer_compositing_strategy] - Image(painter = painterResource(id = R.drawable.dog), + Image( + painter = painterResource(id = R.drawable.dog), contentDescription = "Dog", contentScale = ContentScale.Crop, modifier = Modifier @@ -373,11 +374,9 @@ fun ModifierGraphicsLayerCompositingStrategy() { ) ) } - } ) // [END android_compose_graphics_modifiers_graphicsLayer_compositing_strategy] - } @Preview @@ -390,8 +389,8 @@ fun CompositingStrategyExamples() { .wrapContentSize(Alignment.Center) ) { // Does not clip content even with a graphics layer usage here. By default, graphicsLayer - //does not allocate + rasterize content into a separate layer but instead is used - //for isolation. That is draw invalidations made outside of this graphicsLayer will not + // does not allocate + rasterize content into a separate layer but instead is used + // for isolation. That is draw invalidations made outside of this graphicsLayer will not // re-record the drawing instructions in this composable as they have not changed * Canvas( modifier = Modifier @@ -441,22 +440,26 @@ fun CompositingStrategy_ModulateAlpha() { Spacer(modifier = Modifier.size(36.dp)) // Alpha 0.5f applied to whole composable - Canvas(modifier = Modifier - .size(200.dp) - .graphicsLayer { - alpha = 0.5f - }) { + Canvas( + modifier = Modifier + .size(200.dp) + .graphicsLayer { + alpha = 0.5f + } + ) { drawSquares() } Spacer(modifier = Modifier.size(36.dp)) // 0.75f alpha applied to each draw call when using ModulateAlpha - Canvas(modifier = Modifier - .size(200.dp) - .graphicsLayer { - compositingStrategy = CompositingStrategy.ModulateAlpha - alpha = 0.75f - }) { + Canvas( + modifier = Modifier + .size(200.dp) + .graphicsLayer { + compositingStrategy = CompositingStrategy.ModulateAlpha + alpha = 0.75f + } + ) { drawSquares() } } @@ -508,17 +511,19 @@ fun ModifierGraphicsFlippedUsage() { // [START android_compose_graphics_faded_edge_example] @Composable fun FadedEdgeBox(modifier: Modifier = Modifier, content: @Composable () -> Unit) { - Box(modifier = modifier - .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) - .drawWithContent { - drawContent() - drawRect( - brush = Brush.verticalGradient( - listOf(Color.Black, Color.Transparent) - ), - blendMode = BlendMode.DstIn - ) - }) { + Box( + modifier = modifier + .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) + .drawWithContent { + drawContent() + drawRect( + brush = Brush.verticalGradient( + listOf(Color.Black, Color.Transparent) + ), + blendMode = BlendMode.DstIn + ) + } + ) { content() } } @@ -587,18 +592,19 @@ fun Avatar( val stroke = remember(strokeWidth) { Stroke(width = strokeWidth) } - Box(modifier = modifier - .drawWithContent { - drawContent() - drawCircle( - Color.Black, - size.minDimension / 2, - size.center, - style = stroke, - blendMode = BlendMode.Clear - ) - } - .clip(CircleShape) + Box( + modifier = modifier + .drawWithContent { + drawContent() + drawCircle( + Color.Black, + size.minDimension / 2, + size.center, + style = stroke, + blendMode = BlendMode.Clear + ) + } + .clip(CircleShape) ) { content() } @@ -661,4 +667,4 @@ private fun StackedAvatars() { } } } -// [END android_compose_graphics_stacked_clipped_avatars] \ No newline at end of file +// [END android_compose_graphics_stacked_clipped_avatars] diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt index 277bc2bb9..5973733a5 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/text/TextSnippets.kt @@ -19,7 +19,6 @@ package com.example.compose.snippets.text import android.graphics.Typeface -import android.provider.ContactsContract.CommonDataKinds.Email import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee @@ -29,7 +28,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.requiredSize @@ -37,7 +35,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicSecureTextField import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.input.TextFieldDecorator import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.TextObfuscationMode import androidx.compose.foundation.text.selection.DisableSelection @@ -50,7 +47,6 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -650,9 +646,9 @@ private fun TextSample(samples: Map Unit>) { private const val SAMPLE_LONG_TEXT = "Jetpack Compose is Android’s modern toolkit for building native UI. " + - "It simplifies and accelerates UI development on Android bringing your apps " + - "to life with less code, powerful tools, and intuitive Kotlin APIs. " + - "It makes building Android UI faster and easier." + "It simplifies and accelerates UI development on Android bringing your apps " + + "to life with less code, powerful tools, and intuitive Kotlin APIs. " + + "It makes building Android UI faster and easier." @Composable @Preview @@ -888,9 +884,11 @@ fun PasswordTextField() { .padding(6.dp), decorator = { innerTextField -> Box(modifier = Modifier.fillMaxWidth()) { - Box(modifier = Modifier - .align(Alignment.CenterStart) - .padding(start = 16.dp,end = 48.dp)) { + Box( + modifier = Modifier + .align(Alignment.CenterStart) + .padding(start = 16.dp, end = 48.dp) + ) { innerTextField() } Icon( @@ -911,7 +909,6 @@ fun PasswordTextField() { } // [END android_compose_text_showhidepassword] - // [START android_compose_text_auto_format_phone_number_validatetext] class EmailViewModel : ViewModel() { var email by mutableStateOf("") @@ -963,4 +960,4 @@ fun ValidateInput() { validatorHasErrors = emailViewModel.emailHasErrors ) } -// [END android_compose_text_auto_format_phone_number_validatetext] \ No newline at end of file +// [END android_compose_text_auto_format_phone_number_validatetext] From 5e56c4baf01243d2e1f2af2304dfc31a02be75e8 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 1 Oct 2024 16:34:10 +0100 Subject: [PATCH 09/10] Clean up snippet --- .../graphics/GraphicsModifiersSnippets.kt | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt index eb08352fe..790d46c9a 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt @@ -527,7 +527,7 @@ fun FadedEdgeBox(modifier: Modifier = Modifier, content: @Composable () -> Unit) content() } } - +// [END android_compose_graphics_faded_edge_example] @Preview @Composable private fun FadingLazyComments() { @@ -539,24 +539,7 @@ private fun FadingLazyComments() { ) { LazyColumn { items(listComments, key = { it.key }) { - Row(modifier = Modifier.padding(bottom = 8.dp)) { - val strokeWidthPx = with(LocalDensity.current) { - 2.dp.toPx() - } - Avatar(strokeWidth = strokeWidthPx, modifier = Modifier.size(48.dp)) { - Image( - painter = painterResource(id = it.avatar), - contentScale = ContentScale.Crop, - contentDescription = null - ) - } - Spacer(Modifier.width(6.dp)) - Text( - it.text, - fontSize = 20.sp, - modifier = Modifier.align(Alignment.CenterVertically) - ) - } + ListCommentItem(it) } item { Spacer(Modifier.height(100.dp)) @@ -565,7 +548,28 @@ private fun FadingLazyComments() { } } -// [END android_compose_graphics_faded_edge_example] +@Composable +private fun ListCommentItem(it: Comment) { + Row(modifier = Modifier.padding(bottom = 8.dp)) { + val strokeWidthPx = with(LocalDensity.current) { + 2.dp.toPx() + } + Avatar(strokeWidth = strokeWidthPx, modifier = Modifier.size(48.dp)) { + Image( + painter = painterResource(id = it.avatar), + contentScale = ContentScale.Crop, + contentDescription = null + ) + } + Spacer(Modifier.width(6.dp)) + Text( + it.text, + fontSize = 20.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } +} + data class Comment( val avatar: Int, val text: String, From fff76ce3c8d0004cd6e8992a7d79d342af13a436 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 1 Oct 2024 16:34:44 +0100 Subject: [PATCH 10/10] Clean up snippet --- .../compose/snippets/graphics/GraphicsModifiersSnippets.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt index 790d46c9a..0493012ed 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/graphics/GraphicsModifiersSnippets.kt @@ -391,7 +391,7 @@ fun CompositingStrategyExamples() { // Does not clip content even with a graphics layer usage here. By default, graphicsLayer // does not allocate + rasterize content into a separate layer but instead is used // for isolation. That is draw invalidations made outside of this graphicsLayer will not - // re-record the drawing instructions in this composable as they have not changed * + // re-record the drawing instructions in this composable as they have not changed Canvas( modifier = Modifier .graphicsLayer()