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
I want cardview elevation animation just like Google is doing in "PlayGames" animation. Here is a sample gif of animation. Can anyone guide me how to do it or refer any library.
sample gif animation
Thanks
How to do it?
Using Viewpager and Shadow Transform
1- create a PagerAdapter for cardview
2- inside the adapter in instantiateItem method set elevation for the card like this
CardView cardView = (CardView) view.findViewById(R.id.cardView);
if (mBaseElevation == 0) {
mBaseElevation = cardView.getCardElevation();
}
cardView.setMaxCardElevation(mBaseElevation * MAX_ELEVATION_FACTOR);
mViews.set(position, cardView);
return view;
after that implement ViewPager.OnPageChangeListener
inside the onPageScrolled
if (currentCard != null) {
if (mScalingEnabled) {
currentCard.setScaleX((float) (1 + 0.1 * (1 - realOffset)));
currentCard.setScaleY((float) (1 + 0.1 * (1 - realOffset)));
}
currentCard.setCardElevation((baseElevation + baseElevation
* (CardAdapter.MAX_ELEVATION_FACTOR - 1) * (1 - realOffset)));
}
CardView nextCard = mAdapter.getCardViewAt(nextPosition);
// We might be scrolling fast enough so that the next (or previous) card
// was already destroyed or a fragment might not have been created yet
if (nextCard != null) {
if (mScalingEnabled) {
nextCard.setScaleX((float) (1 + 0.1 * (realOffset)));
nextCard.setScaleY((float) (1 + 0.1 * (realOffset)));
}
nextCard.setCardElevation((baseElevation + baseElevation
* (CardAdapter.MAX_ELEVATION_FACTOR - 1) * (realOffset)));
}
full code ViewPagerCards
if you want to do that using RecyclerView
you can get the middle item and check onBindViewHolder if that item is middle item then do the scale animation
to know to get the middle check this answer :
Get center visible item of RecycleView when scrolling
I have a ViewPager Fragment with 3 fragments. Each of it has an image as background and a TextView. The TextView is always absolute on the left side of the screen (not the Fragment itself). When I move the fragments with my finger, it stays at the correct position. Also when I move it fast. But when I let it slide into the other Fragment, the TextView doesn't stays at the correct position. It looks like it lags around.
It should stay like in these 2 pictures:
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
SingleTabFragment singleTabFragment = (SingleTabFragment) mSectionsPagerAdapter.getRegisteredFragment(position);
TextView textView = singleTabFragment.getNonResizingTextView();
ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams)textView.getLayoutParams();
marginLayoutParams.leftMargin = positionOffsetPixels;
textView.setLayoutParams(marginLayoutParams);
}
I really don't know what the problem is. I tried it multiple ways, i added Thread.sleep(50); to see if it also happens there. And yes, also there it doesn't work correct. How can I fix this?
EDIT:
Now I have put in the middle. But the problem is the same.
Here is how it should look like (I know, not that much frames. But I think you will get the point):
And if I let it slide it looks like this:
TextView textView = singleTabFragment.getNonResizingTextView();
textView.translationX = positionOffsetPixels;
You should not do marginLayoutParams.leftMargin = positionOffsetPixels; because changing the margin may change the position of the View, which will cause the View to be re-layout on every frame. That is why it is lagging.
Changing the translationX will only change how the view is drawn instead of it's position.
You can use following method to do the same instead of doing it in onPageScrolled:
mPager.setPageTransformer(false, new FadePageTransformer());
public class FadePageTransformer implements ViewPager.PageTransformer {
public void transformPage(View view, float position) {
view.setAlpha(1 - Math.abs(position));
if (position < 0) {
view.setScrollX(-(int) ((float) (view.getWidth()) * -position));
} else if (position > 0) {
view.setScrollX((int) ((float) (view.getWidth()) * position));
} else {
view.setScrollX(0);
}
}
}
use textView.setTranslationX(positionOffsetPixels); instead of margins
Have a look at Slowing speed of Viewpager controller in android
You can set custom duration for Viewpager scrolling.
I have a RecyclerView with items of varying heights with a scrollbar.
Because of the different heights of the items, the scrollbar changes it's vertical size, dependent on which items are currently displayed (see screenshots).
I have created an example project that displays the problem here.
Has anyone had the same problem and fixed it?
How can I override the calculation of the scrollbar height and position to come up with an own implementation?
EDIT: The scrollbar's position and height can be controlled by overriding RecyclerViews computeVerticalScrollOffset, computeVerticalScrollRange and computeVerticalScrollExtent.
I have no idea though on how to implement these to make the scrollbar work properly with dynamic item heights.
The problem, I reckon, is that RecyclerView estimates the total height of all items based on the items currently visible and sets position and height of the scrollbar accordingly. One way to solve this might be to give a better estimation of the total height of all items.
The best way to handle this situation may be to somehow calculate the scroll bar range based on the size of each item. That may not be practical or desirable. In lieu of that, here is a simple implementation of a custom RecyclerView that you can play with to try to get what you want. It will show you how you can use the various scroll methods to control the scroll bar. It will stick the size of the thumb to an initial size based upon the number of items displayed. The key thing to remember is that the scroll range is arbitrary but all other measurements (extent, offset) must use the same units.
See the documentation for computeVerticalScrollRange().
Here is a video of the result.
Update: The code has been updated to correct a few issues: The movement of the thumb is less jerky and the thumb will now come to rest at the bottom as the RecyclerView scrolls to the bottom. There are also a few caveats that are given after the code.
MyRecyclerView.java (updated)
public class MyRecyclerView extends RecyclerView {
// The size of the scroll bar thumb in our units.
private int mThumbHeight = UNDEFINED;
// Where the RecyclerView cuts off the views when the RecyclerView is scrolled to top.
// For example, if 1/4 of the view at position 9 is displayed at the bottom of the RecyclerView,
// mTopCutOff will equal 9.25. This value is used to compute the scroll offset.
private float mTopCutoff = UNDEFINED;
public MyRecyclerView(Context context) {
super(context);
}
public MyRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Retrieves the size of the scroll bar thumb in our arbitrary units.
*
* #return Scroll bar thumb height
*/
#Override
public int computeVerticalScrollExtent() {
return (mThumbHeight == UNDEFINED) ? 0 : mThumbHeight;
}
/**
* Compute the offset of the scroll bar thumb in our scroll bar range.
*
* #return Offset in scroll bar range.
*/
#Override
public int computeVerticalScrollOffset() {
return (mTopCutoff == UNDEFINED) ? 0 : (int) ((getCutoff() - mTopCutoff) * ITEM_HEIGHT);
}
/**
* Computes the scroll bar range. It will simply be the number of items in the adapter
* multiplied by the given item height. The scroll extent size is also computed since it
* will not vary. Note: The RecyclerView must be positioned at the top or this method
* will throw an IllegalStateException.
*
* #return The scroll bar range
*/
#Override
public int computeVerticalScrollRange() {
if (mThumbHeight == UNDEFINED) {
LinearLayoutManager lm = (LinearLayoutManager) getLayoutManager();
int firstCompletePositionw = lm.findFirstCompletelyVisibleItemPosition();
if (firstCompletePositionw != RecyclerView.NO_POSITION) {
if (firstCompletePositionw != 0) {
throw (new IllegalStateException(ERROR_NOT_AT_TOP_OF_RANGE));
} else {
mTopCutoff = getCutoff();
mThumbHeight = (int) (mTopCutoff * ITEM_HEIGHT);
}
}
}
return getAdapter().getItemCount() * ITEM_HEIGHT;
}
/**
* Determine where the RecyclerVIew display cuts off the list of views. The range is
* zero through (getAdapter().getItemCount() - 1) inclusive.
*
* #return The position in the RecyclerView where the displayed views are cut off. If the
* bottom view is partially displayed, this will be a fractional number.
*/
private float getCutoff() {
LinearLayoutManager lm = (LinearLayoutManager) getLayoutManager();
int lastVisibleItemPosition = lm.findLastVisibleItemPosition();
if (lastVisibleItemPosition == RecyclerView.NO_POSITION) {
return 0f;
}
View view = lm.findViewByPosition(lastVisibleItemPosition);
float fractionOfView;
if (view.getBottom() < getHeight()) { // last visible position is fully visible
fractionOfView = 0f;
} else { // last view is cut off and partially displayed
fractionOfView = (float) (getHeight() - view.getTop()) / (float) view.getHeight();
}
return lastVisibleItemPosition + fractionOfView;
}
private static final int ITEM_HEIGHT = 1000; // Arbitrary, make largish for smoother scrolling
private static final int UNDEFINED = -1;
private static final String ERROR_NOT_AT_TOP_OF_RANGE
= "RecyclerView must be positioned at the top of its range.";
}
Caveats
The following issues may need to be addressed depending on the implementation.
The sample code works only for vertical scrolling. The sample code also assumes that the contents of the RecyclerView are static. Any updates to the data backing the RecyclerView may cause scrolling issues. If any changes are made that effect the height of any view displayed on the first full screen of the RecyclerView, the scrolling will be off. Changes below that will probably work OK. This is due to how the code calculates the scrolling offset.
To determine the base value for the scrolling offset, (variable mTopCutOff), the RecyclerView must be scrolled to the top the first time computeVerticalScrollRange() is invoked so views can be measured; otherwise, the code will stop with an "IllegalStateException". This is especially troublesome on an orientation change if the RecyclerView is scrolled at all. A simple way around this would be to inhibit restoration of the scrolling position so it defaults to the top on an orientation change.
(The following is probably not the best solution...)
var lm: LinearLayoutManager = object : LinearLayoutManager(this) {
override fun onRestoreInstanceState(state: Parcelable?) {
// Don't restore
}
}
I hope this helps. (btw, your MCVE made this a lot easier.)
Use item positions as metric of scroll progress. This will cause your scroll indicator to become a bit jumpy, but at least it will remain fixed-sized.
There are multiple implementations of custom scroll indicators for RecyclerView. Most double as fast scrollers.
Here is my own implementation, based on RecyclerViewFastScroller library. Basically, one have to create a custom View subclass, that will be animated, similarly to ScrollView and DrawerLayout:
Store current offset
During animation offset position of thumb View via View#offset* calls
During layout set position based on current offset.
You probably don't want to start learning all that magic now, just use some existing fast scrolling library (RecyclerViewFastScroller or one of it's clones).
Inspired by Cheticamp's solution I managed to spin my own extension of RecyclerView which doesn't have the computeVerticalScrollRange limitations.
In fact, this alternative solution doesn't require extending computeVerticalScrollRange at all.
By reasoning with things in terms of spans I managed to think of a solution that doesn't depend on calculating the height of any items in the RecyclerView.
Each item in the list has a span of 1, and I am fixing the scrollbar thumb size to a certain number of spans (meaning the scrollbar doesn't change its height as the user scrolls).
Now consider the following things:
rangeSpanCount to be the number of spans a.k.a the number of items in the adapter
firstSpan to be the position of the first visible span (first completely visible if any, otherwise the first partially visible)
lastSpan to be the position of the last visible span (last completely visible if any, otherwise the last partially visible)
visibleSpanCount, equal to lastSpan - firstSpan, to be the number of spans currently visible in the screen
remainingSpanCount, equal to rangeSpanCount - 1 - visibleSpanCount, to be the number of spans remaining in the RecyclerView
Then for the sake of the explanation assume we have a list of 9 spans, and only 3 of them can be visible at any given time (although the logic holds even if the number of visible spans at a given moment is dynamic):
0 1 2 3 4 5 6 7 8 9
0 1 2{------------}
| the size of this range is:
{--}2 3 4 |===========> rangeSpanCount - 1 - visibleSpanCount
{-----}3 4 5
{-------}4 5 6
{-----------}6 7 8
{-------------}7 8 9
| you can see that this range is simply computed as:
|===========> firstSpan - 0
Then notice how we can use the range that grows as the scrolling from top to bottom happens and the range of spans that is left out of sight at any given moment to calculate the progress of the scrolling throughout the RecyclerView.
First we figure out how much has the growing range grown:
partialProgress = (firstSpan - 0) / remainingSpanCount
(From 0% all the way to 100% when firstSpan == remainingSpanCount)
Then we calculate which span among the visible ones better represent the progress of the scrolling throughout the RecyclerView. Basically, we want to make sure the first span (of position 0) is chosen when RecyclerView is at the very top and the last span (of position rangeSpanCount - 1) to be chosen when we reach the very bottom. This is important otherwise your scrolling will be off when reaching these edges.
progressSpan = firstSpan + (visibleSpanCount * partialProgress)
And finally, you can use the position of this chosen span and the total number of spans to figure out the actual progress percentage across the RecyclerView, and use the real computed scroll range to determine the best offset for the scrollbar:
scrollProgress = progressSpan / rangeSpanCount
scrollOffset = scrollProgress * super.computeVerticalScrollRange()
And that's it! This solution can be adapted to support the horizontal axis, so it carries none of the caveats from Cheticamp's alternative.
It has one caveat, though: the movement of the scrollbar thumb is discrete, not continuous along the axis, meaning the jumping from one position to the next is noticeable. It is consistent, though, never "shaking" itself / going back and forth while the user performs a scroll to any direction.
This caveat can probably be solved by working with a much higher number of spans in respect to the number of items in the adapter (e.g. having multiple spans per item) but I didn't give it too much thought right now.
I hope my explanation is reasonably clear... and I thank you all for helping me with your answers, it really helped point me to the right direction!
Below you can check out the complete solution and source code:
package cz.nn.calllog.view.utils.recyclerview
import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class SmartScrollbarRecyclerView(
context: Context,
attributeSet: AttributeSet?,
defaultStyleAttribute: Int
) : RecyclerView(context, attributeSet, defaultStyleAttribute) {
constructor(
context: Context,
attributeSet: AttributeSet
) : this(context, attributeSet, 0)
constructor(
context: Context
) : this(context, null, 0)
override fun computeVerticalScrollExtent(): Int {
return checkCalculationPrerequisites(
onFailure = {
super.computeVerticalScrollExtent()
},
onSuccess = { _, rangeSpan, scrollRange ->
val extentSpanCount = 1.5F
val scrollExtent = (extentSpanCount / rangeSpan)
(scrollExtent * scrollRange).toInt()
}
)
}
override fun computeVerticalScrollOffset(): Int {
return checkCalculationPrerequisites(
onFailure = {
super.computeVerticalScrollOffset()
},
onSuccess = { layoutManager, rangeSpanCount, scrollRange ->
val firstSpanPosition = calculateFirstVisibleItemPosition(layoutManager)
val lastSpanPosition = calculateLastVisibleItemPosition(layoutManager)
val visibleSpanCount = lastSpanPosition - firstSpanPosition
val remainingSpanCount = rangeSpanCount - 1 - visibleSpanCount
val partialProgress = (firstSpanPosition / remainingSpanCount)
val progressSpanPosition = firstSpanPosition + (visibleSpanCount * partialProgress)
val scrollProgress = progressSpanPosition / rangeSpanCount
(scrollProgress * scrollRange).toInt()
}
)
}
private fun calculateFirstVisibleItemPosition(layoutManager: LinearLayoutManager): Int {
val firstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
return if (firstCompletelyVisibleItemPosition == -1) {
layoutManager.findFirstVisibleItemPosition()
} else {
firstCompletelyVisibleItemPosition
}
}
private fun calculateLastVisibleItemPosition(layoutManager: LinearLayoutManager): Int {
val lastCompletelyVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition()
return if (lastCompletelyVisibleItemPosition == -1) {
layoutManager.findLastVisibleItemPosition()
} else {
lastCompletelyVisibleItemPosition
}
}
private fun checkCalculationPrerequisites(
onFailure: () -> Int,
onSuccess: (LinearLayoutManager, Float, Int) -> Int
): Int {
val layoutManager = layoutManager
if (layoutManager !is LinearLayoutManager) {
return onFailure.invoke()
}
val scrollRange = computeVerticalScrollRange()
if (scrollRange < height) {
return 0
}
val rangeSpanCount = calculateRangeSpanCount()
if (rangeSpanCount == 0F) {
return 0
}
return onSuccess.invoke(layoutManager, rangeSpanCount, scrollRange)
}
private fun calculateRangeSpanCount(): Float {
val recyclerAdapter = adapter ?: return 0F
return recyclerAdapter.itemCount.toFloat()
}
}
If I'm not mistaken the attribute android:scollBarSize="Xdp" should work for you. Add it to your RecyclerView xml.
That way you decide the size, and it will remain fixed.
How can I implement swipe of one image on another likewise the attached image.
In image one can see that background image is static, and user can able to swipe another image. Its a screenshot of housing.com app.
Can anyone help me in this.
Note: tried for viewpager, jazzyviewpager, Parallex view but no success
Use custom Viewpager(jazzy viewpager library with stack effect and disable zoomout functionality inside this) to acheive this.
So after so many days of search n implementations I got this exactly.
Steps to achieve:
1. Implement Viewpager.
2. User custom view pager with transition.
3. The most important one, Implement clipDrawable to clip drawable on upcoming page on view pager.
Here is the Scale transformation class uses clipdrawable :
public class ScalePageTransformer implements PageTransformer {
private static final float SCALE_FACTOR = 0.95f;
private final ViewPager mViewPager;
public ScalePageTransformer(ViewPager viewPager) {
this.mViewPager = viewPager;
}
#Override
public void transformPage(View page, float position){
View mainLayout = page.findViewById(R.id.bg_rel);
ClipDrawable clipDrawable = (ClipDrawable) mainLayout.getBackground();
if (position <= 0){
// apply zoom effect and offset translation only for pages to
// the left
// final float transformValue = Math.abs(Math.abs(position) - 1) * (1.0f - SCALE_FACTOR) + SCALE_FACTOR;
int pageWidth = mViewPager.getWidth();
final float translateValue = position* -pageWidth;
// page.setScaleX(transformValue);
// page.setScaleY(transformValue);
if (translateValue > -pageWidth){
page.setTranslationX(translateValue);
} else {
page.setTranslationX(0);
}
clipDrawable.setLevel(10000);
} else {
//entering view
int level = (int) ((1 - position) * 10000);
clipDrawable.setLevel(level);
mainLayout.setTranslationX(-1 * position * page.getWidth());
}
}
}
rel_bg: My main layout having drawables.
Hope it helps for somebody.