I copied a piece of code from the example in jetpack compose.link
But in Android Studio a problem arises:
I wonder where is the problem? I'm still a beginner
The following is the complete code:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.MaterialTheme.colors
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.freedom.android.ui.theme.MyApplicationTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { Text("Drawer content") },
topBar = {
TopAppBar(
title = { Text("Simple Scaffold Screen") },
navigationIcon = {
IconButton(
onClick = {
scope.launch { scaffoldState.drawerState.open() }
}
) {
Icon(Icons.Filled.Menu, contentDescription = "Localized description")
}
}
)
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text("Inc") },
onClick = { /* fab click handler */ }
)
},
content = { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
items(count = 100) {
Box(
Modifier
.fillMaxWidth()
.height(50.dp)
.background(colors[it % colors.size])
)
}
}
}
)
}
}
}
}
#Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
MyApplicationTheme {
Greeting("Android")
}
}
I think the problem is with the colors variable, it doesn't seem to be an array, but this was copied from the official documentation, I didn't change it.
package androidx.compose.material
object MaterialTheme {
/**
* Retrieves the current [Colors] at the call site's position in the hierarchy.
*
* #sample androidx.compose.material.samples.ThemeColorSample
*/
val colors: Colors
#Composable
#ReadOnlyComposable
get() = LocalColors.current
}
If you look at the full source code of the sample the docs use, it has a top level object:
private val colors = listOf(
Color(0xFFffd7d7.toInt()),
Color(0xFFffe9d6.toInt()),
Color(0xFFfffbd0.toInt()),
Color(0xFFe3ffd9.toInt()),
Color(0xFFd0fff8.toInt())
)
So yeah, the colors that sample is referring to is a list that you can index into. If you want to also have a semi-random set of colors for your backgrounds, you can copy that list into your code as well.
Related
I'm pretty new to compose and having difficulty resizing views. I have a view where I am initially hardcoding it to some size. When clicking the view I want it to expand it to fill the width to certain extent. How is this possible with in compose?
#Composable
fun InputBar(
hint: String,
onClick: () -> Unit
) {
Card(
onClick = { /* update view to fillMaxWidth() */ },
modifier = Modifier
.width(150.dp)
.height(44.dp),
) {
Row(modifier = Modifier.fillMaxWidth()) {
TextField(
value = "",
onValueChange = {
},
placeholder = { Text(hint) }
)
}
}
}
you could achieve your by using something like this:
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import kotlin.math.log
private const val TAG = "InputBar"
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun InputBar(
hint: String,
onClick: () -> Unit
) {
var isFillMaxWidth by remember { mutableStateOf(false) }
val isExpandModifier = remember(isFillMaxWidth){
Modifier
.height(44.dp)
.then(
if (isFillMaxWidth) {
Log.i(TAG, "InputBar: fillMaxWidth")
Modifier.fillMaxWidth()
} else {
Log.i(TAG, "InputBar: 150")
Modifier.width(150.dp)
}
)}
Card(
modifier = isExpandModifier,
) {
Row(modifier = Modifier.fillMaxWidth()) {
TextField(
value = "",
onValueChange = {
},
placeholder = { Text(hint) },
interactionSource = remember { MutableInteractionSource() }
.also { interactionSource ->
LaunchedEffect(interactionSource) {
interactionSource.interactions.collect {
if (it is PressInteraction.Release) {
// works like onClick
isFillMaxWidth = !isFillMaxWidth
Log.i(TAG, "InputBar: onClick")
}
}
}
}
)
}
}
}
I am new to Jetpack compose. I am following the tutorial and the problem is that according to the tutorial the text color is black on the preview as well as on the real device (in the tutorial)
on my preview I also see the color as black, however, when I run the app on my two different devices I see the color is white.
What am I missing here?
UPD
package com.example.composableexmp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.composableexmp.ui.theme.ComposableExmpTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
TopHeader()
}
}
}
}
#Composable
fun MyApp(content: #Composable () -> Unit) {
ComposableExmpTheme {
Surface(
color = MaterialTheme.colors.background
) {
content()
}
}
}
#Preview
#Composable
fun TopHeader(totalPerPerson: Double = 0.0) {
Surface(
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.clip(shape = RoundedCornerShape(corner = CornerSize(12.dp))),
color = Color(color = 0xFFE9D7F7)
) {
Column(
modifier = Modifier.padding(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
val total = "%.2f".format(totalPerPerson)
Text(
text = "Total Per Person",
style = MaterialTheme.typography.h5
)
Text(
text = "$$total",
style = MaterialTheme.typography.h4,
fontWeight = FontWeight.ExtraBold
)
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
ComposableExmpTheme {
MyApp {
Text(text = "Hello Again")
}
}
}
Colors change based on your theme. You should inspect Theme.kt in ui.theme folder.
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
And how these colors are selected
#Composable
fun MyTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: #Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
(view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
On Surface is the color of the Text provided by Surface. Based on your theme it can be the values set to these colors.
I want to change solid or gradient color to jetpack compose snack bar. Please guide me how to
change color
Here is my snack bar using material3 compose, I am looking solution to change the background color
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import compose.material.theme.ui.theme.Material3ComposeTheme
import compose.material.theme.ui.theme.Purple40
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
#OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Material3ComposeTheme {
val context = LocalContext.current
val snackState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
topBar = {
},
content = {
fun launchSnackbar(message: String, actionLabel : String?=null, duration: SnackbarDuration = SnackbarDuration.Short){
scope.launch {
snackState.showSnackbar(message = message,actionLabel=actionLabel, duration=duration)
}
}
Column(
modifier = Modifier
.padding(it)
.verticalScroll(rememberScrollState())
) {
Spacer(modifier = Modifier.height(47.dp))
Text("Snackbar", Modifier.padding(bottom = 10.dp), style = MaterialTheme.typography.labelLarge)
Button(onClick = {
// * Snackbar
launchSnackbar(message = "Hi i am snackbar message", actionLabel = "Hide", duration = SnackbarDuration.Long)
}) { Text("Snackbar",style = MaterialTheme.typography.labelLarge) }
ListDividerPadding()
Text("Toast", Modifier.padding(bottom = 10.dp), style = MaterialTheme.typography.labelLarge)
Button(onClick = {
Toast.makeText(
context,
"Hi i am toast message",
Toast.LENGTH_LONG
).show()
}) { Text("Toast",style = MaterialTheme.typography.labelLarge) }
}
}
)
Box(modifier = Modifier.fillMaxSize(), Alignment.BottomCenter){
SnackbarHost(hostState = snackState)
}
}
}
}
}
You can add SnackBar composable to SnackbarHost and change colors as
SnackbarHost(hostState = snackState) {
Snackbar(
snackbarData = it,
containerColor = Color.Green,
contentColor = Color.Red
)
}
Edit
There is no overload function that takes Brush instead of Color but you can add another Composable as with gradient color or more customization via content: #Composable () -> Unit
#Composable
fun Snackbar(
modifier: Modifier = Modifier,
action: #Composable (() -> Unit)? = null,
dismissAction: #Composable (() -> Unit)? = null,
actionOnNewLine: Boolean = false,
shape: Shape = SnackbarTokens.ContainerShape.toShape(),
containerColor: Color = SnackbarTokens.ContainerColor.toColor(),
contentColor: Color = SnackbarTokens.SupportingTextColor.toColor(),
actionContentColor: Color = SnackbarTokens.ActionLabelTextColor.toColor(),
dismissActionContentColor: Color = SnackbarTokens.IconColor.toColor(),
content: #Composable () -> Unit
)
Can be used as
Snackbar {
Row(
modifier = Modifier.background(
brush = Brush.horizontalGradient(
listOf(
Color.Red,
Color.Green,
Color.Blue
)
)
)
) {
Text("Hello World")
}
}
I am seeing a weird image flickering issue with Jetpack compose. It is a simple two card deck where the top image is animated off-screen to reveal the second card. The second card displays fine for a second and then the first image flashes on the screen. I have tried with Coil, Fresco and Glide and they all behave the same way.
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import coil.compose.rememberImagePainter
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
class MainViewModel : ViewModel() {
var images = MutableLiveData(listOf(
"https://images.pexels.com/photos/212286/pexels-photo-212286.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/163016/crash-test-collision-60-km-h-distraction-163016.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/1366944/pexels-photo-1366944.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/5878501/pexels-photo-5878501.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/3846022/pexels-photo-3846022.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
))
}
class MainActivity : ComponentActivity() {
private val model by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen(model)
}
}
}
#Composable
fun MainScreen(model: MainViewModel) {
val images: List<String> by model.images.observeAsState(listOf())
Box(
modifier = Modifier.fillMaxSize()
) {
images?.take(2).reversed().forEach {
Card(url = it) {
val d = model.images.value?.toMutableList()
d?.let {
it.removeFirst()
model.images.value = it
}
}
}
}
}
#Composable
fun Card(
url: String,
advance: ()-> Unit = {},
){
val coroutineScope = rememberCoroutineScope()
var offsetX = remember(url) { Animatable(0f) }
Box(
modifier = Modifier
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
.fillMaxSize()
.background(color = Color.White)
.clickable {
coroutineScope.launch {
offsetX.animateTo(
targetValue = 3000F
)
}
coroutineScope.launch {
delay(400)
advance()
}
}
) {
Image(
painter = rememberImagePainter(
data = url,
),
contentDescription = null,
modifier = Modifier
.size(400.dp, 400.dp)
)
}
}
Also threw it on a github here in case anyone wants to try it out:
https://github.com/studentjet/learncompose
The problem is this: you have two card views. After deleting the top card, compose reuses them and updates them with the new data. And while the top card loads the second image, it still shows the first one.
You could disable caching, but in that case the image would still flash because it would show an empty space first. Instead, you need to have the second card's view reused for the first one.
Especially for such cases key Composable is made for: it'll reuse content for the same key between recompositions even if order changes.
val images = remember {
mutableStateListOf(
"https://images.pexels.com/photos/212286/pexels-photo-212286.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/163016/crash-test-collision-60-km-h-distraction-163016.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/1366944/pexels-photo-1366944.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/5878501/pexels-photo-5878501.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
"https://images.pexels.com/photos/3846022/pexels-photo-3846022.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
)
}
Box(
modifier = Modifier.fillMaxSize()
) {
images.take(2).reversed().forEach {
key(it) {
Card(url = it) {
images.add(
images.removeFirst()
)
}
}
}
}
Following the Pathway code labs from Google about Jetpack compose, I was trying out this code
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Divider
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.BasicsCodelabTheme
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
MyScreenContent()
}
}
}
}
#Composable
fun MyApp(content: #Composable () -> Unit) {
BasicsCodelabTheme {
Surface(color = Color.Yellow) {
content()
}
}
}
#Composable
fun MyScreenContent(names: List<String> = List(1000) { "Hello Android #$it" }) {
val counterState = remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxHeight()) {
NameList(names, Modifier.weight(1f))
Counter(
count = counterState.value,
updateCount = { newCount ->
counterState.value = newCount
}
)
}
}
#Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
items(items = names) { name ->
Greeting(name = name)
Divider(color = Color.Black)
}
}
}
#Composable
fun Greeting(name: String) {
var isSelected by remember { mutableStateOf(false) }
val backgroundColor by animateColorAsState(if (isSelected) Color.Red else Color.Transparent)
Text(
text = "Hello $name!",
modifier = Modifier
.padding(24.dp)
.background(color = backgroundColor)
.clickable(onClick = { isSelected = !isSelected })
)
}
#Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(
onClick = { updateCount(count + 1) },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (count > 5) Color.Green else Color.White
)
) {
Text("I've been clicked $count times")
}
}
#Preview("MyScreen preview")
#Composable
fun DefaultPreview() {
MyApp {
MyScreenContent()
}
}
I have noticed that LazyColumn will recompose the items whenever they become Visible on the screen (intended behaviour!) however, the Local state of Greeting widget is completely lost!
I believe this is a bug in Compose, Ideally the composer should consider the remember cached state. Is there an elegant way to fix this?
Thanks in advance!
[Update]
Using rememberSaveable {...} gives the power to survive the android change of configuration as well as the scrollability
Docs
Remember the value produced by init.
It behaves similarly to remember, but the stored value will survive the activity or process recreation using the saved instance state mechanism (for example it happens when the screen is rotated in the Android application).
The code now is more elegant and shorter, we don't even need to hoist the state, it can be kept internal. The only thing I am not so sure of now with using rememberSaveable is if there will be any performance penalties when the list grows bigger and bigger, say 1000 items.
#Composable
fun Greeting(name: String) {
val isSelected = rememberSaveable { mutableStateOf(false) }
val backgroundColor by animateColorAsState(if (isSelected.value) Color.Red else Color.Transparent)
Text(
text = "Hello $name! selected: ${isSelected.value}",
modifier = Modifier
.padding(24.dp)
.background(color = backgroundColor)
.clickable(onClick = {
isSelected.value = !isSelected.value
})
)
}
[Original Answer]
Based on #CommonWare's answer The LazyColumn will dispose the composables along with their states when they are off-screen this means when LazyColumn recomposes the Compsoables again it will have fresh start state. To fix this issue all that has to be done is to hoist the state to the consumer's scope, LazyColumn in this case.
Also we need to use mutableStateMapOf() instead of MutableMapOf inside the remember { ... } lambda or Compose-core engine will not be aware of this change.
So far here is the code:
#Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {
val selectedStates = remember {
mutableStateMapOf<Int, Boolean>().apply {
names.mapIndexed { index, _ ->
index to false
}.toMap().also {
putAll(it)
}
}
}
LazyColumn(modifier = modifier) {
itemsIndexed(items = names) { index, name ->
Greeting(
name = name,
isSelected = selectedStates[index] == true,
onSelected = {
selectedStates[index] = !it
}
)
Divider(color = Color.Black)
}
}
}
Happy composing!