I have a Column with remember scroll state. I am using a derived state as follows.
val isHidden = remember {
derivedStateOf {
columnState.value > 400
}
}
I want to display a search bar whenever it scrolls beyond 400 pixels.
Another approach i used is to create a state based on offset of first item in Column using function onGloballyPositioned.
The modifier of first item is as follows:
Box(modifier = Modifier
.graphicsLayer { translationX = horizontalOffset.value }
.onGloballyPositioned { coordinates ->
isHidden.value = coordinates.boundsInRoot().top == 0f
}) {
The problem is no matter which approach i choose it always lags when the state is changed (when the value checks for < or > 400) or in case of onGloballyPositioned if the Compose reaches top of the screen i.e. y coordinate of view == 0.
In both of the approaches isHidden is going to be true. Changing is hidden in both cases causing lag at the point of change.
I can't use lazy row because of some restrictions. Is there any solution to implement this without the lag.
Related
I have a SwipeToDismiss instance to delete items with dismissThresholds 75%.
If user swipes row too fast without reaching 75% threshold the row being deleted. How to prevent that?
Here is code where I execute an action:
val dismissState = rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToStart) {
viewModel.deleteCity(city)
}
true
}
)
I ran into the same issue and found a fix that works for me. My theory is, that when the swipe is very fast but doesn't go very far (doesn't reach the set fractional threshold), the layout just immediately resets. In this case (DismissToStart), meaning the view snaps back to the right screen edge, giving us the value of 1.0f for the threshold and thus triggering the confirmStateChange because the fraction is per definition higher than our threshold. The problem being that our threshold is measured from the right screen edge, and this fracion (in my theory) is measured from the left screen edge.
So my solution is to track the current fractional value, and inside confirmStateChange check if the current value is higher than the threshold, BUT NOT 1.0f. In a real world scenario, I think it's impossible to reach 1.0 with actual swiping the finger from right to left, so the solution seems safe to me.
val dismissThreshold = 0.25f
val currentFraction = remember { mutableStateOf(0f) }
val dismissState = rememberDismissState(
confirmStateChange = {
if (it == DismissValue.DismissedToStart) {
if (currentFraction.value >= dismissThreshold && currentFraction.value < 1.0f) {
onSwiped(item)
}
}
dismissOnSwipe
}
)
SwipeToDismiss(
state = dismissState,
modifier = Modifier.animateItemPlacement(),
directions = setOf(DismissDirection.EndToStart),
dismissThresholds = { direction ->
FractionalThreshold(dismissThreshold)
},
background = {
Box(...) {
currentFraction.value = dismissState.progress.fraction
...
}
}
dismissContent = {...}
)
I'm dealing with the same issue, it seems to be a feature, not a bug, but I did not find the way to disable it.
*If the user has dragged their finger across over 50% of the screen
width, the app should trigger the rest of the swipe back animation. If
it's less than that, the app should snap back to the full app view.
If the gesture is quick, ignore the 50% threshold rule and swipe back.*
https://developer.android.com/training/wearables/compose/swipe-to-dismiss
How do I know a LazyColumn can be navigated?
For example, a list contains 100 elements, then it is a lazy column that can be scroll and we show a fab button to scroll up, but when this list contains 5 elements, the lazy column is not scrollable and the FabButton is no longer needed.
The lazy list layout information can be obtained from state, which is passed to LazyColumn.
In my example, I use derivedStateOf to prevent redundant recompositions - this will cause a recomposition only when the value actually changes. Reading LazyColumn state information without derivedStateOf or side effect function can cause significant performance problems.
The most basic logic in such cases is to check if the first element has been scrolled:
val canScrollToTop by remember {
derivedStateOf {
state.layoutInfo.visibleItemsInfo.run {
isNotEmpty() && (first().index > 0 || first().offset < 0)
}
}
}
LazyColumn(state = state) {
// ...
}
if (canScrollToTop) {
// your button
}
I'm having troubles with some animation in a recycler view. I do the relevant measurements in onViewAttachedToWindow:
override fun onViewAttachedToWindow(holder: PairingViewHolder) {
super.onViewAttachedToWindow(holder)
// get originalHeight & expandedHeight if not gotten before
if (holder.expandedHeight < 0) {
// Execute pending bindings, otherwise the measurement will be wrong.
holder.itemViewDataBinding.executePendingBindings()
holder.cardContainer.layoutParams.width = expandedWidth
holder.expandedHeight = 0 // so that this block is only called once
holder.cardContainer.doOnLayout { view ->
holder.originalHeight = view.height
holder.expandView.isVisible = true
// show expandView and record expandedHeight in next layout pass
// (doOnPreDraw) and hide it immediately.
view.doOnPreDraw {
holder.expandedHeight = view.height
holder.expandView.isVisible = false
holder.cardContainer.layoutParams.width = originalWidth
}
}
}
}
The problem is that doOnPreDraw gets called just for some views. It is something related to the visibility of the views I guess, since the smaller the items (expanded) are, the highest the count of the ones on which onPreDraw gets called.
My guess is that since I'm expanding them in onLayout, the recyclerView consider visible only the ones that when expanded are actually visible on screen. In onPreDraw I collapse them, resulting in some views being able to animate correctly and some not.
How would you solve this?
Thanks in advance.
HI i have the below animation for a view below:
val duration = 2000L
val visible = 1.0f
imageAVater.apply {
animate().translationYBy(-100f).alpha(visible).setDuration(duration).setListener(object : AnimatorListenerAdapter(){
override fun onAnimationEnd(animation: Animator?) {
visibility = View.VISIBLE
}
})
}
i want it to move from slightly off-position and into position and to also reveal itself by setting the alpha .
SO far neither works.
ALl the code above does is move the image from current default position on my layout(lets say i positioned it in xml along the Y axis position 200) and then it moves from position 200 to position 100 and also the alpha does not work, the item is visible all the time despite it being set to View.Gone in my xml
android:visibility="gone"
How can i set a start and end Y axis value for this translation animation and how can i get the alpha to work so that the view appears from hidden/gone?
i want it to start at 200 y and have it transition to 100 y and to also reveal itself from being hidden/gone to being shown at the same time as the transition
you need to specify starting values for y and alpha. EG.
imageAVater.apply {
alpha = 0f
animate().alpha(1f).setDuration(2000).start()
}
leave the view's visibility always to visible. You can not animate a view that is gone
How to make a recycler view with different item elevation onScroll
[
Like this video
if you're not already using ViewPager2 you should try to check what it does, you can apply transformations to pages (items). As you haven't provided any code, i can only reply in a general context.
You can achieve the result you want with the following code:
ViewPager2.PageTransformer { page, position ->
page.apply {
translationY = Math.abs(position) * 500f
scaleX = 1f
scaleY = 1f
}
}
then
viewPager2.setPageTransformer(CompositePageTransformer().also {
it.addTransformer(marginPageTransformer)
it.addTransformer(translationPageTransformer())
})
check this link for more explanation