What is the SpanStyle equivalent of RelativeSizeSpan? - android

In Compose, we use AnnotatedString as a replacement for Spanned. However, I cant seem to find a way to replicate the behaviour of RelativeSizeSpan with a SpanStyle.
The relevant options I can see for SpanStyle are:
fontSize: TextUnit - not useful because this only accepts absolute text sizes, and I need my span style to scale the original font size by some factor
textGeometricTransform: TextGeometricTransform - not useful because TextGeometricTransform only performs X transformations, and I need the text to be scaled in both X and Y.
Can anyone share some insight?

You can achieve it with SpanStyle, but you need to use the em TextUnit, which is a relative font size unit. It means that 1em is equal to the current font size and 2em is a font two times bigger.
Here is the code demonstrating how it behaves on two Texts with different font size:
val annotatedString = buildAnnotatedString {
append("This is a ")
withStyle(style = SpanStyle(fontSize = 2.em)) {
append("big")
}
append(" text")
}
Column {
Text(annotatedString, fontSize = 20.sp)
Text(annotatedString, fontSize = 40.sp)
}
The word big is 2 times bigger than other words in the same Text.
You can also see that it makes the big word from first Text the same size (2 * 20sp) as the other words in the second Text (40sp).

Related

Create color value dynamically

My app shows buttons depending on logged user.
Details and Conditions of the app:
The number of buttons that will be shown is unknown (could be 1, 20, 100, etc.)
A different color must be set for each button
I want some control over color values, as text is always white, so text must always be readable
How can I create a dynamic color value under these conditions?
First you need to decide how "readable" the text should be. WCAG 2.1 is a common standard for accessibility requirements, and its minimum contrast requirement is 4.5:1. (The spec definition is here, or for a lighter overview with some nice examples there's this.)
That amount of contrast will guarantee your text can be read by people with "moderately low vision". 3:1 is recommended for "standard text and standard vision", but I'd always recommend going with the accessible ratio - especially since you're using white and random colours, which will vary in readability quite a bit!
WCAG 2.1 also allows for that 3:1 ratio for large-scale text, which is 18pt or 14pt bold. That works out to about 40dp for regular text and 31dp for bold. Depends on the font too, and also since you often use sp instead the user can control how big the fonts are, so it complicates things. But basically, big text = lower contrast requirements
Now you have your contrast level, you can check whether your colour combo meets it or not. There's a nice tool in ColorUtils that does this for you - it uses the WCAG formula for calculating contrast:
fun meetsMinContrast(#ColorInt foreground: Int, #ColorInt background: Int): Boolean {
val minContrast = 4.5
val actual = ColorUtils.calculateContrast(foreground, background)
return actual >= minContrast
}
As for actually generating colours, I don't know the "smart" way to do it, if there is one - you could possibly generate a colour space of valid colours when paired with white, and pick from that, but I don't really know anything about it - maybe someone else can chime in with a better solution!
So for a purely naive random approach:
val colours = generateSequence {
Color.valueOf(
Random.nextInt(0, 255),
Random.nextInt(0, 255),
Random.nextInt(0, 255)
)
}
val accessibleBackgrounds = colours.filter { background ->
meetsMinContrast(Color.WHITE, background)
}
and then you have a stream of valid, random colours you can set on your buttons.
If you don't like the "just keep generating randomly until you hit one that works" approach (which is pretty hacky and could be slow if you're unlucky), you could work with HSV instead:
fun getAccessibleBackground(): Color {
val hsv = floatArrayOf(
Random.nextFloat() * 360f,
Random.nextFloat(),
Random.nextFloat()
)
var colour: Color
while(true) {
colour = Color.HSVtoColor(hsv)
if (meetsMinContrast(Color.WHITE, colour)) return colour
// darken the colour a bit (subtract 1% value) and try again
hsv[2] -= 0.01f
}
}
(I'd be more explicit about error checking and providing a fallback there, especially if you made it work with a foreground colour parameter, but that should always return a valid colour before you need to worry about subtracting from value too much - it's just a simple example)

Jetpack Compose: multiline Text in Row with weight and wrapping text content

I have a Row layout with two Composables inside:
Text that should wrap itself width and take as much space as possible when content is lengthy
Image that has to be present every time on the right side of Text
To achieve both, I'm currently using weight(weight = 1f, fill = false) for Text Composable. And it works fine for single line content. However, it creates an issue with line breaking and wrapping multiline text. For clearer view, I've added background(Color.Yellow) and some long words. See reference code:
Row {
Text(
text = "This is the message with quite super-unprecedented words",
modifier = Modifier
.background(Color.Yellow)
.weight(
weight = 1f,
fill = false
)
)
Image(
painter = painterResource(R.drawable.image),
contentDescription = null
)
}
And the result looks like this:
Is it possible to remove the padding at the end of Text so Image could fit closely to Text in multiline case as well? Or any other ideas how to achieve this? I've already taken a look on softWrap parameter for Text and requireSize for Image but with no big luck.

How to set the text size 2-3 sp smaller than the original in Jetpack Compose?

I want to set the text size 2-3 sp smaller than the original size. How can this be done?
With "androidx.compose.ui.text.TextStyle.Default.fontSize" you can get the original size, but how should I extract the 2-3 sp from it?
Text("Hello World!", fontSize = androidx.compose.ui.text.TextStyle.Default.fontSize)
The default value of font size is 14.sp
You can choose your font size accordingly.
Screenshot from source code:
Also, private val DefaultFontSize = 14.sp

Offset an item taking its height into account in Jetpack Compose

I want to position my own component (say a Text component) vertically so that I can specify the y-offset relative to the bottom of the Text component. How could I do this in Jetpack compose?
So something like
Column {
Text("Something", modifier = Modifier.offset(y=10.dp))
}
But instead of 10dp representing the top y-position of the Text component it would be the bottom y-position. Basically taking into account the height of the Text even if Text size changes. So y = offset.y - height
As I can see it, there's two problems:
The font size can be changed, so I cannot hard code the text height.
I need to know the size of my Text component during composition, but I don't know how to get that.
You could go for custom Composables,
#Composable
fun CustomText(y: Dp){
Layout(content = { Text(text = "Lorem Ipsum") }){measurables, constraints ->
val text = measurables[0].measure(constraints)
layout(constraints.maxWidth, constraints.maxHeight){ //Change these per your needs
text.placeRelative(IntOffset(0, y.value.roundToInt() - text.height))
}
}
}
You could also use a custom Modifier. Check out using the layout modifier

How to calculate letterSpacing for TextView from sketch values?

In design I have a text field with 16 as text size and 0.6 as character spacing
But, if I set this value for android:letterSpacing attribute of TextView spacing will be much more larger than in design.
So, what is the way to convert sketch value to android value?
According to Romain Guy, Android uses the "1 em = font size" definition, so you'd just do
setLetterSpacing(characterSpacing / textSize)
...just make sure those values are in the same unit (dp vs px)
If Sketch provides the letter spacing value in Points, then the following is the conversion:
float textSizePx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, textSizeInSp, displayMetrics);
float letterSpacingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, sketchLetterSpacing, displayMetrics);
float androidLetterSpacing = letterSpacingPx / textSizePx
return androidLetterSpacing;
Material design 2 uses em as the unit for letter spacing on Android, and the following conversion ratio for Sketch values need to be applied:
(Tracking from Sketch / font size
in sp) = letter spacing
To clarify, here is an example for Jetpack Compose:
fontSize = 14.sp,
letterSpacing = (1.25 / 14).em
1.25 is the Sketch value and 14 is the font size in sp, but note the unit is converted to em.
Check out android:textScaleX
Depending on how much spacing you need, this might help. That's the only thing remotely related to letter-spacing in the TextView.

Categories

Resources