At the moment I'm setting the delay as a constant, instead I want to take the time that is used in the json animation, how can this be done?
Here is code where i used constant:
val composableScope = rememberCoroutineScope()
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
LottieExample()
composableScope.launch {
delay(2000L)
navController.navigate(viewModel.nextRoute) {
popUpTo(0)
}
}
}
And here is the animation code itself:
#Composable
fun LottieExample() {
val compositionResult: LottieCompositionResult = rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.splash_with_background)
)
val progress by animateLottieCompositionAsState(
composition = compositionResult.value,
isPlaying = true,
iterations = LottieConstants.IterateForever,
speed = 1.0f
)
LottieAnimation(
composition = compositionResult.value,
progress = progress,
modifier = Modifier.padding(all = 12.dp)
)
}
Took compositionResult out of bounds and pull out the duration of the animation.
Solved with next solution:
val composableScope = rememberCoroutineScope()
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
val compositionResult: LottieCompositionResult = rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.splash_with_background)
)
SplashScreenAnimation(compositionResult = compositionResult)
composableScope.launch {
delay(compositionResult.value?.duration?.toLong() ?: SPLASH_SCREEN_DURATION)
viewModel.destination.firstOrNull()?.let { route ->
navController.clearBackStackAndNavigateTo(route)
}
}
}
Related
I have following the step to make automatic viewpager in github conversation issue, and im getting bug in my viepager , im using jetpack compose.
I have following the step to make automatic viewpager in github conversation issue, and im getting bug in my viepager , im using jetpack compose.
My Pager Configruatin
Box {
HorizontalPager(count = items.size, state = pagerState) { page ->
AutomaticSliderCard(item = items[page],Modifier.onSizeChanged { pageSize = it })
}
**MyScreen
**
#OptIn(ExperimentalPagerApi::class)
#Composable
fun DashboardScreen(navController: NavController) {
val items = DashboardSlider1.getData()
val itemsBanner = dummyBannerDashboardItem
val itemsKenalan = dummyKenalanDashboardItem
val pagerState = rememberPagerState()
var pageSize by remember { mutableStateOf(IntSize.Zero) }
val lastIndex by remember(pagerState.currentPage) {
derivedStateOf { pagerState.currentPage == items.size - 1 }
}
val pagerStateBanner = rememberPagerState()
LaunchedEffect(Unit) {
while (true) {
yield()
delay(2000)
pagerState.animateScrollBy(
value = if (lastIndex) -(pageSize.width.toFloat() * items.size) else pageSize.width.toFloat(),
animationSpec = tween(if (lastIndex) 2000 else 1400)
)
}
}
#Composable
fun AutomaticSliderCard(item: DashboardSlider1, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.fillMaxWidth()
.height(200.dp)
) {
Image(
painter = painterResource(id = R.drawable.background_dashboard_slider1),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.align(Alignment.CenterStart)
.offset(y = -20.dp)
) {
Column(horizontalAlignment = Alignment.Start) {
Text(
text = item.title.toString(),
fontFamily = Poppins,
fontSize = 20.sp,
color = Color.White,
modifier = Modifier.padding(horizontal = 24.dp)
)
Text(
text = "${item.numberCount}",
fontFamily = Poppins,
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier
.padding(horizontal = 24.dp)
.offset(y = -10.dp)
)
}
Image(
painter = painterResource(id = item.image),
contentDescription = null,
modifier = Modifier.size(120.dp),
alignment = Alignment.CenterEnd
)
}
}
}
Try using
LaunchedEffect(Unit) {
snapshotFlow { pagerState.currentPage }
.debounce(2000L)
.onEach {
pagerState.animateScrollBy(
value = if (lastIndex) - (pageSize.width.toFloat() * items.size) else pageSize.width.toFloat(),
animationSpec = tween(if (lastIndex) 2000 else 1400)
)
}
.launchIn(this)
}
Here snapshotFlow will listen your current page and start animation if last changes was be make more than 2 seconds ago.
Try updating your LaunchedEffect key
LaunchedEffect(key1= Unit, key2= pagerState.currentPage) {
while (true) {
yield()
delay(2000)
pagerState.animateScrollBy(
value = if (lastIndex) -(pageSize.width.toFloat() * items.size) else pageSize.width.toFloat(),
animationSpec = tween(if (lastIndex) 2000 else 1400)
)
}
}
Also, i advice you to use animateScrollToPage instead of animateScrollBy:
LaunchedEffect(key1= Unit, key2= pagerState.currentPage) {
while (true) {
yield()
delay(2000)
var newPage = pagerState.currentPage + 1
if (newPage > items.lastIndex) newPage = 0
pagerState.animateScrollToPage(newPage)
}
}
I have my composable which I am displaying the content according to some states. First time I open the screen everything works fine but when I move my app to background and then come back it stops being recomposed. When I try to debug it I can see that the state is changing but for some reason my view is not being updated. Here is my composable:
val coroutineScope = rememberCoroutineScope()
val scrollState = rememberScrollState()
val isKeyboardOpen by keyboardAsState()
var buttonHeight by remember { mutableStateOf(0.dp) }
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.verticalScroll(scrollState)
.background(color = White),
) {
Text(
modifier = Modifier.padding(
start = 18.dp,
end = 18.dp,
top = 22.dp,
bottom = 12.dp
),
text = "text",
style = MaterialTheme.typography.subtitle1,
color = MaterialTheme.colors.onSurface
)
BasicTextField(
modifier = Modifier
.padding(horizontal = 18.dp)
.onGloballyPositioned { coordinates ->
if (isKeyboardOpen == Keyboard.Opened) {
coroutineScope.launch {
scrollState.animateScrollTo(coordinates.positionInParent().y.roundToInt())
}
}
},
onValueChange = { onEvent.invoke(Event.ReasonFieldValueChanged(it)) },
onFocused = { onEvent.invoke(Event.ReasonFieldFocused) }
)
Card(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 24.dp),
backgroundColor = MaterialTheme.colors.background,
elevation = 0.dp
) {
Text(
modifier = Modifier.padding(16.dp),
text = "text",
style = MaterialTheme.typography.body1.secondary
)
}
if (isKeyboardOpen == Keyboard.Closed) {
Spacer(modifier = Modifier.weight(1f))
CloseAccountButton(state, onEvent, isKeyboardOpen) {
buttonHeight = it
}
} else {
Spacer(modifier = Modifier.size(buttonHeight))
}
}
if (isKeyboardOpen == Keyboard.Opened) {
CloseAccountButton(state, onEvent, isKeyboardOpen) {
buttonHeight = it
}
}
And here is how I get keyboard state:
enum class Keyboard {
Opened, Closed
}
#Composable
fun keyboardAsState(): State<Keyboard> {
val keyboardState = remember { mutableStateOf(Keyboard.Closed) }
val view = LocalView.current
DisposableEffect(view) {
val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener {
val rect = Rect()
view.getWindowVisibleDisplayFrame(rect)
val screenHeight = view.rootView.height
val keypadHeight = screenHeight - rect.bottom
keyboardState.value = if (keypadHeight > screenHeight * 0.15) {
Keyboard.Opened
} else {
Keyboard.Closed
}
}
view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener)
onDispose {
view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener)
}
}
return keyboardState
}
As I said on first opening of the screen everything is fine but when coming back from background neither scrolling to position nor raacting to keyboard visibility is working. I don't have much experience with compose, what is wrong here?
I have a list with 10 items one of them have this elements "rankingCurrentPlace", "rankingPastPlace" and "isUser:true".
What i need to do its an animation on the lazycolumn if the api esponse is like this
"isUser:true", "rankingPastPlace:3" , "rankingCurrentPlace:7"
i need to show an animation in the list where the row starts in the third place and descend to the seventh place
is there a way to do this?
this is what I actually have
LazyColumn(
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
) {
items(
items = leaderboard,
key = { leaderBoard ->
leaderBoard.rankingPlace
}
) { leaderBoard ->
RowComposable( modifier = Modifier
.fillMaxWidth(),
topicsItem = leaderBoard,)
}
This answer works except when swapping first item with any item even with basic swap function without animation. I think it would be better to ask a new question about why swapping first item doesn't work or if it is bug. Other than that works as expected. If you need to move to items that are not in screen you can lazyListState.layoutInfo.visibleItemsInfo and compare with initial item and scroll to it before animation
1.Have a SnapshotStateList of data to trigger recomposition when we swap 2 items
class MyData(val uuid: String, val value: String)
val items: SnapshotStateList<MyData> = remember {
mutableStateListOf<MyData>().apply {
repeat(20) {
add(MyData( uuid = UUID.randomUUID().toString(), "Row $it"))
}
}
}
2.Function to swap items
private fun swap(list: SnapshotStateList<MyData>, from: Int, to: Int) {
val size = list.size
if (from in 0 until size && to in 0 until size) {
val temp = list[from]
list[from] = list[to]
list[to] = temp
}
}
3.Function to swap items one by one. There is a bug with swapping first item. Even if it's with function above when swapping first item other one moves up without showing animation via Modififer.animateItemPlacement().
#Composable
private fun animatedSwap(
lazyListState: LazyListState,
items: SnapshotStateList<MyData>,
from: Int,
to: Int,
onFinish: () -> Unit
) {
LaunchedEffect(key1 = Unit) {
val difference = from - to
val increasing = difference < 0
var currentValue: Int = from
repeat(abs(difference)) {
val temp = currentValue
if (increasing) {
currentValue++
} else {
currentValue--
}
swap(items, temp, currentValue)
if (!increasing && currentValue == 0) {
delay(300)
lazyListState.scrollToItem(0)
}
delay(350)
}
onFinish()
}
}
4.List with items that have Modifier.animateItemPlacement()
val lazyListState = rememberLazyListState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
state = lazyListState,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(
items = items,
key = {
it.uuid
}
) {
Row(
modifier = Modifier
.animateItemPlacement(
tween(durationMillis = 200)
)
.shadow(1.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.size(50.dp),
painter = painterResource(id = R.drawable.landscape1),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
Spacer(modifier = Modifier.width(10.dp))
Text(it.value, fontSize = 18.sp)
}
}
}
Demo
#OptIn(ExperimentalFoundationApi::class)
#Composable
private fun AnimatedList() {
Column(modifier = Modifier.fillMaxSize()) {
val items: SnapshotStateList<MyData> = remember {
mutableStateListOf<MyData>().apply {
repeat(20) {
add(MyData(uuid = UUID.randomUUID().toString(), "Row $it"))
}
}
}
val lazyListState = rememberLazyListState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
state = lazyListState,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(
items = items,
key = {
it.uuid
}
) {
Row(
modifier = Modifier
.animateItemPlacement(
tween(durationMillis = 200)
)
.shadow(1.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.size(50.dp),
painter = painterResource(id = R.drawable.landscape1),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
Spacer(modifier = Modifier.width(10.dp))
Text(it.value, fontSize = 18.sp)
}
}
}
var fromString by remember {
mutableStateOf("7")
}
var toString by remember {
mutableStateOf("3")
}
var animate by remember { mutableStateOf(false) }
if (animate) {
val from = try {
Integer.parseInt(fromString)
} catch (e: Exception) {
0
}
val to = try {
Integer.parseInt(toString)
} catch (e: Exception) {
0
}
animatedSwap(
lazyListState = lazyListState,
items = items,
from = from,
to = to
) {
animate = false
}
}
Row(modifier = Modifier.fillMaxWidth()) {
TextField(
value = fromString,
onValueChange = {
fromString = it
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
TextField(
value = toString,
onValueChange = {
toString = it
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
Button(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
onClick = {
animate = true
}
) {
Text("Swap")
}
}
}
Edit: Animating with Animatable
Another method for animating is using Animatable with Integer vector.
val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })
val coroutineScope = rememberCoroutineScope()
val animatable = remember { Animatable(0, IntToVector) }
And can be used as
private fun alternativeAnimate(
from: Int,
to: Int,
coroutineScope: CoroutineScope,
animatable: Animatable<Int, AnimationVector1D>,
items: SnapshotStateList<MyData>
) {
val difference = from - to
var currentValue: Int = from
coroutineScope.launch {
animatable.snapTo(from)
animatable.animateTo(to,
tween(350 * abs(difference), easing = LinearEasing),
block = {
val nextValue = this.value
if (abs(currentValue -nextValue) ==1) {
swap(items, currentValue, nextValue)
currentValue = nextValue
}
}
)
}
}
on button click, i'm getting values from TextField fo i convert from String
Button(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
onClick = {
val from = try {
Integer.parseInt(fromString)
} catch (e: Exception) {
0
}
val to = try {
Integer.parseInt(toString)
} catch (e: Exception) {
0
}
alternativeAnimate(from, to, coroutineScope, animatable, items)
}
) {
Text("Swap")
}
Result
I suggest you to get your items from a data class. If your other items does not contain the variables you mentioned you can make them nullable in data class and put a condition checker in your lazycolumn
Like this
data class Items(
val otherItems: Other,
val rankingCurrentPlace: Int?,
val rankingLastPlace: Int?,
val isUser: Boolean?
)
Then you can make a list from this data class and pass it to lazycolumn
LazyColumn{
items(list){
(elements with condition)
}
}
i want something like this image. tried many solution but no result.
I use this code to show how much of the page has been scrolled
BoxWithConstraints() {
val scrollState = rememberScrollState()
val viewMaxHeight = maxHeight.value
val columnMaxScroll = scrollState.maxValue
val scrollStateValue = scrollState.value
val paddingSize = (scrollStateValue * viewMaxHeight) / columnMaxScroll
val animation = animateDpAsState(targetValue = paddingSize.dp)
val scrollBarHeight = viewMaxHeight / items
Column(
Modifier
.verticalScroll(state = scrollState)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
if (scrollStateValue < columnMaxScroll) {
Box(
modifier = Modifier
.paddingFromBaseline(animation.value)
.padding(all = 4.dp)
.height(scrollBarHeight.dp)
.width(4.dp)
.background(
color = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.disabled),
shape = MaterialTheme.shapes.medium
)
.align(Alignment.TopEnd),
) {}
}
}
}
Result:
I have a ViewModel that keeps a timer:
class GameViewModel #Inject constructor(application: Application) : AndroidViewModel(application), PlayStopWatch.PlayClockListener {
val playTime = MutableLiveData<Long>()
override fun onPlayClockTick(elapsedTime: Long) {
playTime.value = elapsedTime
}
}
I would like to use it to update a clock in this composable but I don't think I'm understanding the documentation properly:
#Composable
fun PlayTime(viewModel: GameViewModel) {
val playTime by remember { mutableStateOf(viewModel.gameTime)}
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.icon_timer),
contentDescription = "Timer Icon",
tint = yellow,
modifier = Modifier
.size(64.dp)
)
Text(
"$playTime",
color = yellow,
fontFamily = Montserrat,
fontWeight = FontWeight(700),
fontSize = 60.sp,
letterSpacing = -0.5.sp,
lineHeight = 72.sp,
)
}//: End Row
Text(
text = "Tap Screen to Start Play",
color = white_97,
style = ff.h5
)
}
The view model is passed down from a view tree that includes this view
#Composable
fun StartPlayView(viewModel:GameViewModel) {
Surface(
color = background_primary,
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.weight(heightWeight(32f)))
MenuBar()
Spacer(modifier = Modifier.weight(heightWeight(32f)))
ScoresBar()
Spacer(modifier = Modifier.weight(heightWeight(76f)))
PauseButton()
Spacer(modifier = Modifier.weight(heightWeight(200f)))
PlayTime(viewModel)
Spacer(modifier = Modifier.weight(heightWeight(200f)))
BottomBar()
Spacer(modifier = Modifier.weight(heightWeight(16f)))
}
}
}
Which gets passed the viewModel from a NavGraphBuilder via Android's build in HiltViewModel:
#Composable
fun SetupNavGraph(navController: NavHostController) {
NavHost(
navController = navController,
startDestination = Screen.StartPlay.route
){
// Login View
composable(
route = Screen.Login.route
){
composable(route = Screen.StartPlay.route){ StartPlayView(hiltViewModel()) }
}
}
How do I keep the TextView in the PlayTime view display the current time on the timer in real time?
you could try something like this. the delay() is in millis, so the playTime will only be updated every seconds. just change that based on your needs
fun startPlayTimeCounter() {
job = coroutineScope.launch {
val initialTime = System.currentTimeMillis()
var playTime: Long
while (true) {
playTime = System.currentTimeMillis() - initialTime
delay(1000L)
}
}
}
and stop the timer by cancelling the job
fun stopPlayTimeCounter() {
job?.cancel()
}