In Jetpack Compose, I want to change the color of a custom text button depending on its pressed state.
fun CustomTextButton() {
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val color = when (isPressed) {
true -> Color.Red
false -> Color.Black
}
LaunchedEffect(key1 = isPressed) {
println("#### isPressed: $isPressed")
}
Box(
modifier = Modifier
.clickable(
indication = null,
interactionSource = interactionSource,
onClick = { println("#### Box clicked") }
)
) {
Text(
text = "CustomTextButton",
color = color
)
}
}
Unfortunately this only works for longer presses. On a short tap isPressed does not change to true. Consequently the Button's text color does not change. I found this blog post doing something similar but there the button color also only changes on longer presses.
https://medium.com/geekculture/button-selector-in-jetpack-compose-android-fb77a37d03e8
The output on a longer press:
2023-02-17 13:31:22.598 ... #### isPressed: true
2023-02-17 13:31:23.047 ... #### Box clicked
2023-02-17 13:31:23.106 ... #### isPressed: false
The output on a short tap:
2023-02-17 13:33:12.382 ... #### Box clicked
Is collectIsPressedAsState() only usable for press events and do I need to use something else for taps?
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()
}
}
How to write two conditionals under one Modifier? For example for .background i have one conditional that handles pressed state and other is just for setting background color depending of passed variants.
If code is written with two .background modifier then isPressed part is not working.
Box(
Modifier
.pointerInput(Unit) {
detectTapGestures(
onPress = {
try {
isPressed = true
awaitRelease()
} finally {
isPressed = false
}
},
)
}
.background(if (!isPressed) Red else DarkRed)
.background(if (variant == VariantButtons.Primary) Red else if (variant == VariantButtons.Green) Green else Color.Transparent)
)
If I understand your question correctly you can use only one background modifier and combine statements inside it, just like below:
.background(
if (isPressed) {
DarkRed
} else {
when (variant) {
VariantButtons.Primary -> Red
VariantButtons.Green -> Green
else -> Color.Transparent
}
}
)
I have a Text Field in android jetpack compose, which used to get number from user including floating points. How to prevent user from entering 2 dots in the input.
For Eg: Number should be 56.54,.266, 367.5
Should not be like this: 34.3.5
You can use onValueChange of TextField to filter the text:
#Composable
fun MyTextField(
) {
var text by remember { mutableStateOf("") }
TextField(
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
value = text,
onValueChange = {
val newString = it.filter { char ->
char == ".".first()
}
if (newString.length <= 1)
text = it
}
)
}
I'm trying to make a chess engine desktop app with an ui component to it.
The game() method that I mention is a simple while loop that allows me to ask for moves in algebraic notation and make the moves if they are valid ones.
I have the following main and my problem is that if I uncomment and run the game() method inside the classe it wont start the App window, and if I instead try to uncomment the same method but from outside the Window it still wont start the desktop App.
On the other hand if i run it has it is, it will start the UI window.
fun main() = application {
resetBoard()
printBoardSmall()
Window(onCloseRequest = ::exitApplication, icon = painterResource("black_knight.png"), title = "Chess") {
ui()
//game()
}
//game()
}
#Composable
fun ui() {
var squarePair = false
Row {
Column {
for (n in 8 downTo 1) {
Row {
Text(
"" + n,
textAlign = TextAlign.Center,
modifier = Modifier.width(SIZE_TILE),
fontSize = FONT_SIZE_BOARD,
fontWeight = FontWeight.Bold
)
squarePair = boardLines(n, squarePair)
}
}
Row {
Text(" ", textAlign = TextAlign.Center, modifier = Modifier.width(SIZE_TILE))
for (n in 0..7) {
Text(
"" + ('A' + n),
textAlign = TextAlign.Center,
modifier = Modifier.width(SIZE_TILE),
fontSize = FONT_SIZE_BOARD,
fontWeight = FontWeight.Bold
)
}
}
}
Column {
Text(" Play", textAlign = TextAlign.Center, fontSize = 30.sp)
var move = ""
//var move by remember { mutableStateOf("") }
TextField(
value = move,
onValueChange = { move = it },
label = { Text("Move") },
maxLines = 1,
textStyle = TextStyle(color = Color.Black, fontWeight = FontWeight.Bold),
modifier = Modifier.padding(20.dp)
)
print(move)
}
}
}
#Composable
fun board(n: Int, i: Int){
var team = ""
if(utils.isWhite(BOARD[n-1][i-1])) team = TEAM[0]
if(utils.isBlack(BOARD[n-1][i-1])) team = TEAM[1]
for(k in LOWER_CASE_LETTERS.indices) {
if (BOARD[n-1][i-1] == LOWER_CASE_LETTERS[k] || BOARD[n-1][i-1] == UPPER_CASE_LETTERS[k]) {
Image(painter = painterResource(team + "_" + PIECES[k] + ".png"), contentDescription = PIECES[k])
}
}
}
Im new to compose and I can't figure out whats the problem, expecially since im trying to run the method outside the Window
I'm no expert, but I'll try my best to help.
It doesn't work, when you call it in the Window, 'cause that's what it uses to init it's layout. So the Window won't show, 'till the game() stops.
I don't know, why it doesn't work, when you put it after the Window. Looking at fun main() = application { ... } I suspect, that initiating the application is similar to initiating the Window, so it also won't start, untill the game() is over.
You should try putting game() in a seperate thread. Something like this should work:
fun() main = application {
resetBoard()
printBoardSmall()
Window( ... ){
ui()
}
Thread {
game()
}.start()
}
P.S. I'm also very new to compose. And kinda new to programming. And really, really sorry for all the gross oversimplifications I made.
P.P.S. You could add a TextField to your app and use Button's onClick parameter to call a move checking function. Wouldn't need to play with Threads and coroutines then. And you could also use Buttons for the board squares, cause you can assign them a background and an image. If you would need help with any of that - HMU.
I am writing a compose desktop app.
I have a main window:
Window(size = IntSize(600, 600)) {
// snip logic
Column(horizontalAlignment = Alignment.CenterHorizontally) {
ChessBoard(board.value, modifier = Modifier.fillMaxWidth(0.8f))
Row(modifier = Modifier
.fillMaxSize(), horizontalArrangement = Arrangement.SpaceEvenly) {
Button(onClick = previousBoard) {
Text("<")
}
Button(onClick = nextBoard) {
Text(">")
}
}
}
}
Where ChessBoardis defined as
#Composable
fun ChessBoard(board: Board, modifier: Modifier = Modifier) {
Canvas(modifier = modifier) {
// snip logic, I checked that it does not influence the result
}
}
The chessboard takes up the correct amount of space, but the buttons overlap and aren't added at the bottom as expected.
I tried tweaking the modifiers on ChessBoard, but that did not change the fact that the buttons are at the top.
On the Row, remove the Modifier.fillMaxSize()
I think what you need is Modifier.fillMaxWidth()
The Row is taking up the entire space of the screen, and the buttons inside the row, by default stay at the top of the row. To check that this is the case, try adding a Modifier.align(CenterVerticlly) to the row, and the buttons will move to the center of the screen, since the row is still occupying the whole screen. Removing the Modifier.fillMaxSize() should do it, but if you still want to keep it for other Composables, use Modifier.align(Alignment) to fix it