I have read the article.
I know that maybe I need to set isAntiAlias=true when I use nativeCanvas just like Code A.
Do I need set isAntiAlias when I use Android Compose Canvas such as Code B ? If so, how can I do it?
Code A
#Composable
fun ArcTextExample() {
val paint = Paint().asFrameworkPaint()
Canvas(modifier = Modifier.fillMaxSize()) {
paint.apply {
isAntiAlias = true
textSize = 24f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
}
drawIntoCanvas {
val path = Path()
path.addArc(RectF(0f, 100f, 200f, 300f), 270f, 180f)
it.nativeCanvas.drawTextOnPath("Hello World Example", path, 0f, 0f, paint)
}
}
}
Code B
#Composable
fun setCanvas() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val canvasWidth = size.width
val canvasHeight = size.height
drawLine(
start = Offset(x = 0f, y = canvasHeight),
end = Offset(x = canvasWidth, y = 0f),
color = Color.Blue
)
}
}
Usually, isAntiAlias is used when drawing text to the canvas, Because it will smoothen the edges
You have to access the native canvas from the Android framework in order to draw some text, you can call all the methods related to the native Canvas like drawText() or drawVertices()
To apply a style to the text, a Paint object must be used, and we can change isAntiAlias property just like the code A.
And in compose there is no concept of Paint because compose already took care of those and made easy to draw shapes, but for drawing texts you can only do this with help of nativeCanvas and paint where you can declare AntiAlias_Flag to true for smoothening the edges.
Related
I'm trying to start with jetpack compose and it's getting complicated to be able to change the background automatically according to the theme chosen by the user (light dark).
I am editing the colors from theme.kt
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80,
surface = Color(0xFF0BB000),
background = Color(0xFF0BB000),
onBackground = Color(0xFF0BB000))
The problem is that when I run the app, the background color is still grey.
I think the problem is that my app doesn't take the colors from the theme, since I tried to set it directly, but it doesn't change the background color either.
Surface ( color = MaterialTheme.colorScheme.background)
If anyone has any ideas why it doesn't change color automatically and point me to it, I'd appreciate it.
I can set the palette again from my activity and change it, it depends on the mode chosen by the user, but it is not an optimal solution and it looks ugly.
I leave my activity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TruequeGualeguaychuTheme {
Surface ( color = MaterialTheme.colorScheme.background){
Text(
modifier = Modifier
.fillMaxSize()
.wrapContentHeight(),
textAlign = TextAlign.Center,
text = "Hello Android!",
)
}
}
}
}
}
It should have a green background but it does not take the color declared in ( background = Color(0xFF0BB000) )
val Colors.myBackgroundColor get() = if (isLight) Color.White else Color.Black
you can create color like this and use this color in Surface background color.
Surface(modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colors.myBackgroundColor)) {
// your code
}
The part it looks like you're missing is the hinted at in #vaspike's answer. All views that should utilize your custom theme should derive from an instance of the theme. For example:
#Composable
fun MyTheme(content: #Composable () -> Unit) {
MaterialTheme(
colors = LightColors, // A list of key/values I define.
content = content
)
}
#Composable
fun HomePage(){
MyTheme(){
// composables that use the theme.
}
}
This comes from the example found here, https://developer.android.com/codelabs/jetpack-compose-theming#3
The problem is that newer versions of android have dynamic colors that use a color palette based on the system.
By default the theme has this option activated which limits the setting that one can make of the color palette.
dynamicColor: Boolean = true
I have implemented similar functions before,but it's at Jb-compose desktop.Provide a reference:
MainApp.kt:
#OptIn(ExperimentalMaterialApi::class)
#Composable
#Preview
fun launchApp() {
MaterialTheme(
colors = darkColors(
primary = MyColor.primaryColor,
primaryVariant = MyColor.primaryColor,
secondary = MyColor.primaryColor,
secondaryVariant = MyColor.primaryColor,
background = MyColor.darkBackgroundColor,
surface = MyColor.darkBackgroundColor,
onPrimary = Color.White,
onSecondary = Color.White,
onBackground = Color.White,
onSurface = Color.White,
error = Color.Red,
onError = Color.White
),
){
//...
}
}
Main.kt:
fun main(args: Array<String>) = application {
Window(
onCloseRequest = ::exitApplication,
icon = painterResource("icon/xx.ico"),
title = "xx ${RuntimeContext.APP_VERSION}",
) {
MainApp.launchApp()
}
}
I'm trying to create a scrollable background in Jetpack Compose.
The problem is that the variable "currentPadding" isn't updating it's state after the value "padding" is modified after recomposition. In the first composition (loading state) the "padding" value is set to 112.dp and after load the value changes to 160.dp.
It's strange because I have used the remember function this way multiple times in other places in the app and it's the first time that this happens.
Could you help me out?
Thanks a lot.
#Composable
fun ScrollableBackground(
scrollState: ScrollState,
composable: ComposableFun,
modifier: Modifier = Modifier,
isBannerListEmpty: Boolean,
) {
val padding = if (isBannerListEmpty) {
112.dp
} else {
160.dp
}
val minPadding: Dp = 29.dp
val dp0 = dimensionResource(id = R.dimen.no_dp)
var currentPadding: Dp by remember { mutableStateOf(padding) }
val state: Dp by animateDpAsState(targetValue = currentPadding)
val nestedScrollConnection: NestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val percent = scrollState.value.toFloat() / scrollState.maxValue.toFloat() * 100f
val delta = available.y.dp
val newSize = currentPadding + delta / 3
currentPadding = when {
percent > 20f -> minPadding
newSize < minPadding -> minPadding
newSize > padding -> padding
else -> newSize
}
return Offset.Zero
}
}
}
Box(
modifier = modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
) {
Surface(
color = White,
modifier = Modifier
.padding(top = state)
.fillMaxSize()
.clip(
CircleShape.copy(
topStart = Shapes.medium.topStart,
topEnd = Shapes.medium.topEnd,
bottomEnd = CornerSize(dp0),
bottomStart = CornerSize(dp0)
)
)
) {}
composable.invoke()
}
}
I tried sending other kind of parameters from the view model to the composable (instead of a boolean, in this case "isBannerListEmpty"), like the current desired padding value, and nothing seems to work.
You have put nestedScrollConnection in remember. It also remembers the padding variable when first encountered. So when the actual value of padding is changed, this change is not propagated in remember of nestedScrollConnection.
Put padding inside onPreScroll.
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val padding = if (...) { //Don't use isBannerListEmpty here as neither this will update on recomposition
112.dp
} else {
160.dp
}
...
I'm trying to create a sky view in my Android app using Jetpack Compose. I want to display it inside a Card with a fixed height. During nigth time, the card background turns dark blue and I'd like to have some blinking stars spread over the sky.
To create the stars blinking animation, I'm using an InfiniteTransition object and a scale property with animateFloat that I apply to several Icons. Those Icons are created inside a BoxWithConstraints, to spread then randomly using a for loop. The full code I'm using is shown below:
#Composable
fun NightSkyCard() {
Card(
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(20.dp),
backgroundColor = DarkBlue
) {
val infiniteTransition = rememberInfiniteTransition()
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
)
)
BoxWithConstraints(
modifier = Modifier.fillMaxSize()
) {
for (n in 0..20) {
val size = Random.nextInt(3, 5)
val start = Random.nextInt(0, maxWidth.value.toInt())
val top = Random.nextInt(10, maxHeight.value.toInt())
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = null,
modifier = Modifier
.padding(start = start.dp, top = top.dp)
.size(size.dp)
.scale(scale),
tint = Color.White
)
}
}
}
}
The problem with this code is that the BoxWithConstraints's scope is recomposing continously, so I get a lot of dots appearing and dissapearing from the screen very fast. I'd like the scope to just run once, so that the dots created at first time would blink using the scale property animation. How could I achieve that?
Instead of continuous recomposition you should look for least amount of recompositions to achieve your goal.
Compose has 3 phases. Composition, Layout and Draw, explained in official document. When you use a lambda you defer state read from composition to layout or draw phase.
If you use Modifier.scale() or Modifier.offset() both of three phases above are called. If you use Modifier.graphicsLayer{scale} or Modifier.offset{} you defer state read to layout phase. And the best part, if you use Canvas, which is a Spacer with Modifier.drawBehind{} under the hood, you defer state read to draw phase as in example below and you achieve your goal only with 1 composition instead of recomposing on every frame.
For instance from official document
// Here, assume animateColorBetween() is a function that swaps between
// two colors
val color by animateColorBetween(Color.Cyan, Color.Magenta)
Box(Modifier.fillMaxSize().background(color))
Here the box's background color is switching rapidly between two
colors. This state is thus changing very frequently. The composable
then reads this state in the background modifier. As a result, the box
has to recompose on every frame, since the color is changing on every
frame.
To improve this, we can use a lambda-based modifier–in this case,
drawBehind. That means the color state is only read during the draw
phase. As a result, Compose can skip the composition and layout phases
entirely–when the color changes, Compose goes straight to the draw
phase.
val color by animateColorBetween(Color.Cyan, Color.Magenta)
Box(
Modifier
.fillMaxSize()
.drawBehind {
drawRect(color)
}
)
And how you can achieve your result
#Composable
fun NightSkyCard2() {
Card(
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(20.dp),
backgroundColor = Color.Blue
) {
val infiniteTransition = rememberInfiniteTransition()
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
)
)
val stars = remember { mutableStateListOf<Star>() }
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.background(Color.Blue)
) {
SideEffect {
println("🔥 Recomposing")
}
LaunchedEffect(key1 = Unit) {
repeat(20) {
stars.add(
Star(
Random.nextInt(2, 5).toFloat(),
Random.nextInt(0, constraints.maxWidth).toFloat(),
Random.nextInt(10, constraints.maxHeight).toFloat()
)
)
}
}
Canvas(modifier = Modifier.fillMaxSize()) {
if(stars.size == 20){
stars.forEach { star ->
drawCircle(
Color.White,
center = Offset(star.xPos, star.yPos),
radius = star.radius *(scale)
)
}
}
}
}
}
}
#Immutable
data class Star(val radius: Float, val xPos: Float, val yPos: Float)
One solution is to wrap your code in a LaunchedEffect so that the animation runs once:
#Composable
fun NightSkyCard() {
Card(
modifier = Modifier
.height(200.dp)
.fillMaxWidth(),
elevation = 2.dp,
shape = RoundedCornerShape(20.dp),
backgroundColor = DarkBlue
) {
val infiniteTransition = rememberInfiniteTransition()
val scale by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = 0f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
)
)
BoxWithConstraints(
modifier = Modifier.fillMaxSize()
) {
for (n in 0..20) {
var size by remember { mutableStateOf(0) }
var start by remember { mutableStateOf(0) }
var top by remember { mutableStateOf(0) }
LaunchedEffect(key1 = Unit) {
size = Random.nextInt(3, 5)
start = Random.nextInt(0, maxWidth.value.toInt())
top = Random.nextInt(10, maxHeight.value.toInt())
}
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = null,
modifier = Modifier
.padding(start = start.dp, top = top.dp)
.size(size.dp)
.scale(scale),
tint = Color.White
)
}
}
}
}
You then get 21 blinking stars.
I'm learning Jetpack Compose Canvas. I know that drawIntoCanvas() — Provides access to draw directly with the underlying Canvas. This is helpful for situations to re-use alternative drawing logic in combination with DrawScope.
I want to use different style line for different goal, such as:
Style 1: color = Color.Blue , strokeWidth = 3f,...
Style 2: color = Color.Red , strokeWidth = 4f, ...
I can re_use thses styles with Code B. I hope to I can re_use these style with Code A, how can I do?
Code A
#Composable
fun setCanvas() {
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val canvasWidth = size.width
val canvasHeight = size.height
drawLine(
start = Offset(x = 0f, y = canvasHeight),
end = Offset(x = canvasWidth, y = 0f),
color = Color.Blue,
strokeWidth = 3f
)
}
}
Code B
#Composable
fun setCanvas() {
val linePaint1 = Paint()
linePaint1.isAntiAlias = true
linePaint1.style = PaintingStyle.Stroke
linePaint1.color = Color.Blue
val linePaint2 = Paint()
linePaint2.isAntiAlias = true
linePaint2.style = PaintingStyle.Stroke
linePaint2.color = Color.Red
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val canvasWidth = size.width
val canvasHeight = size.height
drawIntoCanvas {
it.drawLine(
Offset(x = 0f, y = canvasHeight),
Offset(x = canvasWidth, y = 0f),
linePaint1
)
it.drawLine(
Offset(x = 10f, y = canvasHeight-50),
Offset(x = canvasWidth-10, y = 10f),
linePaint2
)
}
}
}
There is no reason not to use drawInCanvas, and the DrawScope you are calling in A is meant to be stateless and does not depens on androids Paint class. If you must use paint objects, you could create an extension like so:
fun DrawScope.drawLine(
start: Offset?,
end: Offset?,
paint: Paint,
blendMode: BlendMode? = DefaultBlendMode
) = drawLine(
start,
end,
paint.strokeWidth,
paint.strokeCap.toStrokeCap(),
paint.pathEffect.toComposePathEffect(),
paint.alpha.toFloat() / 255f,
paint.colorFilter.asComposeColorFilter(),
blendMode
)
fun Paint.Cap.toStrokeCap() = when(this) {
Paint.Cap.BUTT -> StrokeCap.Butt
Paint.Cap.SQUARE -> StrokeCap.Square
Paint.Cap.ROUND -> StrokeCap.Round
}
Since there is only a difference in color, I encourage you not to use this method and just call the existing drawLine function. If you must use paints, use this.
I have here my code which just displays a simple color palette:
#Composable
fun ColorPicker(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxSize()
.background(Brush.horizontalGradient(colors()))
) {
}
}
fun colors(n: Int = 359): List<Color> {
val cols = mutableListOf<Color>()
for (i in 0 until n) {
val color = java.awt.Color.getHSBColor(i.toFloat() / n.toFloat(), 0.85f, 1.0f)
cols.add(Color(color.red, color.green, color.blue, color.alpha))
}
return cols
}
That looks pretty good:
But now I want to get the RGB Value if the usere clicks on the color palette. How would I do this? (This is Jetpack Compose Desktop but it should be the same as on Android)
You could instead create Cards of a very small width for each colour and modify a state holder from their onClicks
For example,
var rgb by mutableStateOf ("") // displays RGB values
Text(rgb)
Layout( content = {
for (i in 0 until 359) {
val color =
java.awt.Color.getHSBColor(i.toFloat() / n.toFloat(), 0.85f, 1.0f)
Card(
Modifier.background(Color(
color.red,
color.green,
color.blue,
color.alpha
)
),
onClick = { rgb = color. // Assign appropriate value
}
}
){measurables, constraints ->
val placeables = measurables.map{
it.measure(constraints.maxWidth/measurables.size, constraints.maxHeight)
}
int x = 0
layout(constraints.maxWidth, constraints.maxHeight){
placeables.forEach{
it.placeRelative(x, 0)
x+=it.width
}
}
}
This distributes width equally