I have RecyclerView and use it instead ViewPager with BottomNavigationView from support library. RecyclerView for nicely and comfort scroll have PagerSnapHelper.
And i faced with terrible and strange problem:
when my listener for BottomNavigationView catches in method onNavigationItemSelected new position i make this:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
var newPos = -1
//little code for checked only a NEW position, the same values ignored
recycler.smoothScrollToPosition(currentPos) //values 0, 1 and 2
return true
}
when i smoothScrollToPosition with currentPos = 0 recycler scroll to First position (0) very slow (compared to other
). Time between call method smoothScrollToPosition and onScrollStateChanged (for recycler view scroll listener with parameter newState = RecyclerView.SCROLL_STATE_IDLE) with values 1 and 2 very small, and with value = 0 about a second (!)
Why is this happening and how can I fix it?
Use recyclerView.scrollToPosition(position); instead of recycler.smoothScrollToPosition(currentPos)
recyclerView.scrollToPosition(position); will not show the animation of scrolling down, the effect will be immediate.
You can also try this if you set linearLayoutManager for your recyclerView
linearLayoutManager.scrollToPositionWithOffset(position, 0);
Related
Hey everyone I am new in android with kotlin. I have problem in viewpager2 which I cannot solve. I have one Mutable list in which 30 items data is present on it. When application opens I sat by default 15 index number data as default as index of viewpager2 as current item. If I move to page to 0 index then, I am reseting the Mutablelist data and adding 10 or 20 data on left and 0 index value set in the middle, which means I am shifting the data in which I am in successful. But the main problem is to disable the smooth scroll between 0 to 10-20 when I reset the list also I have observable variable as my current index which I am changing the current item of viewpager2.
private var b = false
mViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(int position, float arg1, int arg2) {
if(b){
if(position > 0 && position < 15){
mViewPager.setCurrentItem(currenIndex,false)
b = false
}
}
}
}
In Activity when I am resetting the mutable list or adding some value on it I changed the boolean value as true
b = true
also in my observable code
currenIndex.observer(this, Observer {
mViewPager.setCurrentItem(it, true)
})
It always jumping when resetting the list to 0,1,2,3,4,5,6,7,8,9,10.. and I want to disable all animation between them and only wanted to display the page directly 15 page
Also I tried to remove
mViewPager.setCurrentItem(it, true)
from my observable variable it works but their is some problem in between 0 and 1 the animation stops between them when first time to reaching index 0 without resetting the list.
Sorry for my English. Thanks in advance
The second parameter of ViewPager.setCurrentItem funtion is boolean smoothScroll Try this:
mViewPager.setCurrentItem(15, false)
I have a callback that is fired after a programmatic scroll to a certain item of a RecyclerView via LinearLayoutManager.smoothScrollToPosition(). The user taps on an item and the right item is scrolled to the top of the RecyclerView. I subclassed LinearLayoutManager to have it always snap to the top of the item.
This works in case the scroll event is fired, but when the RecyclerView is already in the right position, I don't get the onScrollStateChanged callback, as no scrolling occurs. Is there a way to get that event anyway? Like decide beforehand whether or not the RecyclerView needs to scroll or not?
Hope the following code would help
if(LinearLayoutManager.findFirstCompletelyVisibleItem() == yourDesiredPosition) {
//do your stuff
} else {
LinearLayoutManager.scrollToPositionWithOffset(yourDesiredPosition, offset);
//onScrollStateChanged would be trigger then.
}
I found the following solution myself:
// get the view the user selected
View view = mLayoutManager.findViewByPosition(index);
// get top offset
int offset = view.getTop();
if (offset == 0) { // the view is at the top of the scrollview
showDetailViewInternal(event);
} else {
// scrolling
}
I have RecyclerView with LinearLayoutManager (Vertical, not inverted). When I insert or move item to 0 position, such method is invoked:
private void scrollToStart() {
int firstVisiblePosition = ((LinearLayoutManager) recentList.getLayoutManager()).findFirstCompletelyVisibleItemPosition();
boolean notScrolling = recentList.getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
if (firstVisiblePosition < 2 && notScrolling) {
recentList.scrollToPosition(0);
}
}
But sometimes it smooth scrolls to the end of list.
(I've seen such behaviour in some apps like Instagram. It looks like list scrolls to top and then starts to move in the opposite direction.)
If I replace smoothScrollToPosition with simple scrollToPosition, it behaves as it should.
How to prevent this scroll?
In my case the fact items are recyclable was the cause to it. Try to place this.setIsRecyclable(false); inside of your ViewHolder
I Use Recyclerview Replace with list view
I want to keep Recyclerview always scroll bottom.
ListView can use this method setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL)
RecyclerView I use method smoothScrollToPosition(myAdapter.getItemCount() - 1)
but when Soft keyboard Pop ,its replace RecyclerView content.
If you want to keep the scroll position anchored to the bottom of the RecyclerView, it's useful in chat apps. just call setStackFromEnd(true) to on the LinearLayoutManager to make the keyboard keep the list items anchored on the bottom (the keyboard) and not the top.
This is because RV thinks its reference point is TOP and when keyboard comes up, RV's size is updated by the parent and RV keeps its reference point stable. (thus keeps the top position at the same location)
You can set LayoutManager#ReverseLayout to true in which case RV will layout items from the end of the adapter.
e.g. adapter position 0 is at the bottom, 1 is above it etc...
This will of course require you to reverse the order of your adapter.
I'm not sure but setting stack from end may also give you the same result w/o reordering your adapter.
recyclerView.scrollToPosition(getAdapter().getItemCount()-1);
I have faced the same problem and I solved it using the approach mentioned here. It is used to detect whether soft keyboard is open or not and if it is open, just call the smoothScrollToPosition() method.
A much simpler solution is to give your activity's root view a known ID, say '#+id/activityRoot', hook a GlobalLayoutListener into the ViewTreeObserver, and from there calculate the size diff between your activity's view root and the window size:
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
if (heightDiff > 100) {
recyclerView.smoothScrollToPosition(myAdapter.getItemCount() - 1);
}
}
});
Easy!
I have also faced same problem. But following code help me. I hope this is useful.
In this staus is arraylist.
recyclerView.scrollToPosition(staus.size()-1);
next one is:-
In This you can use adapter class
recyclerView.scrollToPosition(showAdapter.getItemCount()-1);
I ran into this problem myself and I ended up creating my own LayoutManager to solve it. It's a pretty straightforward solution that can be broken down into three steps:
Set stackFromEnd to true.
Determine whether forceTranscriptScroll should be set to true whenever onItemsChanged is called. Per the documentation, onItemsChanged gets called whenever the contents of the adapter changes. If transcriptMode is set to Disabled, forceTranscriptScroll will always be false, if it's set to AlwaysScroll, it will always be true, and if it's set to Normal, it will only be true if the last item in the adapter is completely visible.
In onLayoutCompleted, scroll to the last item in the list if forceTranscriptScroll is set to true and the last item in the list isn't already completely visible.
Below is the code that accomplishes these three steps:
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class TranscriptEnabledLinearLayoutManager(context: Context, transcriptMode: TranscriptMode = TranscriptMode.Normal) :
LinearLayoutManager(context) {
enum class TranscriptMode {
Disabled, Normal, AlwaysScroll
}
private var transcriptMode: TranscriptMode = TranscriptMode.Disabled
set(value) {
field = value
// Step 1
stackFromEnd = field != TranscriptMode.Disabled
}
private var forceTranscriptScroll = false
init {
this.transcriptMode = transcriptMode
}
// Step 2
override fun onItemsChanged(recyclerView: RecyclerView) {
super.onItemsChanged(recyclerView)
forceTranscriptScroll = when (transcriptMode) {
TranscriptMode.Disabled -> false
TranscriptMode.Normal -> {
findLastCompletelyVisibleItemPosition() == itemCount - 1
}
TranscriptMode.AlwaysScroll -> true
}
}
// Step 3
override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state)
val recyclerViewState = state ?: return
if (!recyclerViewState.isPreLayout && forceTranscriptScroll) {
// gets the position of the last item in the list. returns if list is empty
val lastAdapterItemPosition = recyclerViewState.itemCount.takeIf { it > 0 }
?.minus(1) ?: return
val lastCompletelyVisibleItem = findLastCompletelyVisibleItemPosition()
if (lastCompletelyVisibleItem != lastAdapterItemPosition ||
recyclerViewState.targetScrollPosition != lastAdapterItemPosition) {
scrollToPositionWithOffset(lastAdapterItemPosition, 0)
}
forceTranscriptScroll = false
}
}
}
I want to change my ListView I currently have over to use RecyclerView so I can make use of StaggeredGridLayoutManager but RecyclerView does not have the ability to add a header like ListView.
Usually with a ListView I set an empty view in the header and put the image below the listview and translate the bottom image with the scrolling of the list to create the Parallax effect.
So with out a header how can I create the same parallax effect with RecyclerView?
the easiest way to do it, is using below onScrollListener without relying on any library.
View view = recyclerView.getChildAt(0);
if(view != null && recyclerView.getChildAdapterPosition(view) == 0) {
view.setTranslationY(-view.getTop() / 2);// or use view.animate().translateY();
}
make sure your second viewHolder item has a background color to match the drawer/activity background. so the scrolling looks parallax.
So today I tried to archive that effect on a RecyclerView. I was able to do it but since the code is too much I will paste here my github project and I will explain some of the key points of the project.
https://github.com/kanytu/android-parallax-recyclerview
First we need to look at getItemViewType on the RecyclerView.Adapter class. This methods defines what type of view we're dealing with. That type will be passed on to onCreateViewHolder and there we can inflate different views. So what I did was: check if the position is the first one. If so then inflate the header, if not inflate a normal row.
I've added also a CustomRelativeLayout that clips the view so we don't have any trouble with the dividers and with the rows getting on top of the header.
From this point you seem to know the rest of the logic behind it.
The final result was:
EDIT:
If you need to insert something in adapter make sure you notify the correct position by adding 1 in the notifyItemChanged/Inserted method. For example:
public void addItem(String item, int position) {
mData.add(position, item);
notifyItemInserted(position + 1); //we have to add 1 to the notification position since we don't want to mess with the header
}
Another important edit I've done is the scroll logic. The mCurrentOffset system I was using didn't work with the item insertion since the offset will change if you add an item. So what I did was:
ViewHolder holder = findViewHolderForPosition(0);
if (holder != null)
((ParallaxRecyclerAdapter) getAdapter()).translateHeader(-holder.itemView.getTop() * 0.5f);
To test this I added a postDelayed Runnable, started the app, scrolled to the end, add the item in position 0, and scroll up again. The result was:
If anyone is looking for other parallax effects they can check my other repo:
https://github.com/kanytu/android-parallax-listview
For kotlin, you may config the recycler view as below
//setting parallax effects
recyclerView.addOnScrollListener(object :RecyclerView.OnScrollListener(){
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val view = recyclerView?.getChildAt(0)
if (view != null && recyclerView?.getChildAdapterPosition(view) === 0) {
val imageView = view.findViewById(R.id.parallaxImage)
imageView.translationY = -view.top / 2f
}
}
})
This answer is for those curious about adding a parallax header to a GridLayoutManager or a StaggeredGridLayoutManager
You'll want to add the following code to your adapter in either onBindViewHolder or onCreateViewHolder
StaggeredGridLayoutManager.LayoutParams layoutParams =
(StaggeredGridLayoutManager.LayoutParams) holder.itemView.getLayoutParams();
layoutParams.setFullSpan(true);