Sliding an item next to the expanding animation - android

I've got the following animation:
The problem is:
When animation's starting the search icon (magnifier) slides immediately to the left of the screen.
When the search bar is folding back the icon moves smoothly and near the end speed up.
What I want to achieve here is to make this icon slides more smoothly for a better experience.
Is there any way to achieve that?
Code responsible for animation:
IconButton(onClick = {
isSearchEnabled = !isSearchEnabled
}) {
Icon(Icons.Default.Search, "search")
}
AnimatedVisibility(
visible = isSearchEnabled,
enter = fadeIn(
animationSpec = tween(durationMillis = 300)
) + slideInHorizontally(
initialOffsetX = { it / 2 },
animationSpec = tween(durationMillis = 700)
),
exit = fadeOut(
animationSpec = tween(300, easing = FastOutLinearInEasing)
) + shrinkHorizontally(
shrinkTowards = Alignment.End,
animationSpec = tween(durationMillis = 700, easing = FastOutLinearInEasing)
)
) {
TextField(
modifier = Modifier.padding(end = 16.dp),
shape = RoundedCornerShape(10.dp),
value = text,
onValueChange = { text = it; onValueChange(it) })
}

This would expand and shrink the search bar,
#ExperimentalAnimationApi
#Composable
fun ExpandableSearchbar() {
var text by remember {
mutableStateOf("")
}
var isSearchEnabled by remember {
mutableStateOf(false)
}
val slow = 700
val fast = 300
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.background(Color(0xFFE2E2E2))
.height(120.dp),
) {
IconButton(
onClick = {
isSearchEnabled = !isSearchEnabled
},
) {
Icon(Icons.Default.Search, "search")
}
AnimatedVisibility(
visible = isSearchEnabled,
enter = fadeIn(
animationSpec = tween(durationMillis = fast)
) + expandHorizontally(
expandFrom = Alignment.End,
animationSpec = tween(
durationMillis = slow,
easing = FastOutLinearInEasing,
)
),
exit = fadeOut(
animationSpec = tween(
durationMillis = slow,
easing = FastOutLinearInEasing,
)
) + shrinkHorizontally(
shrinkTowards = Alignment.End,
animationSpec = tween(
durationMillis = slow,
easing = FastOutLinearInEasing,
)
)
) {
TextField(
modifier = Modifier.padding(end = 16.dp),
shape = RoundedCornerShape(10.dp),
value = text,
onValueChange = {
text = it
},
)
}
}
}

Related

push an animation in composable function to start again whenever I click a button/ JetPack/ Kotlin

**every time the user click the button i want to show different icon inside the box and the transition should start again from its initial state **
fun card(iconColor: Color){
val animateShape = remember { Animatable(50f) }
Card(
modifier = Modifier
.size(100.dp)
.padding(5.dp)
.border(width = Dp(animateShape.value), White, shape = CircleShape),
shape = CircleShape,
elevation = 5.dp,
backgroundColor = BlueGrey,
) {
Icon(
imageVector = Icons.Default.DoneAll,
contentDescription = null,
tint = iconColor,
modifier = Modifier
.padding(12.dp)
)
}
LaunchedEffect(iconColor) {
animateShape.animateTo(
targetValue = 2f,
animationSpec = repeatable(
animation = tween(
durationMillis = 3000,
easing = LinearEasing,
delayMillis = 500
),
iterations = 1
)
)
}
}```
I'm not quite sure, but are you looking for something like this?, every click it changes the Icon with scaleIn/scaleOut animation
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun SwitchIconOnClick() {
var iconState by remember { mutableStateOf("State_1") }
AnimatedContent(
targetState = iconState,
contentAlignment = Alignment.Center,
transitionSpec = {
scaleIn(animationSpec = tween(durationMillis = 200)) with
scaleOut(animationSpec = tween(durationMillis = 200))
}
) { state ->
Box(
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
.clickable {
when (state) {
"State_1" -> {
iconState = "State_2"
}
"State_2" -> {
iconState = "State_3"
}
"State_3" -> {
iconState = "State_1"
}
}
},
contentAlignment = Alignment.Center
) {
Icon(imageVector = when (state) {
"State_1" -> {
Icons.Rounded.NotificationAdd
}
"State_2" -> {
Icons.Rounded.ClosedCaption
}
"State_3" -> {
Icons.Rounded.Delete
}
else -> {
Icons.Rounded.DataArray
}
},
contentDescription = null
)
}
}
}

Android Compose create shake animation

I am trying to make shaking animation of shape in Jetpack Compose. I want to use this animation to show error when user enters invalid Pin code. But all I can find is slide in, slide out animations and some scale animations. Any ideas how I can accomplish this?
Update:
After #Thracian answer. I used code as below, shaking my items horizontally:
fun Modifier.shake(enabled: Boolean, onAnimationFinish: () -> Unit) = composed(
factory = {
val distance by animateFloatAsState(
targetValue = if (enabled) 15f else 0f,
animationSpec = repeatable(
iterations = 8,
animation = tween(durationMillis = 50, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
finishedListener = { onAnimationFinish.invoke() }
)
Modifier.graphicsLayer {
translationX = if (enabled) distance else 0f
}
},
inspectorInfo = debugInspectorInfo {
name = "shake"
properties["enabled"] = enabled
}
)
Gif is slower than actual animation unfortunately but it gives an idea of outcome.
This can be done in many ways. You should change scaleX or scaleY or both in short time duration to have a shake effect. If you wish to have rotation change rotationZ of Modifier.graphicsLayer either
#Composable
private fun ShakeAnimationSamples() {
Column(modifier = Modifier
.fillMaxSize()
.padding(10.dp)) {
var enabled by remember {
mutableStateOf(false)
}
val scale by animateFloatAsState(
targetValue = if (enabled) .9f else 1f,
animationSpec = repeatable(
iterations = 5,
animation = tween(durationMillis = 50, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
),
finishedListener = {
enabled = false
}
)
val infiniteTransition = rememberInfiniteTransition()
val scaleInfinite by infiniteTransition.animateFloat(
initialValue = 1f,
targetValue = .85f,
animationSpec = infiniteRepeatable(
animation = tween(30, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
val rotation by infiniteTransition.animateFloat(
initialValue = -10f,
targetValue = 10f,
animationSpec = infiniteRepeatable(
animation = tween(30, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Icon(
imageVector = Icons.Default.NotificationsActive,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.graphicsLayer {
scaleX = if (enabled) scale else 1f
scaleY = if (enabled) scale else 1f
}
.background(Color.Red, CircleShape)
.size(50.dp)
.padding(10.dp)
)
Icon(
imageVector = Icons.Default.NotificationsActive,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.graphicsLayer {
scaleX = scaleInfinite
scaleY = scaleInfinite
rotationZ = rotation
}
.background(Color.Red, CircleShape)
.size(50.dp)
.padding(10.dp)
)
Button(onClick = { enabled = !enabled }) {
Text("Animation enabled: $enabled")
}
}
}
Also you can do it as a Modifier either
fun Modifier.shake(enabled: Boolean) = composed(
factory = {
val scale by animateFloatAsState(
targetValue = if (enabled) .9f else 1f,
animationSpec = repeatable(
iterations = 5,
animation = tween(durationMillis = 50, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Modifier.graphicsLayer {
scaleX = if (enabled) scale else 1f
scaleY = if (enabled) scale else 1f
}
},
inspectorInfo = debugInspectorInfo {
name = "shake"
properties["enabled"] = enabled
}
)
Usage
Icon(
imageVector = Icons.Default.NotificationsActive,
contentDescription = null,
tint = Color.White,
modifier = Modifier
.shake(enabled)
.background(Color.Red, CircleShape)
.size(50.dp)
.padding(10.dp)
)

zIndex is not refeshing as result of state change

I managed to work this out, and setup 3 cards one on top of the other as seperate boxs compose elements with onclick and on drag properties.
The issue is now, that I'd like the card that I'm pressing/dragging to set to the front, so, I played with the z-index modifier, but, it looks like I'm doing something wrong. Any idea?
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Test1Theme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
for (i in 1 until 4) {
DraggableBox(title = "Box_${+1}", initX = 100f*i.toFloat(), initY = 100f, content =
{
Text(text = "Box_${i}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
)
}
}
}
}
}
}
#Composable
fun DraggableBox(title: String, initX: Float = 0f, initY: Float = 0f, content: #Composable() () -> Unit) {
val cardInitWidth = 135f
val cardInitHeight = 190f
val expandValue = 20f
Box(
modifier = Modifier
.fillMaxSize()
) {
val shape = RoundedCornerShape(12.dp)
val coroutineScope = rememberCoroutineScope()
val enable = remember { mutableStateOf(true) }
var offsetX = remember { Animatable(initialValue = initX) }
var offsetY = remember { Animatable(initialValue = initY) }
val interactionSource = remember { MutableInteractionSource() }
val clickable = Modifier.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current
) { }
val isPressed by interactionSource.collectIsPressedAsState()
val size = animateSizeAsState(
targetValue = if (enable.value && !isPressed) {
Size(width = cardInitWidth, height = cardInitHeight)
} else {
Size(width = cardInitWidth + expandValue, height = cardInitHeight + expandValue)
}
)
Box(
Modifier
.offset {
IntOffset(
x = offsetX.value.roundToInt(),
y = offsetY.value.roundToInt()
)
}
.zIndex(zIndex = if (enable.value && !isPressed) 5f else 0f)
.size(size.value.width.dp, size.value.height.dp)
.clip(shape)
//.background(Color(0xFF5FA777))
.background(color = MaterialTheme.colors.primary)
.border(BorderStroke(2.dp, Color.Black), shape = shape)
.pointerInput(Unit) {
detectDragGestures(
onDragStart = {
enable.value = !enable.value
},
onDrag = { change, dragAmount ->
change.consumeAllChanges()
coroutineScope.launch {
offsetX.snapTo(targetValue = offsetX.value + dragAmount.x)
offsetY.snapTo(targetValue = offsetY.value + dragAmount.y)
}
spring(stiffness = Spring.StiffnessHigh, visibilityThreshold = 0.1.dp)
},
onDragEnd = {
enable.value = !enable.value
spring(stiffness = Spring.StiffnessLow, visibilityThreshold = 0.1.dp)
coroutineScope.launch {
launch {
offsetY.animateTo(
targetValue = initY,
animationSpec = tween(
durationMillis = 700,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)
}
offsetX.animateTo(
targetValue = initX,
animationSpec = tween(
durationMillis = 700,
delayMillis = 50,
easing = LinearOutSlowInEasing
)
)
}
}
)
}
.then(clickable)
) {
Row (modifier = Modifier
.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically
)
{
Column (modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
)
{
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(text = "init-X: ${initX.toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
Text(text = "init-Y: ${initY.toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
Text(text = "offset-X: ${offsetX.value.roundToInt().toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
Text(text = "offset-Y: ${offsetY.value.roundToInt().toString()}", color = Color.White, fontSize = 16.sp, textAlign = TextAlign.Center)
}
Column (
horizontalAlignment = Alignment.CenterHorizontally
)
{
content()
}
}
}
}
}
}
The Modifier.zIndex works only for children within the same parent.
In your case you should move this modifier to the topmost Box. To do so you have to move enable and isPressed one level up too, and I would move all the other variables as well - but that's just a matter of taste, I guess.
val enable = remember { mutableStateOf(true) }
val isPressed by interactionSource.collectIsPressedAsState()
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(zIndex = if (enable.value && !isPressed) 5f else 0f)
) {
// ...

Reserve space for invisible items in Jetpack Compose

I am trying to animate an image and a text field in my News App.
I want the Icon to appear first and after a few seconds the text.
The problem that I am facing is that when the icon loads(first) then it is in the absolute center and when text becomes visible, the icon shifts a little bit upwards. I want to stop this shift of the icon which was loaded first.
Or more precisely, how can I allocate space for an invisible item on screen?
Here is my Code:
#Composable
fun SplashScreen(
navController: NavController
){
var imageVisibility by remember{
mutableStateOf(false)
}
var textVisibility by remember{
mutableStateOf(false)
}
LaunchedEffect(Unit){
delay(1000)
imageVisibility = true
delay(5000)
textVisibility = true
delay(5000)
navController.navigate(Destinations.HomeScreen.route)
}
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
Column{
AnimatedVisibility(
visible = imageVisibility,
enter = fadeIn(
TweenSpec(
durationMillis = 3000
)
)
) {
Image(
modifier = Modifier.fillMaxWidth(),
painter = painterResource(id = R.drawable.news_splash_screen),
contentDescription = "News Splash Screen"
)
}
AnimatedVisibility(
visible = textVisibility,
enter = fadeIn(
TweenSpec(
durationMillis = 3000
)
)
){
Text(
modifier = Modifier.fillMaxWidth(),
text = "Read News Everyday",
textAlign = TextAlign.Center
)
}
}
}
}
Use animateFloatAsState to animate alpha, instead of AnimatedVisibility for the Text.
Sample code,
#Composable
fun SplashScreen(
navHostController: NavController,
) {
var imageVisibility by remember {
mutableStateOf(false)
}
var textVisibility by remember {
mutableStateOf(false)
}
LaunchedEffect(Unit) {
delay(1000)
imageVisibility = true
delay(5000)
textVisibility = true
delay(5000)
// navHostController.navigate("second")
}
val alpha: Float by animateFloatAsState(
targetValue = if (textVisibility) {
1f
} else {
0f
},
animationSpec = tween(
durationMillis = 3000,
easing = LinearEasing,
),
)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column {
AnimatedVisibility(
visible = imageVisibility,
enter = fadeIn(
TweenSpec(
durationMillis = 3000
)
)
) {
Image(
modifier = Modifier.fillMaxWidth(),
painter = painterResource(id = R.drawable.ic_launcher_background),
contentDescription = "News Splash Screen"
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.alpha(
alpha = alpha,
),
text = "Read News Everyday",
textAlign = TextAlign.Center
)
}
}
}

How can I control the duration of AnimatedVisibility in compose?

I have used compose AnimatedVisibility in my project,
But the anim is to short for my need.
Is there a related API?
Here is an example taken from codelabs. You can add your own animation specs with the durationMillis of each animation, one for entering, and one for exiting:
AnimatedVisibility(
visible = shown,
enter = slideInVertically(
// Enters by sliding down from offset -fullHeight to 0.
initialOffsetY = { fullHeight -> -fullHeight },
animationSpec = tween(durationMillis = 150, easing = LinearOutSlowInEasing)
),
exit = slideOutVertically(
// Exits by sliding up from offset 0 to -fullHeight.
targetOffsetY = { fullHeight -> -fullHeight },
animationSpec = tween(durationMillis = 250, easing = FastOutLinearInEasing)
)
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = MaterialTheme.colors.secondary,
elevation = 4.dp
) {
Text(
text = stringResource(R.string.edit_message),
modifier = Modifier.padding(16.dp)
)
}
}
Well this is one way
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun AnimatedVisibilityMark2(content: #Composable () -> Unit, visible: Boolean, durationMillis: Int) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(
animationSpec = keyframes {
this.durationMillis = durationMillis
}
),
exit = fadeOut(
animationSpec = keyframes {
this.durationMillis = durationMillis
}
)
){
content()
}
}

Categories

Resources