I have button to record voice so I want it to start record when user press it and stop when he leave it
#Composable
fun Screen(){
Button(){
Text("record")
}
}
If you're asking just about the press/release actions, I don't know how to do this with a button, but you can achieve the same result using a Box (for instance) and use some modifiers to design it the way you want...
Here is a possible solution.
#Composable
fun TestButton() {
var isPressed by remember {
mutableStateOf(false)
}
Column {
Box(
Modifier
.pointerInput(Unit) {
detectTapGestures(
onPress = {
try {
isPressed = true
// Start recording here
awaitRelease()
} finally {
isPressed = false
// Stop recording here
}
},
)
}
.background(
MaterialTheme.colors.primary.copy(alpha = if (isPressed) .88f else 1f),
MaterialTheme.shapes.small
)
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Text(
text = "Press me!",
Modifier.align(Alignment.Center),
color = MaterialTheme.colors.onPrimary
)
}
Text(text = if (isPressed) "Pressed" else "Unpressed")
}
}
Notice that I'm using a Box with a similar design of a Button.
Here is the result:
To get the press/release actions in a Button you can use the InteractionSource.collectIsPressedAsState to know if the Button is pressed.
You can add a side effect to know when the Button is released.
Something like:
val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
if (isPressed){
println("Pressed")
//Use if + DisposableEffect to wait for the press action is completed
DisposableEffect(Unit) {
onDispose {
println("released")
}
}
}
Button(onClick={},
interactionSource = interactionSource
){
Text("record")
}
Related
This question already has answers here:
Why don't Indication work for Button or Icons?
(2 answers)
Closed 6 months ago.
I have seen that we can disable the ripple effect of a view with the clickable(interactionSource, indication) inside for example a row or column but my question is that if we can disable it from a Button or FloatingActionButton
I see that FloatingActionButton has an interactionSource attribute and I have tried this
FloatingActionButton(
modifier = Modifier
.size(40.dp),
onClick = {
buttonState = when (buttonState) {
ButtonState.PRESSED -> ButtonState.UNPRESSED
ButtonState.UNPRESSED -> ButtonState.PRESSED
}
},
interactionSource = remember {
MutableInteractionSource()
})
this is not working to disable the ripple effect.
Then I have tried with the indication modifier like this
FloatingActionButton(
modifier = Modifier
.size(40.dp)
.indication(
interactionSource = remember {
MutableInteractionSource()
},
indication = null
),
onClick = {
buttonState = when (buttonState) {
ButtonState.PRESSED -> ButtonState.UNPRESSED
ButtonState.UNPRESSED -> ButtonState.PRESSED
}
})
also is not working, and then last thing I tried is adding the .clickable(...) in the modifier of the Fab button but I think that is pointless since the button has its own onClick event.
All the cases above yields to this
Is there anyway from any Button to disable its ripple effect without adding a Column or Box with a clickable attribute into its modifier ?
You can change ripple o by providing RippleTheme
private class CustomRippleTheme : RippleTheme {
#Composable
override fun defaultColor(): Color = Color.Unspecified
#Composable
override fun rippleAlpha(): RippleAlpha = RippleAlpha(
draggedAlpha = 0f,
focusedAlpha = 0f,
hoveredAlpha = 0f,
pressedAlpha = 0f,
)
}
Demo
#Composable
private fun RippleDemo() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(50.dp)
) {
Button(onClick = { /*TODO*/ }) {
Text("Button with ripple", fontSize = 20.sp)
}
Spacer(Modifier.height(20.dp))
FloatingActionButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Filled.Add, contentDescription = null)
}
Spacer(Modifier.height(20.dp))
CompositionLocalProvider(LocalRippleTheme provides CustomRippleTheme()) {
Button(onClick = { /*TODO*/ }) {
Text("Button with No ripple", fontSize = 20.sp)
}
Spacer(Modifier.height(20.dp))
FloatingActionButton(onClick = { /*TODO*/ }) {
Icon(imageVector = Icons.Filled.Add, contentDescription = null)
}
}
}
}
Result
A Button, internally, is just a surface with modifications applied to it to make it clickable. It has a default indication set within the implementation, hence cannot be "turned off" at the calling site.
Just pull up the source code and remove the indication, storing the resultant inside a new Composable.
Just do a quick Ctrl+Left Click on the text "Button" in Studio, and it'll take you there.
I try show AlertDialog when press a button.
For AlertDialog i have a composable function - showDialog.
It is clear that this function calls a dialog.
I have another composable function which displays some window with text and buttons.
When the button is clicked, I want to call a function that stores the AlertDialog.
AlertDialog body:
fun monthDialog() {
val openDialog = remember { mutableStateOf(true) }
if (openDialog.value) {
AlertDialog(
onDismissRequest = {
openDialog.value = false
},
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { openDialog.value = false }
) {
Text("Dismiss")
}
}
}
)
}
first I tried the simplest solution that came to my mind:
IconButton(onClick = monthDialog())
and got error (#Composable invocations can only happen from the context of a #Composable function)
after i tried a normal trigger using mutablestateof and following condition:
val showDialog = remember { mutableStateOf(false)}
if(showDialog.value == true) monthDialog()
and put some like this into onClick event:
monthHeader(onClick = {showDialog.value = !showDialog.value})
and this is work....but ugly and badly.
for a first time this is worf fine. but after the first click, I need to click two more times to trigger that event again.
button code snippet:
fun Calendar (){
//...
val showDialog = remember { mutableStateOf(false)}
if(showDialog.value == true) monthDialog()
Card(
){
Column(){
IconButton(onClick ={
monthDialog() // error here
//showDialog.value = !showDialog.value
}
}
}
after few hours for searching in google
i try my own solution
val clicked = remember { mutableStateOf(false)}
val showDialog = remember { mutableStateOf(false)}
if(showDialog.value) monthDialog()
else if(clicked.value) monthDialog()
Card(){
Column(){
monthHeader(onClick = {
clicked.value = showDialog.value
showDialog.value = !clicked.value
})
}
}
but in my opinion this is crutch/kludge
Leaving a better solution here (imho):
Hoist the state of your dialog.
#Composable
fun MonthDialog(onClose: () -> Unit) {
AlertDialog(
onDismissRequest = onClose,
title = {
Text(text = "Title")
},
text = {
Text(
"This area typically contains the supportive text " +
"which presents the details regarding the Dialog's purpose."
)
},
buttons = {
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onClose
) {
Text("Dismiss")
}
}
}
)
Noticed that I removed the state from this component and make it stateless. This component will just notify when the dialog is closed.
Now you can call the dialog like this:
var showDialog by remember { mutableStateOf(false) }
if (showDialog) {
MonthDialog(onClose = { showDialog = false })
}
Card {
MonthHeader( // use upper case for naming your composables
onClick = {
showDialog = true
}
)
}
One can also use Modifier / PointerInputScope:
modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onPress = { },
onTap = { },
onDoubleTap = { },
onLongPress = { }
)
I try to create a tappable surface in Jetpack Compose where the elevation changes when the user taps the surface. The following code already works:
var tapped by remember { mutableStateOf(false) }
val elevation by animateDpAsState(
targetValue = if (tapped) 0.dp else 5.dp,
animationSpec = tween(50)
)
Surface(
shape = RoundedCornerShape(20.dp),
modifier = Modifier
.padding(16.dp)
.requiredSize(150.dp)
.pointerInput(Unit) {
detectTapGestures(onPress = {
tapped = true
tryAwaitRelease()
tapped = false
})
},
elevation = elevation
) {
...
}
However I would like to have a ripple effect during the tap. How could I achieve this?
The default button/surface onClick and clickable is not suitable because it only handles press inputs but no taps.
You use Modifier.indication to add ripple effect, and pass events with interactionSource to update it state like this:
var tapped by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }
Surface(
modifier = Modifier
.indication(interactionSource, LocalIndication.current)
.pointerInput(Unit) {
detectTapGestures(onPress = { offset ->
tapped = true
val press = PressInteraction.Press(offset)
interactionSource.emit(press)
tryAwaitRelease()
interactionSource.emit(PressInteraction.Release(press))
tapped = false
})
}
) {
}
So I have this composable in my project ...
#Composable
private fun ShowDialog() {
var showText by remember { mutableStateOf(false) }
val text = if (showText) {
"Hide Text"
} else {
"Show Text"
}
Dialog(onDismissRequest = { }) {
Card(modifier = Modifier.padding(15.dp)) {
Column(modifier = Modifier.padding(15.dp)) {
AnimatedVisibility(visible = showText) {
Text(
text = "Here is the show text sample",
modifier = Modifier.padding(5.dp),
style = MaterialTheme.typography.body1,
color= Color.Black
)
}
Button(onClick = { showText = !showText }) {
Text(text = text)
}
}
}
}
}
If you have gone through the code, you might get what it is supposed to do. i.e it is basically a dialog with one text and a button below it. When the user clicks on a button the text above the button will toggle its visibility.
But the problem with the code is, When I click on the button, the text appears but the button gets invisible in other words the text takes the space and pushes a button to below. But yet the container in this case card or the column doesn't expand its height.
Is it supposed to work like that ? Or is this a bug?
I tried animateContentSize() on Column and Card but it didn't work. And checked similar questions on StackOverflow but didn't found any useful information.
Luckily, I found a temporary working answer for this problem,
What we need to use is just pass DialogProperties(usePlatformDefaultWidth = false) as properties parameter for dialog. This will make the dialog to resizable like this
#Composable
private fun ShowDialog() {
var showText by remember { mutableStateOf(false) }
val text = if (showText) {
"Hide Text"
} else {
"Show Text"
}
Dialog(
onDismissRequest = { },
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Card(
modifier = Modifier
.padding(15.dp)
.wrapContentWidth()
.animateContentSize()
) {
Column(modifier = Modifier.padding(15.dp).fillMaxWidth(1f)) {
AnimatedVisibility(visible = showText) {
Text(
text = "Sample",
modifier = Modifier
.padding(5.dp)
.fillMaxWidth(1f),
style = MaterialTheme.typography.body1,
color = Color.Black
)
}
Button(onClick = { showText = !showText }) {
Text(text = text)
}
}
}
}
}
Caution: It uses #ExperimentalComposeUiApi
This API is experimental and is likely to change in the future.
I have this Text:
Text(
text = stringResource(id = R.string.hello)
)
How can I show and hide this component?
I'm using Jetpack Compose version '1.0.0-alpha03'
As CommonsWare stated, compose being a declarative toolkit you tie your component to a state (for ex: isVisible), then compose will intelligently decide which composables depend on that state and recompose them. For ex:
#Composable
fun MyText(isVisible: Boolean){
if(isVisible){
Text(text = stringResource(id = R.string.hello))
}
}
Also you could use the AnimatedVisibility() composable for animations.
You can simply add a condition like:
if(isVisible){
Text("....")
}
Something like:
var visible by remember { mutableStateOf(true) }
Column() {
if (visible) {
Text("Text")
}
Button(onClick = { visible = !visible }) { Text("Toggle") }
}
If you want to animate the appearance and disappearance of its content you can use the AnimatedVisibility
var visible by remember { mutableStateOf(true) }
Column() {
AnimatedVisibility(
visible = visible,
enter = fadeIn(
// Overwrites the initial value of alpha to 0.4f for fade in, 0 by default
initialAlpha = 0.4f
),
exit = fadeOut(
// Overwrites the default animation with tween
animationSpec = tween(durationMillis = 250)
)
) {
// Content that needs to appear/disappear goes here:
Text("....")
}
Button(onClick = { visible = !visible }) { Text("Toggle") }
}
As stated above, you could use AnimatedVisibility like:
AnimatedVisibility(visible = yourCondition) { Text(text = getString(R.string.yourString)) }
/**
* #param visible if false content is invisible ie. space is still occupied
*/
#Composable
fun Visibility(
visible: Boolean,
content: #Composable () -> Unit
) {
val contentSize = remember { mutableStateOf(IntSize.Zero) }
Box(modifier = Modifier
.onSizeChanged { size -> contentSize.value = size }) {
if (visible || contentSize.value == IntSize.Zero) {
content()
} else {
Spacer(modifier = Modifier.size(contentSize.value.width.pxToDp(), contentSize.value.height.pxToDp()))
}
}
}
fun Int.pxToDp(): Dp {
return (this / getSystem().displayMetrics.density).dp
}
usage:
Visibility(text.value.isNotEmpty()) {
IconButton(
onClick = { text.value = "" },
modifier = Modifier
.padding(bottom = 8.dp)
.height(30.dp),
) {
Icon(Icons.Filled.Close, contentDescription = "Clear text")
}
}