When adding an enter and exit transition to AnimatedVisibility, if the composable animated has an offset modifier, the transition is not visible:
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(1000)),
exit = fadeOut(animationSpec = tween(1000))
) {
Text(
text = "Hello, world!", modifier = Modifier
// This will make the transition not working
//.offset(100.dp, 0.dp)
)
}
Button(onClick = { visible = !visible }) {
Text("Click Me")
}
This is the animation without the offset:
and this is the animation with the offset:
Is there a way to make it work?
Don't out the offset modifier onto the Text. Put it onto AnimatedVisibility instead:
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(1000)),
exit = fadeOut(animationSpec = tween(1000)),
modifier = Modifier.offset(100.dp, 0.dp)
) {
Text(text = "Hello, world!")
}
Related
I'm trying to create a multi-item floating action button with the following animation:
I created a multi-item floating action button but I could not implement the intended animation.
I have FilterFabMenuButton composable that I show as a menu item :
FilterFabMenuButton
#Composable
fun FilterFabMenuButton(
item: FilterFabMenuItem,
onClick: (FilterFabMenuItem) -> Unit,
modifier: Modifier = Modifier
) {
FloatingActionButton(
modifier = modifier,
onClick = {
onClick(item)
},
backgroundColor = colorResource(
id = R.color.primary_color
)
) {
Icon(
painter = painterResource(item.icon), contentDescription = null, tint = colorResource(
id = R.color.white
)
)
}
}
I have FilterFabMenuLabel composable which is a label for FilterFabMenuButton:
FilterFabMenuLabel
#Composable
fun FilterFabMenuLabel(
label: String,
modifier: Modifier = Modifier
) {
Surface(
modifier = modifier,
shape = RoundedCornerShape(6.dp),
color = Color.Black.copy(alpha = 0.8f)
) {
Text(
text = label, color = Color.White,
modifier = Modifier.padding(horizontal = 20.dp, vertical = 2.dp),
fontSize = 14.sp,
maxLines = 1
)
}
}
I have FilterFabMenuItem composable which is a Row that contains FilterFabMenuLabel and FilterFabMenuButton composables:
FilterFabMenuItem
#Composable
fun FilterFabMenuItem(
menuItem: FilterFabMenuItem,
onMenuItemClick: (FilterFabMenuItem) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically
) {
//label
FilterFabMenuLabel(label = menuItem.label)
//fab
FilterFabMenuButton(item = menuItem, onClick = onMenuItemClick)
}
}
I have FilterFabMenu composable which is a Column that shows menu items:
FilterFabMenu
#Composable
fun FilterFabMenu(
visible: Boolean,
items: List<FilterFabMenuItem>,
modifier: Modifier = Modifier
) {
val enterTransition = remember {
expandVertically(
expandFrom = Alignment.Bottom,
animationSpec = tween(150, easing = FastOutSlowInEasing)
) + fadeIn(
initialAlpha = 0.3f,
animationSpec = tween(150, easing = FastOutSlowInEasing)
)
}
val exitTransition = remember {
shrinkVertically(
shrinkTowards = Alignment.Bottom,
animationSpec = tween(150, easing = FastOutSlowInEasing)
) + fadeOut(
animationSpec = tween(150, easing = FastOutSlowInEasing)
)
}
AnimatedVisibility(visible = visible, enter = enterTransition, exit = exitTransition) {
Column(
modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
items.forEach { menuItem ->
FilterFabMenuItem(
menuItem = menuItem,
onMenuItemClick = {}
)
}
}
}
}
I have FilterFab composable that expands/collapses FilterMenu:
FilterFab
#Composable
fun FilterFab(
state: FilterFabState,
rotation:Float,
onClick: (FilterFabState) -> Unit,
modifier: Modifier = Modifier
) {
FloatingActionButton(
modifier = modifier
.rotate(rotation),
elevation = FloatingActionButtonDefaults.elevation(defaultElevation = 0.dp),
onClick = {
onClick(
if (state == FilterFabState.EXPANDED) {
FilterFabState.COLLAPSED
} else {
FilterFabState.EXPANDED
}
)
},
backgroundColor = colorResource(
R.color.primary_color
),
shape = CircleShape
) {
Icon(
painter = painterResource(R.drawable.fab_add),
contentDescription = null,
tint = Color.White
)
}
}
Last but not least, I have a FilterView composable which is a Column that contains FilterFabMenu and FilterFab composables:
FilterView
#SuppressLint("UnusedTransitionTargetStateParameter")
#Composable
fun FilterView(
items: List<FilterFabMenuItem>,
modifier: Modifier = Modifier
) {
var filterFabState by rememberSaveable() {
mutableStateOf(FilterFabState.COLLAPSED)
}
val transitionState = remember {
MutableTransitionState(filterFabState).apply {
targetState = FilterFabState.COLLAPSED
}
}
val transition = updateTransition(targetState = transitionState, label = "transition")
val iconRotationDegree by transition.animateFloat({
tween(durationMillis = 150, easing = FastOutSlowInEasing)
}, label = "rotation") {
if (filterFabState == FilterFabState.EXPANDED) 230f else 0f
}
Column(
modifier = modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(16.dp,Alignment.Bottom)
) {
FilterFabMenu(items = items, visible = filterFabState == FilterFabState.EXPANDED)
FilterFab(
state = filterFabState,
rotation = iconRotationDegree, onClick = { state ->
filterFabState = state
})
}
}
This produces the following result:
expandVertically in your enterTransition is not the correct approach for this kind of animation. Per documentation, it animates a clip revealing the content of the animated item from top to bottom, or vice versa. You apply this animation to the entire column (so all items at once), resulting in the gif you have shown us.
Instead, you should use a different enter/exit animation type, maybe a custom animation where you work with the item scaling to emulate the "pop in" effect like such:
scaleFactor.animateTo(2f, tween(easing = FastOutSlowInEasing, durationMillis = 50))
scaleFactor.animateTo(1f, tween(easing = FastOutSlowInEasing, durationMillis = 70))
(the scaleFactor is an animatabale of type Animatable<Float, AnimationVector1D> in this instance).
Then you create such an animatable for each of the column items, i.e. your menu items. After that, just run the animations in a for loop for each menu item inside a coroutine scope (since compose animations are suspend by default, they will run in sequence, use async/awaitAll if you want to do it in parallel).
Also, don't forget to put your animatabales in remember {}, then just use the values you are animating like scaleFactor inside modifiers of your column items, and trigger them inside a LaunchedEffect when you click to expand/close the menu.
I have a AnimatedVisibility component in my screenA. And I also have a screenB. When the component is visible, navigating to screenB and then back will sure turn the component into invisble state. I wish it can stick to visible state. I know there is a EnterExiteState.Visible. Will this help? What should I do?
Here is my set. A button is used to change the value of editable between true and false.
var editable by rememberSaveable { mutableStateOf(false) }
AnimatedVisibility(
visible = editable,
modifier = Modifier.constrainAs(layer) {
bottom.linkTo(parent.bottom, 20.dp)
start.linkTo(parent.start, 16.dp)
},
enter = slideInVertically {
// Slide in from 40 dp from the top.
with(density) { -40.dp.roundToPx() }
} + expandVertically(
// Expand from the top.
expandFrom = Alignment.Top
) + fadeIn(
// Fade in with the initial alpha of 0.3f.
initialAlpha = 0.3f
),
exit = slideOutVertically{
// Slide in from 40 dp from the top.
with(density) { 40.dp.roundToPx() }
} + shrinkVertically( shrinkTowards = Alignment.Bottom )
+ fadeOut()
) {
Card(
shape = RoundedCornerShape(20.dp),
elevation = CardDefaults.cardElevation(1.dp)
) {
InfoPanel()
}
}
I am trying to build a screen with the content starting right behind the TopAppBar, using accompanist-insets. However, the app bar is always transparent.
How to make it a solid/non-transparent navigation bar?
Current Output
Code
val scrollState = rememberLazyListState()
var isScrollStateChanged by remember { mutableStateOf(false) }
isScrollStateChanged = scrollState.firstVisibleItemScrollOffset != 0
val position by animateFloatAsState(if (isScrollStateChanged) 0f else -45f)
val listItems = (1..100).toList()
ProvideWindowInsets {
Surface(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding(),
color = Color.White
) {
Box(Modifier.fillMaxSize()) {
TopAppBar(
content = { Text("Hello nav bar!") },
modifier = Modifier
.offset(0.dp, position.dp)
.alpha(min(1f, 1 + (position / 45f)))
.navigationBarsPadding(bottom = false),
backgroundColor = Color.Red
)
LazyColumn(
state = scrollState
) {
items(items = listItems) {
Text(
"Hello $it",
Modifier
.padding(20.dp)
.fillMaxWidth()
)
}
}
}
}
}
It's doesn't have transparent background. Actually it's LazyColumn has transparent background, and TopAppBar is placed underneath.
Using Box you put items on top of each other, so second one will be under the first one. Reorder them:
Box(Modifier.fillMaxSize()) {
LazyColumn(
state = scrollState
) {
items(items = listItems) {
Text(
"Hello $it",
Modifier
.padding(20.dp)
.fillMaxWidth()
)
}
}
TopAppBar(
content = { Text("Hello nav bar!") },
modifier = Modifier
.offset(0.dp, position.dp)
.alpha(min(1f, 1 + (position / 45f)))
.navigationBarsPadding(bottom = false),
backgroundColor = Color.Red
)
}
The text in one of my Composables is struck-through when a certain boolean variable is true. How can I animate this change in TextStyle on recomposition so that the line fades in rather than appearing and disappearing abruptly?
#Composable
fun MyComposable(
completed: Boolean
) {
val textStyle = TextStyle(textDecoration = if (completed) TextDecoration.LineThrough else null)
Text(
text = title,
color = textColor,
style = textStyle,
modifier = Modifier.align(Alignment.CenterVertically)
)
Not sure if it exists a way to animate a TextStyle.
Not a great solution, but just a workaround:
Box() {
AnimatedVisibility(
visible = !completed,
enter = fadeIn(
animationSpec = tween(durationMillis = duration)
),
exit = fadeOut(
animationSpec = tween(durationMillis = duration)
)) {
Text(
text = title,
style = TextStyle(textDecoration=null)
)
}
AnimatedVisibility(
visible = completed,
enter = fadeIn(
animationSpec = tween(durationMillis = duration)
),
exit = fadeOut(
animationSpec = tween(durationMillis = duration)
)) {
Text(
text = title,
style = TextStyle(textDecoration = TextDecoration.LineThrough),
)
}
}
Gabriele's answer is also a decent workaround, but perhaps a simpler one would be to put the Text and the "Stroke", in a box, overlapping. Say,
#Composable
fun MyComposable(
completed: Boolean
) {
Box{
Text(
text = title,
color = textColor,
style = textStyle,
modifier = Modifier.align(Alignment.CenterVertically)
)
AnimatedVisibility (completed) {
Box(Modifier.width(1.dp)){
//Empty Box of unit width to serve as the stroke
// Add background modifiers to match the font color
}
}
}
Building a simple keyboard is fairly simple and straightforward in Jetpack Compose.
I built a really simple KeyRow by using this:
Key.kt
#Composable
fun Key(modifier: Modifier = Modifier, label: String, onClick: () -> Unit) {
val shape = RoundedCornerShape(4.dp)
//TODO: make clickable outside but don't show ripple
Box(modifier = modifier
.padding(2.dp)
.clip(shape)
.clickable(onClick = onClick)
.background(Color.White)
.padding(vertical = 12.dp, horizontal = 4.dp), contentAlignment = Alignment.Center) {
Text(text = label, fontSize = 20.sp)
}
}
KeyRow.kt
#Composable
fun KeyRow(keys: List<String>) {
Row(modifier = Modifier.fillMaxWidth().background(color = grey200)) {
keys.forEach {
Key(modifier = Modifier.weight(1f), label = it, onClick = { })
}
}
}
That's what it looks like:
I want to achieve this animation:
However, I'm currently stuck with this
![4]
Hierachy
-Keyboard
--KeyRow
---KeyLayout
----Key
----KeyPressedOverlay (only visible when pressed)
My main problem is that I don't know how to show the KeyPressedOverlay Composale (which is larger than the Key Composable) without making the parent Layout larger. As a result, I need to overflow the parent layout in some way.
Not sure if it's the best way (probably not), but I found a solution using ConstraintLayout...
val keys = listOf("A", "B", "C", "D")
ConstraintLayout(
modifier = Modifier.graphicsLayer(clip = false)
) {
val refs = keys.map { createRef() }
refs.forEachIndexed { index, ref ->
val modifier = when (index) {
0 -> Modifier.constrainAs(ref) {
start.linkTo(parent.start)
}
refs.lastIndex -> Modifier.constrainAs(ref) {
start.linkTo(refs[index - 1].end)
end.linkTo(parent.end)
}
else -> Modifier.constrainAs(ref) {
start.linkTo(refs[index - 1].end)
end.linkTo(refs[index + 1].start)
}
}
val modifierPressed = Modifier.constrainAs(createRef()) {
start.linkTo(ref.start)
end.linkTo(ref.end)
bottom.linkTo(ref.bottom)
}
KeyboardKey(
keyboardKey = keys[index],
modifier = modifier,
modifierPressed = modifierPressed,
pressed = { s -> /* Do something with the key */}
)
}
}
One important detail here is graphicLayer(clip = false) (which is similar to the clipChildren in View Toolkit). Then, I'm creating a modifier to each key and to the pressed key. Noticed that the modifierPressed is aligned to the center/bottom of the other modifier.
Finally the KeyboardKey is described below.
#Composable
fun KeyboardKey(
keyboardKey: String,
modifier: Modifier,
modifierPressed: Modifier,
pressed: (String) -> Unit
) {
var isKeyPressed by remember { mutableStateOf(false) }
Text(keyboardKey, Modifier
.then(modifier)
.pointerInput(Unit) {
detectTapGestures(onPress = {
isKeyPressed = true
val success = tryAwaitRelease()
if (success) {
isKeyPressed = false
pressed(keyboardKey)
} else {
isKeyPressed = false
}
})
}
.background(Color.White)
.padding(
start = 12.dp,
end = 12.dp,
top = 16.dp,
bottom = 16.dp
),
color = Color.Black
)
if (isKeyPressed) {
Text(
keyboardKey, Modifier
.then(modifierPressed)
.background(Color.White)
.padding(
start = 16.dp,
end = 16.dp,
top = 16.dp,
bottom = 48.dp
),
color = Color.Black
)
}
}
This is the result I got:
Edit:
Adding some more logic, I was able to get this...
I hope it helps this time ;)
Here's the gist just in case...
https://gist.github.com/nglauber/4cb1573efba9024c008ea71f3320b4d8
I guess you're looking for the pressIndicatorGestureFilter modifier...
I tried this and worked for me...
var pressed by remember { mutableStateOf(false) }
val padding = if (pressed) 32.dp else 16.dp
Text("A", Modifier
.pressIndicatorGestureFilter(
onStart = {
pressed = true
},
onStop = {
pressed = false
},
onCancel = {
pressed = false
}
)
.background(Color.White)
.padding(start = 16.dp, end = 16.dp, top = padding, bottom = padding)
)