How contentColor is applied to child in surface in jetpack compose - android

How contentColor affects the childs of Surface internally in Jetpack compose for example in this example the text color will be cyan.

Let's look at the Surface code:
#Composable
private fun Surface(
modifier: Modifier,
shape: Shape,
color: Color,
contentColor: Color,
border: BorderStroke?,
elevation: Dp,
clickAndSemanticsModifier: Modifier,
content: #Composable () -> Unit
) {
val elevationOverlay = LocalElevationOverlay.current
val absoluteElevation = LocalAbsoluteElevation.current + elevation
val backgroundColor = if (color == MaterialTheme.colors.surface && elevationOverlay != null) {
elevationOverlay.apply(color, absoluteElevation)
} else {
color
}
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalAbsoluteElevation provides absoluteElevation
) {
Box(
modifier
.shadow(elevation, shape, clip = false)
.then(if (border != null) Modifier.border(border, shape) else Modifier)
.background(
color = backgroundColor,
shape = shape
)
.clip(shape)
.then(clickAndSemanticsModifier),
propagateMinConstraints = true
) {
content()
}
}
}
You can see here that Surface is setting up the CompositionLocalProvider for value LocalContentColor. A lot of Material composables are using LocalContentColor as a default value of their color, in your example Text:
#Composable
fun Text(
text: AnnotatedString,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
) {
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
}
val mergedStyle = style.merge(
TextStyle(
color = textColor,
fontSize = fontSize,
fontWeight = fontWeight,
textAlign = textAlign,
lineHeight = lineHeight,
fontFamily = fontFamily,
textDecoration = textDecoration,
fontStyle = fontStyle,
letterSpacing = letterSpacing
)
)
BasicText(
text,
modifier,
mergedStyle,
onTextLayout,
overflow,
softWrap,
maxLines,
inlineContent
)
}
And especially this part:
val textColor = color.takeOrElse {
style.color.takeOrElse {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
}
Here you can see that Text is trying to get color defined as a parameter. If it's Unspecified it tries to get one from the TextStyle. If there is no color in there - Text is using value from LocalContentColor.current (mixed with LocalContentAlpha, but that's topic for another question).

Related

How to change border width of a particular side only in jetpack compose?

I am trying to achieve the below layout
enter image description here
There is no way to set individual border width in jetpack compose
This is my code
Row(
modifier = Modifier
.border(width = 1.dp, shape = RoundedCornerShape(4.dp), color = Purple3)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.info),
contentDescription = stringResource(id = R.string.long_press_an_item),
)
Text(
text = stringResource(id = R.string.long_press_an_item),
fontFamily = FontFamily(Font(R.font.inter_medium)),
fontSize = 12.sp,
color = Gray5,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
I used different colors, but you can see the result:
I see there three ways, how you can achieve this effect:
You can create custom modifier extension method, as I did in one my project, using drawBehind Modifier:
#Preview
#Composable
fun Preview() {
Row(
modifier = Modifier
.someCustomOutline(
outlineColor = MaterialTheme.colors.primary,
surfaceColor = MaterialTheme.colors.surface,
startOffset = 3.dp,
outlineWidth = 1.dp,
radius = 4.dp
)
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
imageVector = Icons.Default.Info,
contentDescription = null,
)
Text(
text = "long_press_an_item",
fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
#Composable
fun Modifier.someCustomOutline(
outlineColor: Color,
surfaceColor: Color,
startOffset: Dp,
outlineWidth: Dp,
radius: Dp = 1.dp
) = drawBehind {
val startOffsetPx = startOffset.toPx()
val outlineWidthPx = outlineWidth.toPx()
val radiusPx = radius.toPx()
drawRoundRect(
color = outlineColor,
topLeft = Offset(0f, 0f),
size = size,
cornerRadius = CornerRadius(radiusPx, radiusPx),
style = Fill
)
drawRoundRect(
color = surfaceColor,
topLeft = Offset(startOffsetPx, outlineWidthPx),
size = Size(size.width - startOffsetPx - outlineWidthPx, size.height - outlineWidthPx * 2),
cornerRadius = CornerRadius(radiusPx - outlineWidthPx, radiusPx - outlineWidthPx),
style = Fill
)
}
Usage:
this is very bad approach, but I will mention it for just in case
You can just frap your element with Box and add padding to it:
Box(
modifier = Modifier
.background(shape = RoundedCornerShape(4.dp), color = Purple3)
.padding(start = 3.dp, top = 1.dp, end = 1.dp, bottom = 1.dp)
) {
Row(
modifier = Modifier
.background(MaterialTheme.colors.surface, shape = RoundedCornerShape(3.dp))
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
imageVector = painterResource(id = R.drawable.info),
contentDescription = stringResource(id = R.string.long_press_an_item),
)
Text(
text = stringResource(id = R.string.long_press_an_item),
fontSize = 12.sp,
fontFamily = FontFamily(Font(R.font.inter_medium)),
color = Gray5,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
This can be done using a Box with background as your border and adding padding more padding to start than other corners
#Preview
#Composable
private fun Test() {
Box(modifier = Modifier.background(Color(0xffBA68C8), RoundedCornerShape(4.dp))) {
Row(
modifier = Modifier
.padding(start = 4.dp, top = 1.dp, bottom = 1.dp, end = 1.dp)
.background(Color.White, RoundedCornerShape(4.dp))
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
imageVector = Icons.Default.Info,
contentDescription = "Long press item to enable multi-select and bulk operations"
)
Text(
text = "Long Press on Item",
fontSize = 12.sp,
color = Color.Gray,
modifier = Modifier.padding(horizontal = 10.dp)
)
}
}
}
Container(
decoration: BoxDecoration(
gradient: LinearGradient(stops: const [
0.014,
0.014
], colors: [
isError ? redColor : greenColor,
Theme.of(context).colorScheme.background
]),
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: isError ? redColor : greenColor, width: 1),
),
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 12,
),
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
text,
style: GoogleFonts.inter(
fontWeight: FontWeight.w500,
fontSize: 12,
color: gray6Color,
),
),
)
],
)

How to align leadingIcon in OutlinedTextField of jetpack compose with the value/placeholder

I am new to jetpack compose, I want my app to load the Exo two light font so I override the material typography like this,
val exoTwoLight = FontFamily(
Font(R.font.exo_two_light),
Font(R.font.exo_two_light, FontWeight.Bold)
)
val exoTwoLightTypography = Typography(
h1 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Bold,
fontSize = 96.sp
),
h2 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Bold,
fontSize = 60.sp
),
h3 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Bold,
fontSize = 48.sp
),
h4 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Bold,
fontSize = 34.sp
),
h5 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
),
h6 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Bold,
fontSize = 12.sp
),
subtitle1 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
subtitle2 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Medium,
fontSize = 14.sp
),
body1 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
body2 = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Normal,
fontSize = 14.sp
),
button = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Medium,
fontSize = 14.sp,
),
caption = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Normal,
fontSize = 12.sp,
),
overline = TextStyle(
fontFamily = exoTwoLight,
fontWeight = FontWeight.Normal,
fontSize = 10.sp,
)
)
Now when I try to load an input field in the app I can see that placeholder and leading icons are not aligned at all
I tried to customize the placeholder as it is required as a composable
By customization, I mean adding top padding of 2/4 dp to the placeholder
Row(modifier = Modifier.padding(top=10.dp)) {
OutlinedTextField(
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.gallery_icon),
contentDescription = "",
tint = colorResource(R.color.teal_200),
modifier = Modifier.clickable {
}
)
},
placeholder = {
Text(
text = "Placeholder",
style = MaterialTheme.typography.button,
modifier = Modifier.padding(top = 3.dp)
)
},
modifier = Modifier.fillMaxWidth(),
readOnly = false,
value = sampleValue,
onValueChange = {
sampleValue = it
},
maxLines = 1,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
)
}
It only solves the issue for the placeholder but values seem to be misaligned
This is the complete code
var sampleValue by remember {
mutableStateOf("")
}
Column{
Row(modifier = Modifier.padding(top=10.dp)){
OutlinedTextField(
leadingIcon = {
if (true) {
Icon(
painter = painterResource(id = R.drawable.capture_more_images),
contentDescription = "",
tint = colorResource(R.color.teal_200),
modifier = Modifier.clickable {
}
)
} else {
Icon(
painter = painterResource(id = R.drawable.capture_more_images),
contentDescription = "",
tint = colorResource(R.color.disabled)
)
}
},
placeholder = {
Row(
modifier = Modifier.height(24.dp),
verticalAlignment = Alignment.CenterVertically
){
Text(
text = "Placeholder",
style = MaterialTheme.typography.button
)
}
},
modifier = Modifier.fillMaxWidth(),
readOnly = false,
value = sampleValue,
onValueChange = {
sampleValue = it
},
maxLines = 1,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
)
}
Row(modifier = Modifier.padding(top=10.dp)) {
OutlinedTextField(
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.gallery_icon),
contentDescription = "",
tint = colorResource(R.color.teal_200),
modifier = Modifier.clickable {
}
)
},
placeholder = {
Text(
text = "Placeholder",
style = MaterialTheme.typography.button,
modifier = Modifier.padding(top = 3.dp)
)
},
modifier = Modifier.fillMaxWidth(),
readOnly = false,
value = sampleValue,
onValueChange = {
sampleValue = it
},
maxLines = 1,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
)
}
Row(modifier = Modifier.padding(top=10.dp)) {
OutlinedTextField(
placeholder = {
Text(
text = "Placeholder",
style = MaterialTheme.typography.button,
)
},
modifier = Modifier.fillMaxWidth(),
value = sampleValue,
onValueChange = {
sampleValue = it
}
)
}
}
I want that placeholder to be aligned vertically centered ( without giving any top padding ) with the leading icon what is the solution?
The solution to this is to set the singleLine property of the TextField to true. Like this:
TextField(
singleLine = true,
placeholder = {
Text(text = "Search for reminders")
},
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.search_icon),
tint = MaterialTheme.colorScheme.outline,
contentDescription = "Back button"
)
}
)
Many icons have built-in padding on one or more sides. To compensate for this, you can adjust the icon's position using the offset modifier. Set it some positive or negative value:
placeholder = {
Row(
modifier = Modifier.height(24.dp).offset(y = 5.dp),
verticalAlignment = Alignment.CenterVertically
){
Text(
text = "Placeholder",
style = MaterialTheme.typography.button
)
}
}

Jetpack Compose – Why aren't buttonColors taking effect?

I've defined my button composable like this:
#Composable
fun PrimaryButton(modifier: Modifier = Modifier, onClick: Callback, content: #Composable RowScope.() -> Unit) {
val buttonColors = ButtonDefaults.buttonColors(
backgroundColor = MaterialTheme.colors.primary,
contentColor = contentColorFor(backgroundColor = MaterialTheme.colors.primary)
)
Button(modifier=modifier, onClick = onClick, content=content, colors=buttonColors)
}
I've checked with the debugger and contentColorFor returns the appropiate color (white) (0xFFFFFFFF)
But I can't get to have the text on the button to be white.
Here's the preview
#Preview(name="Buttons")
#Composable
fun PrimaryButtonPreview() {
MyAppTheme {
Row {
PrimaryButton(modifier = Modifier, onClick={}) {
Text(text = "Sample")
}
}
}
}
And the result:
Setting the button = TextStyle(…) results in a change on the text color but I'd like to set it from the button colors rather than having one unique color for the text
Edit
After setting the button's text style color to unspecified it still doesn't work:
Shapes are the default by the Android Studio project generator
Colors
val mainBrown = Color(0xFFB4A996)
val primaryBrown = Color(0xFFC2A686)
val clearBrown = Color(0xFFAE967A)
val white = Color.White
val smokeWhite = Color(0xFFF3F3F3)
val gray = Color(0xFF9AA5AF)
val lightGray =Color(0xFFF1F1EF)
val darkGray = Color(0xFF4D5151)
val black = Color(0xFF4D4646)
val red = Color(248,113,113)
Theme
private val LightColorPalette = darkColors(
primary = primaryBrown,
onPrimary = white,
primaryVariant = primaryBrown,
secondary = white,
onSecondary = primaryBrown,
secondaryVariant = white,
background = darkGray,
surface = lightGray,
onSurface = black,
error = red,
onError = smokeWhite,
)
#Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit
) {
MaterialTheme(
colors = LightColorPalette,
typography = typography,
shapes = Shapes,
content = content
)
}
Typography
val typography = Typography(
h1 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.W700,
fontSize = 1.5f.rem,
lineHeight = 2.rem,
),
h2 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.W500,
fontSize = 1.25f.rem,
lineHeight = 1.75f.rem
),
h3 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.Normal,
fontSize = 1.5f.rem,
lineHeight = 2.rem
),
h4 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.Normal,
fontSize = 1.25f.rem,
lineHeight = 1.75f.rem
),
body1 = TextStyle(
color = black,
fontSize = 1.rem,
lineHeight = 1.25f.rem
),
button = TextStyle(
fontSize = 1.rem,
color = Color.Unspecified,
lineHeight = 1.25f.rem
),
defaultFontFamily = ralewayRegular
)
My setup was fine except for the typography.
I was setting the color every time so that takes over how it should look from the MaterialTheme.
Don't set those values and let the framework handle it for you.
Not working because I am setting (and forcing) mainBrown as the color
val typography = Typography(
h1 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.W700,
fontSize = 1.5f.rem,
lineHeight = 2.rem,
),
h2 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.W500,
fontSize = 1.25f.rem,
lineHeight = 1.75f.rem
),
h3 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.Normal,
fontSize = 1.5f.rem,
lineHeight = 2.rem
),
h4 = TextStyle(
color = mainBrown,
fontWeight = FontWeight.Normal,
fontSize = 1.25f.rem,
lineHeight = 1.75f.rem
),
body1 = TextStyle(
color = black,
fontSize = 1.rem,
lineHeight = 1.25f.rem
),
button = TextStyle(
fontSize = 1.rem,
color = Color.Unspecified,
lineHeight = 1.25f.rem
),
defaultFontFamily = ralewayRegular
)
Working
val typography = Typography(
h1 = TextStyle(
fontWeight = FontWeight.W700,
fontSize = 1.5f.rem,
lineHeight = 2.rem,
),
h2 = TextStyle(
fontWeight = FontWeight.W500,
fontSize = 1.25f.rem,
lineHeight = 1.75f.rem
),
h3 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 1.5f.rem,
lineHeight = 2.rem
),
h4 = TextStyle(
fontWeight = FontWeight.Normal,
fontSize = 1.25f.rem,
lineHeight = 1.75f.rem
),
body1 = TextStyle(
fontSize = 1.rem,
lineHeight = 1.25f.rem
),
button = TextStyle(
fontSize = 1.rem,
color = Color.Unspecified,
lineHeight = 1.25f.rem
),
defaultFontFamily = ralewayRegular
)

How to draw a border around multiline text in compose

I want to draw a border around a Text like this,
Text("Box around text",
modifier = Modifier
.padding(top = 8.dp)
.border(width = 2.dp, color = Color.Red)
.background(Color.DarkGray))
Text("Box around text with a very very very very longlonglonglongword",
modifier = Modifier
.padding(top = 8.dp)
.border(width = 2.dp, color = Color.Red)
.background(Color.DarkGray)
)
But in the case of a multiline text, it doesn't look well.
There is a gap on the right between the border and the text.
So how to draw a border around a multiline text, so that it fits the text width?
It looks like a bug, I've created this issue.
Here's how you can restrict it manually:
#Composable
fun WrapTextContent(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
) {
SubcomposeLayout(modifier) { constraints ->
val composable = #Composable { localOnTextLayout: (TextLayoutResult) -> Unit ->
Text(
text = text,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = localOnTextLayout,
style = style,
)
}
var textWidthOpt: Int? = null
subcompose("measureView") {
composable { layoutResult ->
textWidthOpt = (0 until layoutResult.lineCount)
.maxOf { line ->
ceil(layoutResult.getLineRight(line) - layoutResult.getLineLeft(line)).toInt()
}
}
}[0].measure(constraints)
val textWidth = textWidthOpt!!
val placeable = subcompose("content") {
composable(onTextLayout)
}[0].measure(constraints.copy(minWidth = textWidth, maxWidth = textWidth))
layout(width = textWidth, height = placeable.height) {
placeable.place(0, 0)
}
}
}
Usage:
var textWidth by remember { mutableStateOf<Int?>(null) }
WrapTextContent(
"Box around text with a very very very very longlonglonglongword",
color = Color.White,
modifier = Modifier
.border(width = 2.dp, color = Color.Red)
.background(Color.DarkGray)
)
Result:

ColorTrackText in compose

I want to write this type of text ,is this possible?
Using Modifier.drawWithContent and clipRect you can draw only part of the contents of the view. Then you need to put two identical Text in a Box and draw the desired part of each view:
#Composable
fun TwoColorText(
text: String,
color1: Color,
color2: Color,
part: Float,
modifier: Modifier = Modifier,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
) {
val textView = #Composable { color: Color, leftMultiplier: Float, rightMultiplier: Float ->
Text(
text,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
onTextLayout = onTextLayout,
style = style,
modifier = Modifier.drawWithContent {
clipRect(
left = size.width * leftMultiplier,
right = size.width * rightMultiplier
) {
this#drawWithContent.drawContent()
}
}
)
}
Box(modifier) {
textView(color1, 0f, part)
textView(color2, part, 1f)
}
}
Usage:
Row(Modifier.padding(10.dp)) {
TwoColorText(
"新鲜",
color1 = Color.Black,
color2 = Color.Red,
part = 0.27f,
)
Spacer(modifier = Modifier.width(50.dp))
TwoColorText(
"消息",
color1 = Color.Black,
color2 = Color.Red,
part = 0.27f,
)
}
Result:

Categories

Resources