How to scroll horizontal RecyclerView programmatically? - android

I have a horizontal RecyclerView and two button (Next,Previous) as shown in the image below.
so i need to move to the next item or position by use these buttons , i know about method called scrollTo but i don't know how does it work

I found the answer:
case R.id.next:
mRecyclerView.getLayoutManager().scrollToPosition(linearLayoutManager.findLastVisibleItemPosition() + 1);
break;
case R.id.pre:
mRecyclerView.getLayoutManager().scrollToPosition(linearLayoutManager.findFirstVisibleItemPosition() - 1);
break;

RecyclerViews have a methods which they expose for scrolling to a certain position:
Snap scroll to a given position:
mRecyclerView.scrollToPosition(int position)
Smooth scroll to a given position:
mRecyclerView.smoothScrollToPosition(int position)
For these methods to work, the LayoutManager of the RecyclerView needs to have implemented these methods, and LinearLayoutManager does implement these in a basic manner, so you should be good to go.

case R.id.next:
mRecyclerView.getLayoutManager().scrollToPosition(linearLayoutManager.findLastVisibleItemPosition() + 1);
break;
case R.id.pre:
mRecyclerView.getLayoutManager().scrollToPosition(linearLayoutManager.findFirstVisibleItemPosition() - 1);
break;

int mFirst=0, mLast=0;
recyclerview.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
LinearLayoutManager llm = (LinearLayoutManager) recyclerview.getLayoutManager();
mLast = llm.findLastCompletelyVisibleItemPosition();
mFirst = llm.findFirstCompletelyVisibleItemPosition();
}
});
imgRight.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LinearLayoutManager llm = (LinearLayoutManager) recyclerview.getLayoutManager();
llm.scrollToPositionWithOffset(mLast + 1, List.length());
}
});
imgLeft.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LinearLayoutManager llm = (LinearLayoutManager) recyclerview.getLayoutManager();
llm.scrollToPositionWithOffset(mFirst - 1, List.length());
}
});

Adding to the accepted answer you need to have a check for the present position so that you don't get a nullPointerException like below
binding.imageViewleft.setOnClickListener {
if (layoutManager.findFirstCompletelyVisibleItemPosition()>0){
binding.recyclerView.layoutManager?.scrollToPosition(layoutManager.findFirstCompletelyVisibleItemPosition()-1)
}
}
binding.iv2.setOnClickListener {
if (layoutManager.findLastCompletelyVisibleItemPosition()<(sectiondata.size-1) ){
binding.recyclerView.layoutManager?.scrollToPosition(layoutManager.findLastCompletelyVisibleItemPosition()+1)
}
}

Related

recyclerview add scrollListener

I have a RecyclerView with Horizontal LinearLayoutManager.
I would like to check ScrollListener.
My goal is to check these steps:
Check when RecyclerView start scroll
Check when RecyclerView end scroll
Check when RecyclerView's scroll is in center position
Here is a my RecyclerView code with LinearLayoutManager.
LinearLayoutManager horizontalManager = new LinearLayoutManager(context);
horizontalManager.setOrientation(LinearLayoutManager.HORIZONTAL);
viewHolderStory.recyclerView.setLayoutManager(horizontalManager);
viewHolderStory.recyclerView.addItemDecoration(new PaddingItemDecoration((Activity) context));
viewHolderStory.recyclerView.setHasFixedSize(true);
viewHolderStory.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
viewHolderStory.recyclerView.setNestedScrollingEnabled(false);
Is any way to add validation on my addOnScrollListener method?
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState==RecyclerView.SCROLL_STATE_IDLE){
/// User Stops Scroll
}
if(newState==RecyclerView.SCROLL_STATE_DRAGGING){
/// User Starts Scroll
}
}
You can find more from this.
You can compute this with computeVerticalScrollExtent() , computeVerticalScrollOffset() and computeVerticalScrollRange()
if your recyler view is horizontal, these functions have a horizontal counterpart
full code :
viewHolderStory.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
int extent = recyclerView.computeVerticalScrollExtent();
int offset = recyclerView.computeVerticalScrollOffset();
int range = recyclerView.computeVerticalScrollRange();
if(offset == 0){
//fire when recycler view hit top
Log.i("myTag", "onScrolled: top");
}
else if(offset + extent == range){
//fire when recycler view hit bottom
Log.i("myTag", "onScrolled: bottom");
}
else {
//fire the rest of the time
Log.i("myTag", "onScrolled: middle");
}
}
});

Recyclerview indicates that reaches the last item

I have a recycleView that I need to observe when the last item is reached but I have notice the it always indicate that I reached the last item even if I haven't scrolled yet.
My code for setting up the recycler:
newsRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
newsRecyclerView.setFocusable(false);
newsRecyclerView.setNestedScrollingEnabled(false);
newsAdapter = new NewsAdapter(getContext(), newsDetails, categoryNumber);
newsRecyclerView.setAdapter(newsAdapter);
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false);
My xml code is:
<android.support.v7.widget.RecyclerView
android:id="#+id/news_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/news_top_stories_title_text_view" />
This is my code that I put in my Util, and use anywhere.
Util.setRecyclerViewLastPositionListner(rv, linearLayoutManager , new UtilitiesV2.OnLastPositionReached() {
#Override
public void onReached() {
// last position reached
}
});
Put this in Util.
private boolean userScrolled = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
public interface OnLastPositionReached {
void onReached();
}
public void setRecyclerViewLastPositionListner(RecyclerView rvBooksMockTest, final LinearLayoutManager mLayoutManager, final OnLastPositionReached onLastPositionReached) {
rvBooksMockTest.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
userScrolled = true;
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Here get the child count, item count and visibleitems
// from layout manager
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
// Now check if userScrolled is true and also check if
// the item is end then update recycler view and set
// userScrolled to false
if (userScrolled && (visibleItemCount + pastVisiblesItems) == totalItemCount) {
userScrolled = false;
if (onLastPositionReached != null) onLastPositionReached.onReached();
}
}
});
}
Update
According to your requirement, here is NestedScrollView bottom reach listener.
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (nestedScrollView != null) {
if (nestedScrollView.getChildAt(0).getBottom() <= (nestedScrollView.getHeight() + nestedScrollView.getScrollY())) {
//scroll view is at bottom
} else {
//scroll view is not at bottom
}
}
}
});
Thanks to Khemraj who give the tip for solution
because I have a recyclerview inside NestedScrollView and coordinator layout as parent for them
I have solved my problem like this:
public Disposable observeNestedScroll(LoadMoreListener listener) {
return RxNestedScrollView.scrollChangeEvents(nestedScrollView)
.subscribe(
viewScrollChangeEvent -> {
NestedScrollView nestedScrollView =(NestedScrollView) viewScrollChangeEvent.view();
if(nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1) != null) {
if ((viewScrollChangeEvent.scrollY() >= (nestedScrollView.getChildAt(nestedScrollView.getChildCount() - 1).getMeasuredHeight() - nestedScrollView.getMeasuredHeight())) &&
viewScrollChangeEvent.scrollY() > viewScrollChangeEvent.oldScrollY()) {
listener.onLoadMore();
}
}
});
}
I've seen to many responses for this question and I stand that all of them don't give accurate behavior as an outcome. However if you follow this approach I'm positive you'll get the best behavior.
rvCategories is your RecyclerView
categoriesList is the list passed to your adapter
binding.rvCategories.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val position = (recyclerView.layoutManager as LinearLayoutManager).findLastCompletelyVisibleItemPosition()
if (position + 1 == categoriesList.size) {
// END OF RECYCLERVIEW IS REACHED
} else {
// END OF RECYCLERVIEW IS NOT REACHED
}
}
})

RecyclerView get item when it leaves screen

How can I get object from recyclerview when it leaves screen while scrolling? For example I have note with id 11 how can I get this note id whenever it leaves screen?
One way you could do it:
Boolean upScrolling;
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager mgr = (LinearLayoutManager) recyclerView.getLayoutManager();
int topPosition = mgr.findFirstVisibleItemPosition();
/// your code
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy < 0) {
upScrolling = true;
} else if (dy > 0) {
upScrolling = false;
}
}
});
After scrolling up, in the listener you get the 1st visible item. At the position your code you write the code to check if the 1st visible item is the one below the item you want. If this is the case then you get the time it went off the screen.
Edited to check if it scrolls up or down.

Recyclerview onscrolllistener not working when setNestedScrollingEnabled to false

I want to implement pagination with recyclerView, for this I add addOnScrollListener to the recyclerView but I am having trouble with RecyclerView.OnScrollListener not working when I set rvGridExplore.setNestedScrollingEnabled(false);
But when I remove rvGridExplore.setNestedScrollingEnabled(false); it is working fine,
I don't know how to handle this.
Here is code:
rvGridExplore = (RecyclerView) view.findViewById(R.id.rvGridExplore);
final GridLayoutManager glm = new GridLayoutManager(context,2);
// rvGridExplore.setNestedScrollingEnabled(false);
rvGridExplore.setLayoutManager(glm);
// final int visibleItemCount,totalCount,pastVisibleItems;
rvGridExplore.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.v("scrollll","state changed");
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
int totalCount = glm.getItemCount();
int visibleItemCount = glm.getChildCount();
int pastVisibleItems = glm.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisibleItems) >= totalCount) {
Log.v("scroll","scrolled"+pastVisibleItems);
}
}
}
}
});
Do add setOnScrollChangeListner to your NestedScrollView
nestedScrollview.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
if(loading)
onClick();
loading=false;
}
}
});
after loading data from server set boolean loading=true.
This question may be old, but to help others who stumbled upon this problem, i would like to share what i did. I had to implement onScroll Listener to recyclerview to load data from server and to make some UI changes. And also needed swipeRefresh Layout for refreshing data.
This was my xml file structure,
-RelativeLayout
-SwipeRefreshLayout
-NestedScrollView
-LinearLayout(Vertical)
-Multiple views required
After this, to detect up and down scrolling i implemented setOnScrollListener to the NestedScrollView.
Normal usage of SwipeRefreshLayout to refresh data.
And to load more data i implemented the logic inside onScrollListener of NestedScrollingView.
if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
// Load More Data
}
If you are recyclerView is embedded in any of the NestedScrollView, then you are supposed to attach the onScrollListener to NestedScrollView.
This will work!
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
nestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
totalItemCount = gridLayoutManager.getItemCount();
lastVisibleItem = gridLayoutManager.findLastVisibleItemPosition();
if (!loading
&& totalItemCount <= (lastVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
if (onLoadMoreListener != null) {
onLoadMoreListener.onLoadMore();
}
loading = true;
}
}
}
});
}
You said in a comment to your question "
it is under NestedScrollView which is under coordinator layout, if i remove this, Toolbar is not scrolling up". This is a mistake.
I have found that you cannot have it both ways, the CoordinatorLayout behaviour breaks when you have a RecyclerView inside a NestedScrollView to which you've added the behaviour. You need to use one or the other.
When you have a RecyclerView inside a NestedScrollView it will work as long as you set RecyclerView.setNestedScrollingEnabled(false), but as you found out this means that the OnScrollListener is not called.
The only way for all components to work correctly is to remove the NestedScrollView, make sure you do not set nesting scroll to false and work from there. Otherwise the RecyclerView.OnScrollListener events will not fire correctly.
Step 1 : Create EndlessRecyclerOnScrollListener
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
public static String TAG = EndlessRecyclerOnScrollListener.class.getSimpleName();
// use your LayoutManager instead
private LinearLayoutManager llm;
public EndlessRecyclerOnScrollListener(LinearLayoutManager sglm) {
this.llm = llm;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1)) {
onScrolledToEnd();
}
}
public abstract void onScrolledToEnd();
}
Step 2: Apply scroll listener to recycler view.
recyclerview.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager) {
#Override
public void onScrolledToEnd() {
Log.e("Position", "Last item reached");
if (loadMore == true) {
// put your Load more code
// add 10 by 10 to tempList then notify changing in data
}
}
});
remove the nested scroll view use linear or relative layout instead of it as root element then you can write recyclerview.setNestedScrollEnabled(false);

RecyclerView and SwipeRefreshLayout

I'm using the new RecyclerView-Layout in a SwipeRefreshLayout and experienced a strange behaviour. When scrolling the list back to the top sometimes the view on the top gets cut in.
If i try to scroll to the top now - the Pull-To-Refresh triggers.
If i try and remove the Swipe-Refresh-Layout around the Recycler-View the Problem is gone. And its reproducable on any Phone (not only L-Preview devices).
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/contentView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<android.support.v7.widget.RecyclerView
android:id="#+id/hot_fragment_recycler"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
That's my layout - the rows are built dynamically by the RecyclerViewAdapter (2 Viewtypes in this List).
public class HotRecyclerAdapter extends TikDaggerRecyclerAdapter<GameRow> {
private static final int VIEWTYPE_GAME_TITLE = 0;
private static final int VIEWTYPE_GAME_TEAM = 1;
#Inject
Picasso picasso;
public HotRecyclerAdapter(Injector injector) {
super(injector);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position, int viewType) {
switch (viewType) {
case VIEWTYPE_GAME_TITLE: {
TitleGameRowViewHolder holder = (TitleGameRowViewHolder) viewHolder;
holder.bindGameRow(picasso, getItem(position));
break;
}
case VIEWTYPE_GAME_TEAM: {
TeamGameRowViewHolder holder = (TeamGameRowViewHolder) viewHolder;
holder.bindGameRow(picasso, getItem(position));
break;
}
}
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
switch (viewType) {
case VIEWTYPE_GAME_TITLE: {
View view = inflater.inflate(R.layout.game_row_title, viewGroup, false);
return new TitleGameRowViewHolder(view);
}
case VIEWTYPE_GAME_TEAM: {
View view = inflater.inflate(R.layout.game_row_team, viewGroup, false);
return new TeamGameRowViewHolder(view);
}
}
return null;
}
#Override
public int getItemViewType(int position) {
GameRow row = getItem(position);
if (row.isTeamGameRow()) {
return VIEWTYPE_GAME_TEAM;
}
return VIEWTYPE_GAME_TITLE;
}
Here's the Adapter.
hotAdapter = new HotRecyclerAdapter(this);
recyclerView.setHasFixedSize(false);
recyclerView.setAdapter(hotAdapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
contentView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
loadData();
}
});
TypedArray colorSheme = getResources().obtainTypedArray(R.array.main_refresh_sheme);
contentView.setColorSchemeResources(colorSheme.getResourceId(0, -1), colorSheme.getResourceId(1, -1), colorSheme.getResourceId(2, -1), colorSheme.getResourceId(3, -1));
And the code of the Fragment containing the Recycler and the SwipeRefreshLayout.
If anyone else has experienced this behaviour and solved it or at least found the reason for it?
write the following code in addOnScrollListener of the RecyclerView
Like this:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener(){
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int topRowVerticalPosition =
(recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
Before you use this solution:
RecyclerView is not complete yet, TRY NOT TO USE IT IN PRODUCTION UNLESS YOU'RE LIKE ME!
As for November 2014, there are still bugs in RecyclerView that would cause canScrollVertically to return false prematurely. This solution will resolve all scrolling problems.
The drop in solution:
public class FixedRecyclerView extends RecyclerView {
public FixedRecyclerView(Context context) {
super(context);
}
public FixedRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FixedRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean canScrollVertically(int direction) {
// check if scrolling up
if (direction < 1) {
boolean original = super.canScrollVertically(direction);
return !original && getChildAt(0) != null && getChildAt(0).getTop() < 0 || original;
}
return super.canScrollVertically(direction);
}
}
You don't even need to replace RecyclerView in your code with FixedRecyclerView, replacing the XML tag would be sufficient! (The ensures that when RecyclerView is complete, the transition would be quick and simple)
Explanation:
Basically, canScrollVertically(boolean) returns false too early,so we check if the RecyclerView is scrolled all the way to the top of the first view (where the first child's top would be 0) and then return.
EDIT:
And if you don't want to extend RecyclerView for some reason, you can extend SwipeRefreshLayout and override the canChildScrollUp() method and put the checking logic in there.
EDIT2:
RecyclerView has been released and so far there's no need to use this fix.
I came across the same problem recently. I tried the approach suggested by #Krunal_Patel, But It worked most of the times in my Nexus 4 and didn't work at all in samsung galaxy s2. While debugging, recyclerView.getChildAt(0).getTop() is always not correct for RecyclerView. So, After going through various methods, I figured that we can make use of the method findFirstCompletelyVisibleItemPosition() of the LayoutManager to predict whether the first item of the RecyclerView is visible or not, to enable SwipeRefreshLayout.Find the code below. Hope it helps someone trying to fix the same issue. Cheers.
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
}
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
}
});
This is how I have resolved this issue in my case. It might be useful for someone else who end up here for searching solutions similar to this.
recyclerView.addOnScrollListener(new OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
// TODO Auto-generated method stub
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState)
{
// TODO Auto-generated method stub
//super.onScrollStateChanged(recyclerView, newState);
int firstPos=linearLayoutManager.findFirstCompletelyVisibleItemPosition();
if (firstPos>0)
{
swipeLayout.setEnabled(false);
}
else {
swipeLayout.setEnabled(true);
}
}
});
I hope this might definitely help someone who are looking for similar solution.
Source Code
https://drive.google.com/open?id=0BzBKpZ4nzNzURkRGNVFtZXV1RWM
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
}
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
swipeRefresh.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0);
}
});
None of the answers worked for me, but I managed to implement my own solution by making a custom implementation of LinearLayoutManager. Posting it here in case someone else needs it.
class LayoutManagerScrollFixed(context: Context) : LinearLayoutManager(context) {
override fun smoothScrollToPosition(
recyclerView: RecyclerView?,
state: RecyclerView.State?,
position: Int
) {
super.smoothScrollToPosition(recyclerView, state, position)
val child = getChildAt(0)
if (position == 0 && recyclerView != null && child != null) {
scrollVerticallyBy(child.top - recyclerView.paddingTop, recyclerView.Recycler(), state)
}
}
Then, you just call
recyclerView?.layoutManager = LayoutManagerScrollFixed(requireContext())
And it's working!
unfortunately, this is a known bug in LinearLayoutManager. It does not computeScrollOffset properly when the first item is visible.
will be fixed when it is released.
I have experienced same issue. I solved it by adding scroll listener that will wait until expected first visible item is drawn on the RecyclerView. You can bind other scroll listeners too, along this one. Expected first visible value is added to use it as threshold position when the SwipeRefreshLayout should be enabled in cases where you use header view holders.
public class SwipeRefreshLayoutToggleScrollListener extends RecyclerView.OnScrollListener {
private List<RecyclerView.OnScrollListener> mScrollListeners = new ArrayList<RecyclerView.OnScrollListener>();
private int mExpectedVisiblePosition = 0;
public SwipeRefreshLayoutToggleScrollListener(SwipeRefreshLayout mSwipeLayout) {
this.mSwipeLayout = mSwipeLayout;
}
private SwipeRefreshLayout mSwipeLayout;
public void addScrollListener(RecyclerView.OnScrollListener listener){
mScrollListeners.add(listener);
}
public boolean removeScrollListener(RecyclerView.OnScrollListener listener){
return mScrollListeners.remove(listener);
}
public void setExpectedFirstVisiblePosition(int position){
mExpectedVisiblePosition = position;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
notifyScrollStateChanged(recyclerView,newState);
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstVisible = llm.findFirstCompletelyVisibleItemPosition();
if(firstVisible != RecyclerView.NO_POSITION)
mSwipeLayout.setEnabled(firstVisible == mExpectedVisiblePosition);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
notifyOnScrolled(recyclerView, dx, dy);
}
private void notifyOnScrolled(RecyclerView recyclerView, int dx, int dy){
for(RecyclerView.OnScrollListener listener : mScrollListeners){
listener.onScrolled(recyclerView, dx, dy);
}
}
private void notifyScrollStateChanged(RecyclerView recyclerView, int newState){
for(RecyclerView.OnScrollListener listener : mScrollListeners){
listener.onScrollStateChanged(recyclerView, newState);
}
}
}
Usage:
SwipeRefreshLayoutToggleScrollListener listener = new SwipeRefreshLayoutToggleScrollListener(mSwiperRefreshLayout);
listener.addScrollListener(this); //optional
listener.addScrollListener(mScrollListener1); //optional
mRecyclerView.setOnScrollLIstener(listener);
I run into the same problem. My solution is overriding onScrolled method of OnScrollListener.
Workaround is here:
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int offset = dy - ydy;//to adjust scrolling sensitivity of calling OnRefreshListener
ydy = dy;//updated old value
boolean shouldRefresh = (linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0)
&& (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) && offset > 30;
if (shouldRefresh) {
swipeRefreshLayout.setRefreshing(true);
} else {
swipeRefreshLayout.setRefreshing(false);
}
}
});
Here's one way to handle this, which also handles ListView/GridView.
public class SwipeRefreshLayout extends android.support.v4.widget.SwipeRefreshLayout
{
public SwipeRefreshLayout(Context context)
{
super(context);
}
public SwipeRefreshLayout(Context context,AttributeSet attrs)
{
super(context,attrs);
}
#Override
public boolean canChildScrollUp()
{
View target=getChildAt(0);
if(target instanceof AbsListView)
{
final AbsListView absListView=(AbsListView)target;
return absListView.getChildCount()>0
&&(absListView.getFirstVisiblePosition()>0||absListView.getChildAt(0)
.getTop()<absListView.getPaddingTop());
}
else
return ViewCompat.canScrollVertically(target,-1);
}
}
The krunal's solution is good, but it works like hotfix and does not cover some specific cases, for example this one:
Let's say that the RecyclerView contains an EditText at the middle of screen. We start application (topRowVerticalPosition = 0), taps on the EditText. As result, software keyboard shows up, size of the RecyclerView is decreased, it is automatically scrolled by system to keep the EditText visible and topRowVerticalPosition should not be 0, but onScrolled is not called and topRowVerticalPosition is not recalculated.
Therefore, I suggest this solution:
public class SupportSwipeRefreshLayout extends SwipeRefreshLayout {
private RecyclerView mInternalRecyclerView = null;
public SupportSwipeRefreshLayout(Context context) {
super(context);
}
public SupportSwipeRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setInternalRecyclerView(RecyclerView internalRecyclerView) {
mInternalRecyclerView = internalRecyclerView;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mInternalRecyclerView.canScrollVertically(-1)) {
return false;
}
return super.onInterceptTouchEvent(ev);
}
}
After you specify internal RecyclerView to SupportSwipeRefreshLayout, it will automatically send touch event to SupportSwipeRefreshLayout if RecyclerView cannot be scrolled up and to RecyclerView otherwise.
Single line solution.
setOnScrollListener is deprecated.
You can use setOnScrollChangeListener for same purspose like this :
recylerView.setOnScrollChangeListener((view, i, i1, i2, i3) -> swipeToRefreshLayout.setEnabled(linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0));
In case of someone find this question and is not satisfied by the answer :
It seems that SwipeRefreshLayout is not compatible with adapters that have more than 1 item type.
If you are using recyclerview without scrollview you can do this and it will work
recyclerview.isNestedScrollingEnabled = true

Categories

Resources