I'm implementing an horizontal pager with Accompanist. Is there an elegant way to automatically switch the images every 5 seconds?
Otherwise, I'd have to fake a manual swipe by using a channel in the viewmodel that increments the currentPage every 5 seconds.. which, to be honest, I'm not quite a fan of.
Before Compose, I used to implement the Why Not Image Carousel, which has a built-in autoplay property.
Any help would be appreciated. Thx!
You can do the following:
val images = // list of your images...
val pageState = rememberPagerState(pageCount = images.size)
HorizontalPager(state = pageState) {
// Your content...
}
// This is the interesting part, when your page changes,
// the LaunchedEffect will run again
LaunchedEffect(pageState.currentPage) {
delay(3000) // wait for 3 seconds.
// increasing the position and check the limit
var newPosition = pageState.currentPage + 1
if (newPosition > images.lastIndex) newPosition = 0
// scrolling to the new position.
pageState.animateScrollToPage(newPosition)
}
Here is the result (in the GIF, the interval is 1 sec):
Related
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.
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
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.
I have implemented from the documentation of viewpager2 the follow animation
private const val MIN_SCALE = 0.85f
private const val MIN_ALPHA = 0.5f
class ZoomOutPageTransformer : ViewPager2.PageTransformer {
override fun transformPage(view: View, position: Float) {
view.apply {
val pageWidth = width
val pageHeight = height
when {
position < -1 -> { // [-Infinity,-1)
// This page is way off-screen to the left.
alpha = 0f
}
position <= 1 -> { // [-1,1]
// Modify the default slide transition to shrink the page as well
val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position))
val vertMargin = pageHeight * (1 - scaleFactor) / 2
val horzMargin = pageWidth * (1 - scaleFactor) / 2
translationX = if (position < 0) {
horzMargin - vertMargin / 2
} else {
horzMargin + vertMargin / 2
}
// Scale the page down (between MIN_SCALE and 1)
scaleX = scaleFactor
scaleY = scaleFactor
// Fade the page relative to its size.
alpha = (MIN_ALPHA +
(((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)))
}
else -> { // (1,+Infinity]
// This page is way off-screen to the right.
alpha = 0f
}
}
}
}
}
Now, in my activity I wraped this animation with a Handler to automatically swipe, but is going really fast that I cant even notice when its sliding
private fun slideViewPager(){
var currentPage = 0
var timer: Timer? = null
val DELAY_MS: Long = 2000 //delay in milliseconds before task is to be executed
val PERIOD_MS: Long = 3000
val handler = Handler()
val update = Runnable {
if (currentPage == NUM_PAGES - 1) {
currentPage = 0
}
viewPager.setCurrentItem(currentPage++, true)
}
timer = Timer() // This will create a new Thread
timer!!.schedule(object : TimerTask() { // task to be scheduled
override fun run() {
handler.post(update)
}
}, DELAY_MS, PERIOD_MS)
}
I have adjusted the delay from the handler, but is not that the problem, because that just delays when its gonna change to the other page, but the transition during this change is really fast, how can I slow it down ?
I have encountered a similar problem when working with ViewPager2. I had two items in my ViewPager, and put the switch to the next item as the action of a button. The first time you press it, the animation is instantaneous, like you describe. However, in my setup, if you press again on it, it scrolls back to the left. Once it does that, the animation works correctly (both left and right).
From what I can tell from your code, in your case you continue to scroll in a single direction - hence you only observe the instantaneous scrolling. Try to play around with it and make it scroll back, instead of only forward. See if the animation appears correctly in that case.
EDIT: If you find that the same behavior applies in your case as well, you can apply a completely inelegant hack:
You can "browse" through your ViewPager's items when you're opening up your activity, as quickly as possible. Then, after going through each item, switch back to the first item and then just apply whatever logic you have now. If you do this, the items will already be "loaded" and the animations will work.
Of course, while you're doing that in the "background", you can display a progress loader when you first get into the activity, and once you've scrolled back to the first item, display your activity. The user will think the activity is "loading", but in reality you will be going through each of your ViewPager's items, effectively "pre-loading" them.
Completely ugly solution, but it should work. I hope someone can come up with a better one than this, as I don't exactly know the reason why the animation isn't playing correctly the first time.
I use recyclerview for dynamic list. After insert and removed items recyclerview performs a sharp update. I use DefaultItemAnimator for more beautiful update I list.
optionList2.itemAnimator = DefaultItemAnimator()
I would like to try to increase the delay for the animation. Can you tell me how to do this?
Use set(Change/Add/Move/Remove)Duration method:
optionList2.itemAnimator = DefaultItemAnimator().apply {
changeDuration = 250
addDuration = 250
moveDuration = 250
removeDuration = 250
}