I'm trying to implement an horizontal RecycleView in my Android app that displays content with never-ending scroll animation. See example of required UI here.
My question is, what would be the best practice on how to achieve this goal? (RecycleView should support scroll gestures from the user).
If I'm correct you're looking for the endless scrolling or infinite scrolling (Not Official names for that)
If it is correct then it shows like say you have 5 images in your recyclerview now when the user reaches the end item then the recycler view starts again from the first item like
item1, item2, item3, item4, item5 and again item5, item1, item2.....
To achieve this
Go to your adapter class and do the following code.
#Override
public int getItemCount() {
return items == null ? 0 : items.size() * 2; //Here instead of items.size(); you have to change like this
}
Now in your OnBindViewHolder method
MODEL_CLASS item = items.get(position%items.size());
Now head over to the activity where you set the adapter to recyclerview and add the following code
YOUR_ADAPTER_NAME.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int firstItemVisible = linearLayoutManager.findFirstVisibleItemPosition();
if (firstItemVisible != 0 && firstItemVisible % YOUR_LIST_ITEM.size() == 0) {
recyclerView.getLayoutManager().scrollToPosition(0);
}
}
});
By this you can see the items when scrolled to right it continues loops in your list.
Related
I have a scenario in which I need to know if a child view of a RecyclerView item is visible on screen.
In this case, each RecyclerView item has a TextView and I need to know if that TextView is fully visible on screen. I've already figured this part out, but now my question is this:
How can I make a call to the adapter from my fragment to let it know that the view is visible on screen? What best practice should I follow for this?
Here is my fragment class method where I get the visible child view:
private void getFirstVisibleChildView() {
int findFirstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
int findLastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
int [] positions = { findFirstVisibleItemPosition, findLastVisibleItemPosition };
PostAdapter.PostViewHolder viewHolder;
Rect scrollBounds = new Rect();
recyclerView.getDrawingRect(scrollBounds);
int[] location = new int[2];
for (int position : positions) {
RecyclerView.ViewHolder item = recyclerView.findViewHolderForAdapterPosition(position);
if (item instanceof PostAdapter.PostViewHolder) {
viewHolder = (PostAdapter.PostViewHolder) item;
viewHolder.getChildView().getLocationInWindow(location);
if (location[1] < 0 || location[1] > scrollBounds.bottom) {
// Not visible
} else {
// Visible
// How to call the RecyclerView adapter here and be able to manipulate it?
// Custom listener, direct call to ViewHolder, or some other method?
}
}
}
}
Thanks!
You can set addOnScrollListener on the RecyclerView, so it checkes if your specific view is fully shown whenever it's scrolled
And normally create a custom method in your adapter that you want to call within this listener when the above condition is met.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mRecyclerViewTafsir.getLayoutManager() == null) return;
// Here you can check if the particular textView is fully
// appeared on the screen >> You already did this part
mAdapter.callBack(); // call a method in the adapter when the condition is met
}
});
findLastCompletelyVisibleItemPosition : Returns the adapter position of the last fully visible view. This position does not include adapter changes that were dispatched after the last layout pass.
findLastVisibleItemPosition: Returns the adapter position of the last visible view. This position does not include adapter changes that were dispatched after the last layout pass.
I have 16 items . So when scrolled till end (when last item is fully visible) both method gives result as 16. But when i scroll till end but last item is half visible findLastCompletelyVisibleItemPosition shows 14 and findLastVisibleItemPosition shows 15.
Can someone explain me why it is displaying 14 ? and what is the exact difference between these two function.
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerView.LayoutManager linearLayoutManager = recyclerView.getLayoutManager();
if(linearLayoutManager != null && linearLayoutManager instanceof LinearLayoutManager) {
int position = ((LinearLayoutManager) linearLayoutManager).findLastCompletelyVisibleItemPosition();
int position1 = ((LinearLayoutManager) linearLayoutManager).findLastVisibleItemPosition();
Log.d(TAG, "position: " + position);
Log.d(TAG, "position1: " + position1);
}
}
If you have 16 items it is impossible to return 16 as the visible position since your last position is the 15th.
Secondly it looks pretty straight forward to understand what each method does from their naming. If the last position is the 15th and you can see half of it, findLastCompletelyVisibleItemPosition will return 14 and findLastVisibleItemPosition will return 15.
How do I get the position of a list item that's on focus after scrolling the RecyclerView? Will this work?
recyclerview1.addOnScrollListener(new RecyclerView.OnScrollListener())
If yes, then how?
Yes there are two ways of doing this.
One using onBindViewHolder , in onBindViewHolder add
Log.d("onBindViewHolder", "position =>"+position);
count++; //for the position
but remember in this way count call several time for each news and you must count only in first call.
Other way around is using layoutManagers i.e LinearLayoutManager or GridLayoutManager by doing this
int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
int lastVisiblePosition = layoutManager.findLastVisibleItemPosition();
OR for scrollListener you can do this
recycler.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
//you can use other methods as per your requirements
Toast.makeText(mContext, "Scrolled to"+layoutManager.findLastCompletelyVisibleItemPosition(), Toast.LENGTH_SHORT).show();
})
Hope this helps you.
I just faced this problem, and i got I put the recyclerView in scrollView So please never put the recyclerview inside the scrollView that may also cause mLayoutManager.findFirstVisibleItemPosition() that always return a fixed value. and also check recyclerview.setNestedScrollingEnabled(false);
I'm looking for best practices. I want to implement load more [progressbar] after every 10 items (Using RecyclerView).
In past, I did this by simply creating Loader class item and just added into the specific position in my List. Well, this time, I'm using PairList and it's impossible to add Loader class item (even tho I personally think, that's a pretty clean way to do it).
I found a few solutions on SO, which are almost the same: How to implement load more recyclerview in android
Is there any other way I could implement this load more progressbar after every 10th element (First load 10 items from API, then, when user reaches 10th item, we show progress bar [meanwhile we fetch the next 10 items], remove progress bar, add 10 items and so on).
Use this InfiniteScrollListener in your recycler scrollListener:
public abstract class InfiniteScrollListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager)recyclerView.getLayoutManager();
int visibleItemCount = linearLayoutManager.getChildCount();
int totalItemCount = linearLayoutManager.getItemCount();
int pastVisiblesItems = linearLayoutManager.findFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
loadMore();
}
}
}
protected abstract void loadMore();}
recyclerView Listener:
recyclerView.addOnScrollListener(new InfiniteScrollListener() {
#Override
protected void loadMore() {
boolean willLoad = //get your last api call data. if empty set false
int offset = recyclerView.getAdapter().getItemCount();
if(willLoad){
willLoad=false;
onCallApi(offset,10);
}
}
});
your api:
private void onCallApi(int offset,int limit){
//your api
}
I have a vertically scrolling RecyclerView with horizontally scrolling inner RecyclerViews just like this.
With this implementation, users can scroll each horizontal recyclerview synchronously. However, when a user scroll vertically to the parent recyclerView, a new horizontal recyclerview which has just attached on window doesn't display on same scroll x position. This is normal. Because it has just created.
So, I had tried to scroll to the scrolled position before it was displayed. Just like this:
Note: this is in adapter of the parent recyclerview whose orientation is vertical.
#Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
super.onViewAttachedToWindow(holder);
CellColumnViewHolder viewHolder = (CellColumnViewHolder) holder;
if (m_nXPosition != 0) {
// this doesn't work properly
viewHolder.m_jRecyclerView.scrollBy(m_nXPosition, 0);
}
}
As you can see, scrollBy doesn't effect for row 10, row 11, row 12 and row 13 After that, I debugged the code to be able find out find out what's happening. When I set scroll position using scrollBy, childCount() return zero for row 10, row 11, row 12 and row 13 So they don't scroll. But why ? and Why others work ?
How can I fix this ?
Is onViewAttachedToWindow right place to scroll new attached recyclervViews ?
Note: I have also test scrollToPosition(), it doesn't get any problem like this. But I can't use it at my case. Because users can scroll to the any x position which may not the exact position. So I need to set scroll position using x value instead of the position.
Edit: You can check The source code
I found a solution that is use scrollToPositionWithOffset method instead using scrollBy. Even if both of two scroll another position, they have really different work process in back side.
For example: if you try to use scrollBy to scroll any pixel position and your recyclerView had not been set any adapter which means there is no any data to display and so it has no any items yet, then scrollBy doesn't work. RecyclerView uses its layoutManager's scrollBy method. So in my case, I am using LinearLayoutManager to the horizontal recyclerViews.
Lets see what it's doing :
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
ensureLayoutState();
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
As you can see scrollBy ignores the scroll intentions if there is no any child at that time.
if (getChildCount() == 0 || dy == 0) {
return 0;
}
On the other hand scrollToPosition can work perfectly even if there is no any set data yet.
According to the Pro RecyclerView slide, the below sample works perfectly. However you can not do that with scrollBy.
void onCreate(SavedInstanceState state) {
....
mRecyclerView.scrollToPosition(selectedPosition);
mRecyclerView.setAdapter(myAdapter);
}
As a result, I have changed little thing to use scrollToPositionWithOffset().
Before this implementation I was calculating the exact scroll x position as a pixel.
After that, when the scroll came idle state, calculating the first complete visible position to the first parameter of the scrollToPositionWithOffset().
For second parameter which is the offset, I am getting the value using view.getLeft() function which helps to get left position of this view relative to its parent.
And it works perfectly!!