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
Related
I have a long itemView in RecyclerView adapter and what I want is a listener to check if a view in itemView is visible or not, while scrolling recyclerView.
What I need is somthing like "onRecyclerViewScrolled()" in this example code:
public class MyAdapter extends RecyclerView.Adapter<ViewHolder> {
#Override
public void onBindViewHolder(#NonNull final ViewHolder myViewHolder, int
position) {
onRecyclerViewScrolled() {
if (!isVisible(myViewHolder.myView)) {
//do something
}
}
}
}
public static boolean isVisible(final View view) {
if (view == null) {
return false;
}
if (!view.isShown()) {
return false;
}
final Rect actualPosition = new Rect();
view.getGlobalVisibleRect(actualPosition);
final Rect screen = new Rect(0, 0, getScreenWidth(), getScreenHeight());
return actualPosition.intersect(screen);
}
public static int getScreenWidth() {
return Resources.getSystem().getDisplayMetrics().widthPixels;
}
public static int getScreenHeight() {
return Resources.getSystem().getDisplayMetrics().heightPixels;
}
You can set addOnScrollListener on your RecyclerView to get notified when the user scrolls, and you can get the first/last visible views from LinearLayoutMangar or GridLayoutManager:
RecyclerView recycler = findViewById(R.id.recycler_view);
reycler.setAdapter(YOUR_ADAPTER);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recycler.setLayoutManager(layoutManager);
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//First visible item position.
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
//Last visible item position.
int lastVisiblePosition = layoutManager.findLastVisibleItemPosition();
/*If you really need to access your visible views,
Loop through all the visible views*/.
for (int position = firstVisiblePosition; position == lastVisiblePosition; position++) {
View lastVisibleView = layoutManager.getChildAt(firstVisiblePosition);
//do something
}
dataList.get(firstVisiblePosition);
}
});
More info from documentation RecyclerView.OnScrollListener have to functions:
onScrolled(RecyclerView recyclerView, int dx, int dy)
Callback method to be invoked when the RecyclerView has been scrolled.
onScrollStateChanged(RecyclerView recyclerView, int newState)
Callback method to be invoked when RecyclerView's scroll state changes.
Apparently, onScrolled is called when the scroll is completed.
For more info see Yigit answer`s
Basically I want my recyclerview to automatically scroll to a position where the item is not half shown. Like the one in googleplay.
I have written a code
public void scrollToVisible(){
int firstVisibleItemPosition = ((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstVisibleItemPosition();
View view = recyclerView.getLayoutManager().getChildAt(0);
if (firstVisibleItemPosition > 0 && view != null) {
int offsetTop = view.getTop();
if (firstVisibleItemPosition - 1 >= 0 && adapter.getItemCount() > 0) {
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(firstVisibleItemPosition - 1, offsetTop);
}
}
}
The problem comes next. I dont know where to put this code. I have a vague idea to put it when the recyclerview stops on scrolling but I've been searching for quite some time now and i cant find such a method. when i put it on the onScroll some unexpected behavior comes out
You may create a CustomRecyclerView extending RecyclerView
public class CustomRecyclerView extends RecyclerView {
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
// check if scrolling has stopped
if (state == SCROLL_STATE_IDLE) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) getLayoutManager();
// use code here
}
}
If it maybe of any help to someone:
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.d("y value",String.valueOf(dy));
if (dy > 0) {
//scrolling up
} else {
// Scrolling down
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
// Do something
Log.e("SCROLL_STATE_FLING","SCROLL_STATE_FLING");
} else if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
Log.e("SCROLLTOUCH_SCROLL","SCROLL_STATE_TOUCH_SCROLL");
//slideUp(party_view);
// Do something
} else if (newState==AbsListView.OnScrollListener.SCROLL_STATE_IDLE){
// Do something
//slideDown(party_view);
Log.e("SCROLL_STATE_IDLE","SCROLL_STATE_IDLE");
}
}
});
complete example with UI synchronization
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Handler handler = new Handler(getMainLooper());
if (newState == 0) {
handler.removeCallbacks(MainActivity.this::hideFab);
handler.postDelayed(MainActivity.this::showFab, 400);
} else {
handler.removeCallbacks(MainActivity.this::showFab);
handler.postDelayed(MainActivity.this::hideFab, 100);
}
}
});
private void hideFab() {
addFile.hide();
addText.hide();
camera.hide();
}
private void showFab() {
addFile.show();
addText.show();
camera.show();
}
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);
I am developing an App for Android TV via Nexus Player. In my App, I use the VerticalGridFragment to show TV shows. For shows' number is 2000+, I want to use page loading when the VerticalGridView scrolls to its bottom.
However, I get stuck on how to judge the VerticalGridView has scrolled to bottom.
I use reflect to get the VerticalGridView,and addScrollListener() to it.Here the codes:
private void addGridScrollListener() {
try {
Class<VerticalGridFragment> VerticalGridFragmentClass = VerticalGridFragment.class;
Field verticalGridViewHolder = VerticalGridFragmentClass.getDeclaredField("mGridViewHolder");
verticalGridViewHolder.setAccessible(true);
VerticalGridPresenter.ViewHolder viewHolder = (VerticalGridPresenter.ViewHolder) verticalGridViewHolder.get(this);
VerticalGridView gridView = viewHolder.getGridView();
gridView.addOnScrollListener(scrollListener);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
};
RecyclerView doesn't has the function such as getLastVisiableItemPosition, so I can't know if the VerticalGridView has scrolled to bottom or not.
What should I do in the scrollListener ? Or is there any other solutions?
Thanks!
I found the solution:
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState != RecyclerView.SCROLL_STATE_IDLE) {
return;
}
//get current last child View
View lastChildView = recyclerView.getLayoutManager().getChildAt(recyclerView.getLayoutManager().getChildCount() - 1);
//get the bottom
int lastChildBottom = lastChildView.getBottom();
// get recyclerView's bottom
int recyclerBottom = recyclerView.getBottom() - recyclerView.getPaddingBottom();
//get last childview's position
int lastPosition = recyclerView.getLayoutManager().getPosition(lastChildView);
if (lastChildBottom == recyclerBottom && lastPosition == recyclerView.getLayoutManager().getItemCount() - 1) {
// yes to bottom.
}
}
};
I have problem with my layout, I created SwipeRefreshLayout with RecyclerView inside.
in android 4.2.2+ all is working good, but in andorid 2.3.4 I cant to scroll up because in any place in the RecyclerView it will refresh, and I must to scroll down and then scroll up.
This is my code:
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/forum_swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/LVP"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center" />
</android.support.v4.widget.SwipeRefreshLayout>
I found this issue:https://code.google.com/p/android/issues/detail?id=78191 but no a solution.
Any idea how to fix it?
override RecyclerView's method OnScrollStateChanged
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
// TODO Auto-generated method stub
//super.onScrollStateChanged(recyclerView, newState);
try {
int firstPos = mLayoutManager.findFirstCompletelyVisibleItemPosition();
if (firstPos > 0) {
mSwipeRefreshLayout.setEnabled(false);
} else {
mSwipeRefreshLayout.setEnabled(true);
if(mRecyclerView.getScrollState() == 1)
if(mSwipeRefreshLayout.isRefreshing())
mRecyclerView.stopScroll();
}
}catch(Exception e) {
Log.e(TAG, "Scroll Error : "+e.getLocalizedMessage());
}
}
Check if Swipe Refresh is Refreshing and try to Scroll up then you got error, so when swipe refresh is going on and i try do this mRecyclerView.stopScroll();
I fixed the scroll up issue using the following code :
private RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager manager = ((LinearLayoutManager)recyclerView.getLayoutManager());
boolean enabled =manager.findFirstCompletelyVisibleItemPosition() == 0;
pullToRefreshLayout.setEnabled(enabled);
}
};
Then you need to use setOnScrollListener or addOnScrollListener depending if you have one or more listeners.
Unfortunately, this is a known issue and will be fixed in a future release.
https://code.google.com/p/android/issues/detail?id=78191
Meanwhile, if you need urgent fix, override canChildScrollUp in SwipeRefreshLayout.java and call recyclerView.canScrollVertically(mTarget, -1). Because canScrollVertically was added after gingerbread, you'll also need to copy that method and implement in recyclerview.
Alternatively, if you are using LinearLayoutManager, you can call findFirstCompletelyVisibleItemPosition.
Sorry for the inconvenience.
You can disable/enable the refresh layout based on recyclerview's scroll ability
public class RecyclerSwipeRefreshHelper extends RecyclerView.OnScrollListener{
private static final int DIRECTION_UP = -1;
private final SwipeRefreshLayout refreshLayout;
public RecyclerSwipeRefreshHelper(
SwipeRefreshLayout refreshLayout) {
this.refreshLayout = refreshLayout;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
refreshLayout.setEnabled((recyclerView.canScrollVertically(DIRECTION_UP)));
}
}
You can override the method canChildScrollUp() in SwipeRefreshLayout like this:
public boolean canChildScrollUp() {
if (mTarget instanceof RecyclerView) {
final RecyclerView recyclerView = (RecyclerView) mTarget;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int position = ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition();
return position != 0;
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(null);
for (int i = 0; i < positions.length; i++) {
if (positions[i] == 0) {
return false;
}
}
}
return true;
} else if (android.os.Build.VERSION.SDK_INT < 14) {
if (mTarget instanceof AbsListView) {
final AbsListView absListView = (AbsListView) mTarget;
return absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0)
.getTop() < absListView.getPaddingTop());
} else {
return mTarget.getScrollY() > 0;
}
} else {
return ViewCompat.canScrollVertically(mTarget, -1);
}
}
Based on #wrecker answer (https://stackoverflow.com/a/32318447/7508302).
In Kotlin we can use extension method. So:
class RecyclerViewSwipeToRefresh(private val refreshLayout: SwipeToRefreshLayout) : RecyclerView.OnScrollListener() {
companion object {
private const val DIRECTION_UP = -1
}
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
refreshLayout.isEnabled = !(recyclerView?.canScrollVertically(DIRECTION_UP) ?: return)
}
}
And let's add extension method to RecyclerView to easly apply this fix to RV.
fun RecyclerView.fixSwipeToRefresh(refreshLayout: SwipeRefreshLayout): RecyclerViewSwipeToRefresh {
return RecyclerViewSwipeToRefresh(refreshLayout).also {
this.addOnScrollListener(it)
}
}
Now, we can fix recyclerView using:
recycler_view.apply {
...
fixSwipeToRefresh(swipe_container)
...
}
Following code is working for me, please ensure that it is placed below the binding.refreshDiscoverList.setOnRefreshListener{} method.
binding.swipeToRefreshLayout.setOnChildScrollUpCallback(object : SwipeRefreshLayout.OnChildScrollUpCallback {
override fun canChildScrollUp(parent: SwipeRefreshLayout, child: View?): Boolean {
if (binding.rvDiscover != null) {
return binding.recyclerView.canScrollVertically(-1)
}
return false
}
})