How would you detect when the RecyclerView is near the bottom? Right now it only detects when it's at the absolute bottom and can't scroll anymore:
mGridRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(!mGridRecycler.canScrollVertically(1) && newState==RecyclerView.SCROLL_STATE_IDLE){
//Make network call here
}
}
});
In your OnScrollListener calculate the difference between the bottom of the last child view in the Recycler view and the bottom of the Recycler view itself, if the last child view is close enough to the bottom of the recycler view you can make your network call.
mGridRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
View view = (View) recyclerView.getChildAt(recyclerView.getChildCount() - 1);
int difference = (view.getBottom() - (recyclerView.getHeight() + recyclerView.getScrollY()));
if (difference == 0) {
//Make network call here
}
}
});
difference == 0 means the absolute bottom when the Recycler view can't scroll anymore, you can change the condition to check when it 5px from the recycler view bottom difference == 5.
Related
I have a recyclerview with horizontal layout and only one view is visible at a time:
mRecyclerView = findViewById(R.id.rvmain);
mRecyclerView.setOnFlingListener(null);
final SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(mRecyclerView);
mLayoutManager = new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new MainActivityRVAdapter(postsModels,MainActivity.this);
mRecyclerView.setAdapter(mAdapter);
using onScrolllistener, everytime I scroll I want to know the starting position and end position. I am using the below code:
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(count == 0) {
View centerView = snapHelper.findSnapView(mLayoutManager);
if(centerView != null){
initial_position = mLayoutManager.getPosition(centerView);
//initial_position2 = ((LinearLayoutManager)mRecyclerView.getLayoutManager()).findFirstVisibleItemPosition();
Log.e("Initial Item Position:",""+initial_position);
//Log.e("Initial Item Position2:",""+initial_position2);
}
count ++;
}
// get newstate position
if(newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
View centerView = snapHelper.findSnapView(mLayoutManager);
if(centerView != null){
int pos = mLayoutManager.getPosition(centerView);
count = 0; // in idle state clear the count again
Log.e("Snapped Item Position:",""+pos);
}
}
}
The result i get is:
E/Initial Item Position:: 0
E/Snapped Item Position:: 1
E/Initial Item Position:: 1
E/Snapped Item Position:: 1
E/Initial Item Position:: 1
E/Snapped Item Position:: 1
E/Initial Item Position:: 1
E/Snapped Item Position:: 1
And it returns positions multiple times. I wanted to check the difference between final and initial positions.
I wanted only the start and end so that i can compare and check i.e:
E/Initial Item Position:: 0 and
E/Snapped Item Position:: 1
I've met the same problem. And what i found:
RecyclerView.OnScrollListener calls onScrolled(...) multiple times while in SCROLL_STATE_DRAGGING or SCROLL_STATE_SETTLING.
I started listen to a second calback:
onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState)
We are interesting in a final state SCROLL_STATE_IDLE.
So in this case we have to override onScrollStateChanged(...), check and ignore all states except SCROLL_STATE_IDLE and get final position while idle.
But as described in docs, onScrolled(...)
will also be called if visible item range changes after a layout
calculation. In that case, dx and dy will be 0.
On practice i found that if we call adapter.notifyDataSetChanged() and visible position is "0" (ie. on first start), onScrollStateChanged(...) will not be called and onScrolled(...) will be called once with dx == dy == 0.
A final variant could be as following:
private int recyclerVisiblePosition;
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState != RecyclerView.SCROLL_STATE_IDLE) {
return;
}
getNewPosition(recyclerView);
}
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dx == 0 && dy == 0) {
getNewPosition(recyclerView);
}
}
private void getNewPosition(#NonNull final RecyclerView recyclerView) {
final LinearLayoutManager layoutManager = ((LinearLayoutManager)recyclerView.getLayoutManager());
if (layoutManager != null) {
recyclerVisiblePosition = layoutManager.findLastVisibleItemPosition();
}
}
Reason:
When you scroll on the RecyclerView, it will trigger a SCROLL_STATE_IDLE event at the end, this is due to your action that you fling, and then you let go the RecyclerView.
The SnapHelper will listen to RecyclerView's first SCROLL_STATE_IDLE event so that it can prepare to calculate the remaining distance that IT needs to scroll to the target View, when it does that, it will trigger a SCROLL_STATE_IDLE event the second time, since SnapHelper is "helping" you to scroll it.
===============================================================
Solution:
You can create a global variable and use it as a "flag", this will avoid it from being called multiple times. The final code will look something like this:
private boolean isFirstTimeCall = true;
recyclerViewExample.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (isFirstTimeCall) {
isFirstTimeCall = false;
// Do your stuff here...
}
}
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
isFirstTimeCall = true;
}
}
});
I had the same issue and I recognized that it was the snap helper which causes the issue. I guess it is because snap helper triggers a small second movement after your scrolling is complete. So onScrollStateChanged is called twice.
Fortunately, now there is ViewPager2. So I didn't have to go nuts trying to listen snapping. As I had also one visible view at a time like you, I replaced Recyclerview with ViewPager2 and instead of Recyclerview.OnScrollListener, I used ViewPager2.OnPageChangeCallback. That solved the issue.
Hello,
i want to change a view's padding according to bottomSheet's slideOffset.
But when i tried to change view's padding on BoottomSheetBehaviour Callback, BottomSheet sliding speed goes slow down. here is my code:
View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetExpended = false;
} else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetExpended = true;
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
int padding = (int) (10 * slideOffset);
nestedScrollView.setPadding(padding, 0, padding, 0);
}
});
Trying to change nestedScrollview's Padding.
How to solve this problem?
The slideOffset goes from 0 to 1 as you slide up and from 1 to 0 as you slide down. If you have want to go from having padding to no padding as you slide up and from no padding to having padding as you slide down, then do it like this.
View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
behavior = BottomSheetBehavior.from(bottomSheet);
// Get Padding value outside of onSlide
final float originalPadding = getActivity().getResources().getDimension(R.dimen.original_padding);
behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetExpended = false;
} else if (newState == BottomSheetBehavior.STATE_EXPANDED) {
bottomSheetExpended = true;
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
nestedScrollView.setPadding(Math.round(originalPadding * (1 - slideOffset)),
0, Math.round(originalPadding * (1 - slideOffset)), 0);
}
});
To do it the opposite way, just don't subtract 1 from the slide offset.
Note: I am only using Math.round() because I am getting the padding as a float from the dimens resources outside of onSlide().
Don't do anything too resource intensive inside of onSlide() because it gets called a bunch and that could be the reason your bottomsheet is sliding slowly even though it doesn't look like you are doing that here.
Also, you don't need to keep track of the Bottom Sheet's state because you can always call:
if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
// Bottom sheet is expanded
}
else if (behavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
// Bottom sheet is collapsed
}
The onStateChanged() method is more for reacting to the state being changed with things like showing or hiding other views, etc.
So if you have a RecyclerView that extends to the end of the screen and use a FloatingActionButton or the third-party FloatingActionMenu, then there is a slight problem with cover-up: If you scroll to the end of the list, the floating button covers up part of the row and there is no way to see or get to what's underneath it.
Is there a way Android allows you to:
Detect if your list has a sufficient number of items in it, i.e. if there is a row at the bottom of the screen visible.
and
Slide the buttons out of the way if you scroll down to the very end, and then slide them back in if you start scrolling back up?
Edit: Or, alternatively, add dynamic padding to the end of the RecyclerView that only shows up if you've scrolled all the way down?
The common pattern to solve the problem of covering up parts of UI is to hide FAB as soon as the user starts scrolling down ... You can achieve this by this code fragment (used with RecyclerView):
fDemandsRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if(dy > 0 ){
if(fFab.isShown()) fFab.hide();
} else {
if(!fFab.isShown()) fFab.show();
}
}
});
If you insist on hiding it on the very end and you are using RecyclerView with LinearLayoutManager, you can check method findLastCompletelyVisibleItemPosition() on the LayoutManager object during the OnScrollListener callback ...
Of course it is possible. The solution was posted here.
The main idea behind it is that FloatingActionButton.Behavior is overriden and changed, so that it reacts to RecyclerView and CoordinatorLayout scrolling.
However, in your case RecyclerViewScrollListener must be different. Here is the implementation:
private class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
FloatingActionButton mChild;
public RecyclerViewScrollListener(FloatingActionButton child) {
this.mChild = child;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && recyclerView.canScrollVertically(Integer.MAX_VALUE)) {
mChild.show();
} else {
mChild.hide();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(Integer.MAX_VALUE)) {
mChild.hide();
}
}
}
Edit:
When filling RecyclerView with data, invoke such function on it in order to disable or enable scrolling:
private void changeCoordinatorLayoutScrollPossibility(final RecyclerView recyclerView) {
new Handler().post(new Runnable() {
#Override
public void run() {
if (recyclerView.getMeasuredHeight() < alertsRecyclerView.computeVerticalScrollRange()) {
recyclerView.setNestedScrollingEnabled(true);
} else {
recyclerView.setNestedScrollingEnabled(false);
}
}
});
}
I want to add back to top button in my app. I use recyclerview for displaying items. I added button and when I click on it, view is going to top, but my goal is to show this button only if application is scrolled down or up, and my first item is not visible (I hope thats clear explanation). I tried with
LayoutManager.findFirstCompletelyVisibleItemPosition();
and other methods for LayoutManager but without acceptable effects.
My solution is showed below. I add OnScrollListener on my RecyclerView, and when view is scrolled I check if the first item is visible and I set visibility of my button.
MyRecyclerView.addOnScrollListener(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) {
int visibility = (MyLayoutManager.findFirstCompletelyVisibleItemPosition() != 0) ? View.VISIBLE : View.GONE;
btn.setVisibility(visibility);
}
});
I have an arraylist of recyclerveiws in another recyclerview and want to know how to scroll all of them when one is scrolled.
This is what I have so far, however it gives me a stack overflow error:
holder.rv.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);
if (dx != -199382734)
for (sched_vh vh : vhs) {
if (vh != holder)
vh.rv.scrollBy(-199382734, dy);
}
}
});
I have worked on this problem with my friend for a few hours..the final answer is really simple.
The main idea is that two recyclerviews should use the same onscrollListener instead of different ones, so that this listener could get which recyclerview is scrolling, and avoid stack over flow. please try following
//rcv and leftrcv are two recyclerviews
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (recyclerView == leftrcv) {
rcv.removeOnScrollListener(this);
rcv.scrollBy(0, dy);
rcv.addOnScrollListener(this);
} else if (recyclerView == rcv) {
leftrcv.removeOnScrollListener(this);
leftrcv.scrollBy(0, dy);
leftrcv.addOnScrollListener(this);
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
};
leftrcv.addOnScrollListener(scrollListener);
rcv.addOnScrollListener(scrollListener);
But I don't know whether this will result in performance issue...
Your implementation is probably wrong. You should likely have one RecyclerView that has different view types.
From your comments it seems you want several vertical RecycleViews besides each other and want to horizontally scroll them.
If you want each of the vertical lists fill the screen you'd want to use a ViewPager which allows you to swipe horizontally through a list of views (or fragments).
Otherwise you could have a look at HorzizontalScrollView, though that would only make sense for a small set of vertical lists.