I have a HorizontalPager (from Accompanist) with a currentPage focus effect. On the emulator, it works correctly and with the correct spacing that I intend, however, on a smartphone, it doesn't respect the itemSpacing and completely ignores it, overlapping the pager items on the right. Is there any way to make this pager always have the same spacing regardless of the device? Or, at least, that the item on the right does not overlap and that the main item overlaps the other two?
Below a few screenshots of what is happening and the behavior that I want:
Correct behavior (working on emulator):
Wrong behavior & problem (working on physical device):
Code:
HorizontalPager(
state = state,
count = items.count(),
contentPadding = PaddingValues(horizontal = 72.dp),
itemSpacing = 10.dp,
modifier = modifier.padding(top = 16.dp)
) { page ->
Card(
shape = RectangleShape,
modifier = Modifier
.graphicsLayer {
// Calculate the absolute offset for the current page from the
// scroll position. We use the absolute value which allows us to mirror
// any effects for both directions
val pageOffset = calculateCurrentOffsetForPage(page).absoluteValue
// We animate the scaleX + scaleY, between 85% and 100%
lerp(
start = 0.85f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
).also { scale ->
scaleX = scale
scaleY = scale
}
// We animate the alpha, between 50% and 100%
alpha = lerp(
start = 0.6f,
stop = 1f,
fraction = 1f - pageOffset.coerceIn(0f, 1f)
)
}
.requiredWidth(284.dp)
.requiredHeight(168.dp)
//.coloredShadow(Shadow, 0.28f)
) {
// content
}
}
Any additional necessary code can be provided, just let me know.
You should be using how to support different screen sizes in android with jetpack compose, in which you can define dimensions for different configurations.
You can refer to this tutorial on the same
https://proandroiddev.com/supporting-different-screen-sizes-on-android-with-jetpack-compose-f215c13081bd
Related
Haii developer, i have issue to implement view pager transformer like design below, in design its not try to scale the page, the page is still have same size, but the highlight item(in center) have y axis higher then other. This is my expected view result:
But this how i get with my current code:
Here is my code for transforming page:
val nextItemVisiblePx = resources.getDimension(R.dimen.viewpager_next_item_visible)
val currentItemHorizontalMarginPx =
resources.getDimension(R.dimen.viewpager_current_item_horizontal_margin)
val pageTranslationX = nextItemVisiblePx + currentItemHorizontalMarginPx
val pageTransformer = ViewPager2.PageTransformer { page: View, position: Float ->
page.translationX = -pageTranslationX * position
// Next line scales the item's height. You can remove it if you don't want this effect
page.scaleY = 1 - (0.25f * kotlin.math.abs(position))
// If you want a fading effect uncomment the next line:
page.alpha = 0.6f + (1 - kotlin.math.abs(position))
}
layoutBaseFollowingFeedsSuggestion.rvSuggestionFollowingFeedList.setPageTransformer(pageTransformer)
// The ItemDecoration gives the current (centered) item horizontal margin so that
// it doesn't occupy the whole screen width. Without it the items overlap
val itemDecoration = HorizontalMarginItemDecoration(
requireActivity(),
R.dimen.dimen_25dp
)
layoutBaseFollowingFeedsSuggestion.rvSuggestionFollowingFeedList.addItemDecoration(itemDecoration)
Thanks!
I am trying to implement similar animation like this, with Jetpack Compose
figure: https://dribbble.com/shots/4762799-Microinteraction-Exploration-002
For the play icon’s transformation (from a triangle to a rectangle) , it not difficult to get it done by using path morphing animation.After reading some gist, I found that I can build an ImageVector by supplying the pathData , that's new to me~
While for ****the part on the right, the rotation of the cube object. Well, maybe I should call that a rolling-over or whatever, because the cube changed its pivot after -90 degrees.
I'm wondering that wether I can create a VectorGroup for the cube’s pathData and animate it by updating its group parameters.
re: androidx.compose.ui.graphics.vector.ImageVector.Builder#addGroup
ImageVector.Builder(defaultWidth = 100.dp, defaultHeight = 100.dp, viewportWidth = 100, viewportHeight = 100)
.addGroup(name = "CubeRotation", rotate = params.rotate, pivotX = params.pivotX, pivotY = params.pivotY,)
.addPath(pathData = pathData, fill = SolidColor(Color.LightGray))
.clearGroup()
.build()
Here I trying to update the pivotX halfway through the animation, so I divide it into two section:
// rotate -90 degrees twice with different pivot
val rotate = if (fraction <= 0.5f) {
lerp(0f, -180f, fraction)
} else {
lerp(0f, -180f, fraction - 0.5f)
}
// change the pivotX
val pivotX = if (fraction <= 0.5f) 52f else 30f
GroupParams(
pivotX = pivotX,
pivotY = 100f,
rotate = rotate,
)
Frustratedly, after rotating by the first -90 degrees, the transformation that this rotation performed will NOT persist, -or- its transformation NOT being applied after the half time of the transition.
fraction: 0~0.5
fraction: 0.5~1
The code sample I've uploaded: IndicatorDrawable.kt
Can I implement the rotate transformation by just updating the pathData ?
For me, the VectorGroup in ImageVector looks like creating AnimatedVectorDrawable in xml, or maybe I should try some traditional way like ObjectAnimationSet in View system ?
I am trying to implement a scrolling image that is much wider then device's screen. See example:
Right now I implemented it using Row with horizontalScroll modifier, and calling scrollState.animateScrollTo
I do not like this implementation since I need this animation to go back and forth if the image reaches it's end, and scrolling it using animateScrollTo results in ugly code (need to call it in a loop to reverse when it's do) and it's not completely smooth for some reason.
while (true) {
animationPositions.forEach {
if ((scrollState.maxValue * it).toInt() != scrollState.value) {
scrollState.animateScrollTo(
(scrollState.maxValue * it).toInt(),
animationSpec = tween(
cycleDurationMillis / animationPositions.size,
easing = LinearEasing
)
)
}
}
}
Is there a way to do this in cleaner way? I was thinking about simple offset/translationX usage, but the image is clipped on the sides because of parent container (e.g. if I just put in a column and try to move it with offset).
Is there a way to not clip the image if it's too large to fit?
Thanks
// Min and Max possible states
val minima = ...
val maxima = ...
val value by rememberInfiniteTransition().animateInt(
initialValue = minima,
targetValue = maxima,
animationSpec = infiniteRepeatable(
animation = tween(
cycleDurationMillis / animationPositions.size,
easing = LinearEasing
)
),
repeatMode = RepeatMode.Restart
)
scrollState.scrollTo(value) // Do not use animateScrollState here, that's already taken care of
On Android views, we had an animation using an OvershootInterpolator
We want to replicate the same animation in Jetpack Compose.
I see that there are several AnimationSpec described in https://developer.android.com/jetpack/compose/animation but I don't see which one might replicate the OvershootInterpolator
tween spec doesn't seem to do any overshoot, just animating between start and end value without overshooting
spring spec does overshooting, however it doesn't have a durationMillis parameter as tween, so we can't control how fast it plays
keyFrames spec seems a possible solution by doing something like this:
animationSpec = keyframes {
durationMillis = 500
0f at 100 with FastOutSlowInEasing
// Overshoot value to 50f
50f * 2 at 300 with LinearOutSlowInEasing
// Actual final value after overshoot animation
25f at 500
}
Is there a better / simpler way than using keyFrames to replicate OvershootInterpolator?
You can use any Interpolator from Animator world via a custom Easing:
animationSpec = tween(easing = Easing {
OvershootInterpolator().getInterpolation(it)
})
See Easing interface: https://developer.android.com/reference/kotlin/androidx/compose/animation/core/Easing
Though I would recommend using springs over interpolators, especially ones that mimic springs. Springs would give a much more natural look. :)
We can use spring animation of compose to achieve OvershootInterpolator. Even though we cannot provide any custom duration for the animation, we can control the speed of the animation using the stiffness attribute.
We can put any custom float values to the stiffness attribute.
androidx.compose.animation.core currently provides 4 stiffness constant values by default i.e,
object Spring {
/**
* Stiffness constant for extremely stiff spring
*/
const val StiffnessHigh = 10_000f
/**
* Stiffness constant for medium stiff spring. This is the default stiffness for spring
* force.
*/
const val StiffnessMedium = 1500f
/**
* Stiffness constant for a spring with low stiffness.
*/
const val StiffnessLow = 200f
/**
* Stiffness constant for a spring with very low stiffness.
*/
const val StiffnessVeryLow = 50f
.....
}
We can check which stiffness suits our case.
For eg:- (medium speed)
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessMedium // with medium speed
)
We can also put a custom value to the stiffness for eg:-
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy, // this would define how far the overshoot would happen.
stiffness = 1000f // with custom speed less than medium speed.
)
You can play around with custom float values for stiffness to achieve your ideal duration for the animation.
How can I create an animation similar to the stock android launcher animation applied to apps when you change homescreen pages.
Here's a gif: https://i.stack.imgur.com/Zh7qE.gif
As the page swipes, the icons slightly overshoot their mark, and settle back to the center. I don't see how I can do that with a PageTransformer and I can't find any resources to point me in the right direction.
I once created a one-time bounce animation:
val pixels = binding.pager.width / 8
ValueAnimator.ofInt(0, pixels).apply {
duration = 200L
interpolator = DecelerateInterpolator()
repeatCount = 3
repeatMode = ValueAnimator.REVERSE
addUpdateListener {
binding.pager.scrollX = it.animatedValue as Int
}
}.start()
Use ofInt(0, pixels) for bouncing left or ofInt(0, -pixels) to bouncing right.
In my code I used a dragging distance of 1/8 of the view pager.
Feel free to choose how much you want to bounce:
val pixels = PIXELS_TO_DRAG
Finally, the repeatCount determines how many bounces - use 3 for 2 bounces, 5 for 3 bounces, 7 for 4 bounces etc.
You can fire this animation each time a pager transition end and tweak the parameters to get the desired behavior.
Enjoy,
Hope it helps :)
According to #Shlomi, you can do this implementation:
var currentPage = 0
binding.viewPager.onPageChangeListener {
val pixels = binding.vpBennefits.width / 8
ValueAnimator.ofInt(
0, if (currentPage < it)
pixels
else
-pixels
).apply {
duration = 200L
interpolator = DecelerateInterpolator()
repeatCount = 1
repeatMode = ValueAnimator.REVERSE
addUpdateListener {
binding.vpBennefits.scrollX = it.animatedValue as Int
}
}.start()
currentPage = it
}
By this way you can obtain the desired effect in both sides