RecyclerView for large items - android

In our app we have a RecyclerView that shows large items in horizontal list. We use LinearLayoutManager to do that. The issue occurs when user scrolls the screen. When next large items needs to appear on a screen UI is frozen.
What we wanted to implement is some kind of lazy loading for RecyclerView item. So that similar to facebook's implementation user saw an stub, and once he stopped scrolling UI would update to show actual content.
The question is - what is the correct extension point for that? Should I implement custom LayoutManager? Or there are existing solutions for that?

If you are using Xamarin as your tag say you can do this:
public class XamarinRecyclerViewOnScrollListener : RecyclerView.OnScrollListener
{
public delegate void LoadMoreEventHandler(object sender, EventArgs e);
public event LoadMoreEventHandler LoadMoreEvent;
private LinearLayoutManager LayoutManager;
public XamarinRecyclerViewOnScrollListener (LinearLayoutManager layoutManager)
{
LayoutManager = layoutManager;
}
public override void OnScrolled (RecyclerView recyclerView, int dx, int dy)
{
base.OnScrolled (recyclerView, dx, dy);
var visibleItemCount = recyclerView.ChildCount;
var totalItemCount = recyclerView.GetAdapter().ItemCount;
var pastVisiblesItems = LayoutManager.FindFirstVisibleItemPosition();
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
LoadMoreEvent (this, null);
}
}
}
To use this in your view use:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = base.OnCreateView(inflater, container, savedInstanceState);
var recyclerView = view.FindViewById<RecyclerView>(Resource.Id.my_recycler_view);
if (recyclerView != null)
{
recyclerView.HasFixedSize = true;
var layoutManager = new LinearLayoutManager(Activity);
var onScrollListener = new XamarinRecyclerViewOnScrollListener (layoutManager);
onScrollListener.LoadMoreEvent += (object sender, EventArgs e) => {
//Load more stuff here
};
recyclerView.AddOnScrollListener (onScrollListener);
recyclerView.SetLayoutManager(layoutManager);
}
return view;
}

Without seeing exactly what you're doing, I can only make a high level suggestion. And that suggestion is attaching a scroll listener to the recyclerView.
How would this work for you?
final int firstVisiblePosition = layoutManager.findFirstVisibleItemPosition();
final int lastVisiblePosition = layoutManager.findLastVisibleItemPosition();
myRecyclerView.setOnScrollListener(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);
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
for (int i = firstVisiblePosition; i <= lastVisiblePosition; i++) {
// start content loading here
recyclerView.getChildAt(i).loadMyContent;
}
break;
case RecyclerView.SCROLL_STATE_DRAGGING:
for (int i = firstVisiblePosition; i <= lastVisiblePosition; i++) {
// cancel content loading here
recyclerView.getChildAt(i).cancelLoad;
}
break;
case RecyclerView.SCROLL_STATE_SETTLING:
// you could load content here, but it could end up off screen by the time it stops, so I would not recommend it
break;
}
}
});
I would like to add that if you are loading images, take a look at the Picasso library:
From Picasso's webpage:
ADAPTER DOWNLOADS
Adapter re-use is automatically detected and the previous download canceled.
#Override public void getView(int position, View convertView, ViewGroup parent) {
SquaredImageView view = (SquaredImageView) convertView;
if (view == null) {
view = new SquaredImageView(context);
}
String url = getItem(position);
Picasso.with(context).load(url).into(view);
}

Related

How to use scroll listener in onBindViewHolder in Adapter of a RecyclerView?

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

Recycler View - Show only minimum elements and expand recycler view if user clicks expand [duplicate]

I would like to change ListView to RecyclerView. I want to use the onScroll of the OnScrollListener in RecyclerView to determine if a user scrolled to the end of the list.
How do I know if a user scrolls to the end of the list so that I can fetch new data from a REST service?
Thanks to #Kushal and this is how I implemented it
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy > 0) { //check for scroll down
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount) {
loading = false;
Log.v("...", "Last Item Wow !");
// Do pagination.. i.e. fetch new data
loading = true;
}
}
}
}
});
Don't forget to add
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
Make these variables.
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
Set on Scroll for recycler view.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
Log.i("Yaeye!", "end called");
// Do something
loading = true;
}
}
});
Note : Make sure you are using LinearLayoutManager as layout manager for RecyclerView.
LinearLayoutManager mLayoutManager;
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
and for a grid
GridLayoutManager mLayoutManager;
mLayoutManager = new GridLayoutManager(getActivity(), spanCount);
mRecyclerView.setLayoutManager(mLayoutManager);
Have fun with your endless scrolls !! ^.^
Update : mRecyclerView.setOnScrollListener() is deprecated just replace with mRecyclerView.addOnScrollListener() and the warning will be gone! You can read more from this SO question.
Since Android now officially support Kotlin, here is an update for the same -
Make OnScrollListener
class OnScrollListener(val layoutManager: LinearLayoutManager, val adapter: RecyclerView.Adapter<RecyclerAdapter.ViewHolder>, val dataList: MutableList<Int>) : RecyclerView.OnScrollListener() {
var previousTotal = 0
var loading = true
val visibleThreshold = 10
var firstVisibleItem = 0
var visibleItemCount = 0
var totalItemCount = 0
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
visibleItemCount = recyclerView.childCount
totalItemCount = layoutManager.itemCount
firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (loading) {
if (totalItemCount > previousTotal) {
loading = false
previousTotal = totalItemCount
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
val initialSize = dataList.size
updateDataList(dataList)
val updatedSize = dataList.size
recyclerView.post { adapter.notifyItemRangeInserted(initialSize, updatedSize) }
loading = true
}
}
}
and add it to your RecyclerView like this
recyclerView.addOnScrollListener(OnScrollListener(layoutManager, adapter, dataList))
For a full code example, feel free to refer this Github repo.
For those who only want to get notified when the last item is totally shown, you can use View.canScrollVertically().
Here is my implementation:
public abstract class OnVerticalScrollListener
extends RecyclerView.OnScrollListener {
#Override
public final void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (!recyclerView.canScrollVertically(-1)) {
onScrolledToTop();
} else if (!recyclerView.canScrollVertically(1)) {
onScrolledToBottom();
} else if (dy < 0) {
onScrolledUp();
} else if (dy > 0) {
onScrolledDown();
}
}
public void onScrolledUp() {}
public void onScrolledDown() {}
public void onScrolledToTop() {}
public void onScrolledToBottom() {}
}
Note: You can use recyclerView.getLayoutManager().canScrollVertically() if you want to support API < 14.
Here is another approach. It will work with any layout manager.
Make Adapter class abstract
Then create an abstract method in adapter class (eg. load())
In onBindViewHolder check the position if last and call load()
Override the load() function while creating the adapter object in your activity or fragment.
In the overided load function implement your loadmore call
For a detail understanding I wrote a blog post and example project get it here
http://sab99r.com/blog/recyclerview-endless-load-more/
MyAdapter.java
public abstract class MyAdapter extends RecyclerView.Adapter<ViewHolder>{
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
//check for last item
if ((position >= getItemCount() - 1))
load();
}
public abstract void load();
}
MyActivity.java
public class MainActivity extends AppCompatActivity {
List<Items> items;
MyAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
adapter=new MyAdapter(items){
#Override
public void load() {
//implement your load more here
Item lastItem=items.get(items.size()-1);
loadMore();
}
};
}
}
My answer is a modified version of Noor. I passed from a ListView where i had EndlessScrollListener (that you can find easily in many answers on SO) to a RecyclerView so i wanted a EndlessRecyclScrollListener to easily update my past listener.
So here is the code, hope it helps:
public abstract class EndlessScrollRecyclListener extends RecyclerView.OnScrollListener
{
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
private boolean loading = true;
private int visibleThreshold = 5;
int firstVisibleItem, visibleItemCount, totalItemCount;
private int startingPageIndex = 0;
private int currentPage = 0;
#Override
public void onScrolled(RecyclerView mRecyclerView, int dx, int dy)
{
super.onScrolled(mRecyclerView, dx, dy);
LinearLayoutManager mLayoutManager = (LinearLayoutManager) mRecyclerView
.getLayoutManager();
visibleItemCount = mRecyclerView.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition();
onScroll(firstVisibleItem, visibleItemCount, totalItemCount);
}
public void onScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount)
{
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0)
{
this.loading = true;
}
}
// If it’s still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount))
{
loading = false;
previousTotalItemCount = totalItemCount;
currentPage++;
}
// If it isn’t currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem +
visibleThreshold))
{
onLoadMore(currentPage + 1, totalItemCount);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page, int totalItemsCount);
}
For me, it's very simple:
private boolean mLoading = false;
mList.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int totalItem = mLinearLayoutManager.getItemCount();
int lastVisibleItem = mLinearLayoutManager.findLastVisibleItemPosition();
if (!mLoading && lastVisibleItem == totalItem - 1) {
mLoading = true;
// Scrolled to bottom. Do something here.
mLoading = false;
}
}
});
Be careful with asynchronous jobs: mLoading must be changed at the end of the asynchronous jobs. Hope it will be helpful!
With the power of Kotlin's extension functions, the code can look a lot more elegant. Put this anywhere you want (I have it inside an ExtensionFunctions.kt file):
/**
* WARNING: This assumes the layout manager is a LinearLayoutManager
*/
fun RecyclerView.addOnScrolledToEnd(onScrolledToEnd: () -> Unit){
this.addOnScrollListener(object: RecyclerView.OnScrollListener(){
private val VISIBLE_THRESHOLD = 5
private var loading = true
private var previousTotal = 0
override fun onScrollStateChanged(recyclerView: RecyclerView,
newState: Int) {
with(layoutManager as LinearLayoutManager){
val visibleItemCount = childCount
val totalItemCount = itemCount
val firstVisibleItem = findFirstVisibleItemPosition()
if (loading && totalItemCount > previousTotal){
loading = false
previousTotal = totalItemCount
}
if(!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)){
onScrolledToEnd()
loading = true
}
}
}
})
}
And then use it like this:
youRecyclerView.addOnScrolledToEnd {
//What you want to do once the end is reached
}
This solution is based on Kushal Sharma's answer. However, this is a bit better because:
It uses onScrollStateChanged instead of onScroll. This is better because onScroll is called every time there is any sort of movement in the RecyclerView, whereas onScrollStateChanged is only called when the state of the RecyclerView is changed. Using onScrollStateChanged will save you CPU time and, as a consequence, battery.
Since this uses Extension Functions, this can be used in any RecyclerView you have. The client code is just 1 line.
Most answer are assuming the RecyclerView uses a LinearLayoutManager, or GridLayoutManager, or even StaggeredGridLayoutManager, or assuming that the scrolling is vertical or horyzontal, but no one has posted a completly generic answer.
Using the ViewHolder's adapter is clearly not a good solution. An adapter might have more than 1 RecyclerView using it. It "adapts" their contents. It should be the RecyclerView (which is the one class which is responsible of what is currently displayed to the user, and not the adapter which is responsible only to provide content to the RecyclerView) which must notify your system that more items are needed (to load).
Here is my solution, using nothing else than the abstracted classes of the RecyclerView (RecycerView.LayoutManager and RecycerView.Adapter):
/**
* Listener to callback when the last item of the adpater is visible to the user.
* It should then be the time to load more items.
**/
public abstract class LastItemListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// init
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (layoutManager.getChildCount() > 0) {
// Calculations..
int indexOfLastItemViewVisible = layoutManager.getChildCount() -1;
View lastItemViewVisible = layoutManager.getChildAt(indexOfLastItemViewVisible);
int adapterPosition = layoutManager.getPosition(lastItemViewVisible);
boolean isLastItemVisible = (adapterPosition == adapter.getItemCount() -1);
// check
if (isLastItemVisible)
onLastItemVisible(); // callback
}
}
/**
* Here you should load more items because user is seeing the last item of the list.
* Advice: you should add a bollean value to the class
* so that the method {#link #onLastItemVisible()} will be triggered only once
* and not every time the user touch the screen ;)
**/
public abstract void onLastItemVisible();
}
// --- Exemple of use ---
myRecyclerView.setOnScrollListener(new LastItemListener() {
public void onLastItemVisible() {
// start to load more items here.
}
}
Although the accepted answer works perfectly, the solution below uses addOnScrollListener since setOnScrollListener is deprecated, and reduces number of variables, and if conditions.
final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
feedsRecyclerView.setLayoutManager(layoutManager);
feedsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
if ((layoutManager.getChildCount() + layoutManager.findFirstVisibleItemPosition()) >= layoutManager.getItemCount()) {
Log.d("TAG", "End of list");
//loadMore();
}
}
}
});
This is how I do it, simple and short:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if(!recyclerView.canScrollVertically(1) && dy != 0)
{
// Load more results here
}
}
});
Although there are so many answers to the question, I would like to share our experience of creating the endless list view. We have recently implemented custom Carousel LayoutManager that can work in the cycle by scrolling the list infinitely as well as up to a certain point. Here is a detailed description on GitHub.
I suggest you take a look at this article with short but valuable recommendations on creating custom LayoutManagers: http://cases.azoft.com/create-custom-layoutmanager-android/
OK, I did it by using the onBindViewHolder method of RecyclerView.Adapter.
Adapter:
public interface OnViewHolderListener {
void onRequestedLastItem();
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
...
if (position == getItemCount() - 1) onViewHolderListener.onRequestedLastItem();
}
Fragment (or Activity):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
contentView = inflater.inflate(R.layout.comments_list, container, false);
recyclerView = (RecyclerView) mContentView.findViewById(R.id.my_recycler_view);
adapter = new Adapter();
recyclerView.setAdapter(adapter);
...
adapter.setOnViewHolderListener(new Adapter.OnViewHolderListener() {
#Override
public void onRequestedLastItem() {
//TODO fetch new data from webservice
}
});
return contentView;
}
recyclerList.setOnScrollListener(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)
{
int totalItemCount = layoutManager.getItemCount();
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
if (totalItemCount> 1)
{
if (lastVisibleItem >= totalItemCount - 1)
{
// End has been reached
// do something
}
}
}
});
I would try to extend used LayoutManager (e.g. LinearLayoutManager) and override scrollVerticallyBy() method. Firstly, I would call super first and then check returned integer value. If the value equals to 0 then a bottom or a top border is reached. Then I would use findLastVisibleItemPosition() method to find out which border is reached and load more data if needed. Just an idea.
In addition, you can even return your value from that method allowing overscroll and showing "loading" indicator.
I achieved an infinite scrolling type implementation using this logic in the onBindViewHolder method of my RecyclerView.Adapter class.
if (position == mItems.size() - 1 && mCurrentPage <= mTotalPageCount) {
if (mCurrentPage == mTotalPageCount) {
mLoadImagesListener.noMorePages();
} else {
int newPage = mCurrentPage + 1;
mLoadImagesListener.loadPage(newPage);
}
}
With this code when the RecyclerView gets to the last item, it increments the current page and callbacks on an interface which is responsible for loading more data from the api and adding the new results to the adapter.
I can post more complete example if this isn't clear?
For people who use StaggeredGridLayoutManager here is my implementation, it works for me.
private class ScrollListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
firstVivisibleItems = mLayoutManager.findFirstVisibleItemPositions(firstVivisibleItems);
if(!recyclerView.canScrollVertically(1) && firstVivisibleItems[0]!=0) {
loadMoreImages();
}
}
private boolean loadMoreImages(){
Log.d("myTag", "LAST-------HERE------");
return true;
}
}
There is an easy to use library for this named paginate . Supports both ListView and RecyclerView ( LinearLayout , GridLayout and StaggeredGridLayout).
Here is the link to the project on Github
My way to detect loading event is not to detect scrolling, but to listen whether the last view was attached. If the last view was attached, I regard it as timing to load more content.
class MyListener implements RecyclerView.OnChildAttachStateChangeListener {
RecyclerView mRecyclerView;
MyListener(RecyclerView view) {
mRecyclerView = view;
}
#Override
public void onChildViewAttachedToWindow(View view) {
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
RecyclerView.LayoutManager mgr = mRecyclerView.getLayoutManager();
int adapterPosition = mgr.getPosition(view);
if (adapterPosition == adapter.getItemCount() - 1) {
// last view was attached
loadMoreContent();
}
#Override
public void onChildViewDetachedFromWindow(View view) {}
}
Create an abstract class and extends RecyclerView.OnScrollListener
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
private int previousTotal = 0;
private boolean loading = true;
private int visibleThreshold;
private int firstVisibleItem, visibleItemCount, totalItemCount;
private RecyclerView.LayoutManager layoutManager;
public EndlessRecyclerOnScrollListener(RecyclerView.LayoutManager layoutManager, int visibleThreshold) {
this.layoutManager = layoutManager; this.visibleThreshold = visibleThreshold;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
firstVisibleItem = ((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
onLoadMore();
loading = true;
}
}
public abstract void onLoadMore();}
in activity (or fragment) add addOnScrollListener to recyclerView
LinearLayoutManager mLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener(mLayoutManager, 3) {
#Override
public void onLoadMore() {
//TODO
...
}
});
I have a pretty detailed example of how to paginate with a RecyclerView. At a high level, I have a set PAGE_SIZE , lets say 30. So I request 30 items and if I get 30 back then I request the next page. If I get less than 30 items I flag a variable to indicate that the last page has been reached and then I stop requesting for more pages. Check it out and let me know what you think.
https://medium.com/#etiennelawlor/pagination-with-recyclerview-1cb7e66a502b
Here my solution using AsyncListUtil, in the web says:
Note that this class uses a single thread to load the data, so it suitable to load data from secondary storage such as disk, but not from network.
but i am using odata to read the data and work fine.
I miss in my example data entities and network methods.
I include only the example adapter.
public class AsyncPlatoAdapter extends RecyclerView.Adapter {
private final AsyncPlatoListUtil mAsyncListUtil;
private final MainActivity mActivity;
private final RecyclerView mRecyclerView;
private final String mFilter;
private final String mOrderby;
private final String mExpand;
public AsyncPlatoAdapter(String filter, String orderby, String expand, RecyclerView recyclerView, MainActivity activity) {
mFilter = filter;
mOrderby = orderby;
mExpand = expand;
mRecyclerView = recyclerView;
mActivity = activity;
mAsyncListUtil = new AsyncPlatoListUtil();
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).
inflate(R.layout.plato_cardview, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
return new PlatoViewHolderAsync(itemView, this);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final Plato item = mAsyncListUtil.getItem(position);
PlatoViewHolderAsync vh = (PlatoViewHolderAsync) holder;
if (item != null) {
Integer imagen_id = item.Imagen_Id.get();
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
vh.getImage().setVisibility(View.VISIBLE);
vh.getProgress().setVisibility(View.GONE);
String cacheName = null;
String urlString = null;
if (imagen_id != null) {
cacheName = String.format("imagenes/imagen/%d", imagen_id);
urlString = String.format("%s/menusapi/%s", MainActivity.ROOTPATH, cacheName);
}
ImageHelper.downloadBitmap(mActivity, vh.getImage(), vh.getProgress(), urlString, cacheName, position);
} else {
vh.getBinding().setVariable(BR.plato, item);
vh.getBinding().executePendingBindings();
//show progress while loading.
vh.getImage().setVisibility(View.GONE);
vh.getProgress().setVisibility(View.VISIBLE);
}
}
#Override
public int getItemCount() {
return mAsyncListUtil.getItemCount();
}
public class AsyncPlatoListUtil extends AsyncListUtil<Plato> {
/**
* Creates an AsyncListUtil.
*/
public AsyncPlatoListUtil() {
super(Plato.class, //my data class
10, //page size
new DataCallback<Plato>() {
#Override
public int refreshData() {
//get count calling ../$count ... odata endpoint
return countPlatos(mFilter, mOrderby, mExpand, mActivity);
}
#Override
public void fillData(Plato[] data, int startPosition, int itemCount) {
//get items from odata endpoint using $skip and $top
Platos p = loadPlatos(mFilter, mOrderby, mExpand, startPosition, itemCount, mActivity);
for (int i = 0; i < Math.min(itemCount, p.value.size()); i++) {
data[i] = p.value.get(i);
}
}
}, new ViewCallback() {
#Override
public void getItemRangeInto(int[] outRange) {
//i use LinearLayoutManager in the RecyclerView
LinearLayoutManager layoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
outRange[0] = layoutManager.findFirstVisibleItemPosition();
outRange[1] = layoutManager.findLastVisibleItemPosition();
}
#Override
public void onDataRefresh() {
mRecyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public void onItemLoaded(int position) {
mRecyclerView.getAdapter().notifyItemChanged(position);
}
});
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
onRangeChanged();
}
});
}
}
}
if (layoutManager.findLastCompletelyVisibleItemPosition() ==
recyclerAdapter.getItemCount() - 1) {
//load more items.
}
Fair and simple.
This will work.
As #John T suggest. Just use code block below, really short, beauty and simple :D
public void loadMoreOnRecyclerView() {
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(#NonNull #NotNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(1) && dy != 0) {
//Load more items here
}
}
});
}
You can follow my Repo to understand the way that it work.
https://github.com/Nghien-Nghien/PokeAPI-Java/blob/0d8d69d348e068911b883f0ae7791d904cc75cb5/app/src/main/java/com/example/pokemonapi/MainActivity.java
Description info about app like this: https://github.com/skydoves/Pokedex#readme
There is a method public void setOnScrollListener (RecyclerView.OnScrollListener listener) in https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#setOnScrollListener%28android.support.v7.widget.RecyclerView.OnScrollListener%29. Use that
EDIT:
Override onScrollStateChanged method inside the onScrollListener and do this
boolean loadMore = firstVisibleItem + visibleItemCount >= totalItemCount;
//loading is used to see if its already loading, you have to manually manipulate this boolean variable
if (loadMore && !loading) {
//end of list reached
}
Check this every thing is explained in detail:
Pagination using RecyclerView From A to Z
mRecyclerView.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) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mLayoutManager.getChildCount();
int totalItemCount = mLayoutManager.getItemCount();
int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
if (!mIsLoading && !mIsLastPage) {
if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount
&& firstVisibleItemPosition >= 0) {
loadMoreItems();
}
}
}
})
loadMoreItems():
private void loadMoreItems() {
mAdapter.removeLoading();
//load data here from the server
// in case of success
mAdapter.addData(data);
// if there might be more data
mAdapter.addLoading();
}
in MyAdapter :
private boolean mIsLoadingFooterAdded = false;
public void addLoading() {
if (!mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = true;
mLineItemList.add(new LineItem());
notifyItemInserted(mLineItemList.size() - 1);
}
}
public void removeLoading() {
if (mIsLoadingFooterAdded) {
mIsLoadingFooterAdded = false;
int position = mLineItemList.size() - 1;
LineItem item = mLineItemList.get(position);
if (item != null) {
mLineItemList.remove(position);
notifyItemRemoved(position);
}
}
}
public void addData(List<YourDataClass> data) {
for (int i = 0; i < data.size(); i++) {
YourDataClass yourDataObject = data.get(i);
mLineItemList.add(new LineItem(yourDataObject));
notifyItemInserted(mLineItemList.size() - 1);
}
}
None of these answers take into account if the list is too small or not.
Here's a piece of code I've been using that works on RecycleViews in both directions.
#Override
public boolean onTouchEvent(MotionEvent motionEvent) {
if (recyclerViewListener == null) {
return super.onTouchEvent(motionEvent);
}
/**
* If the list is too small to scroll.
*/
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
return super.onTouchEvent(motionEvent);
}
public void setListener(RecyclerViewListener recycleViewListener) {
this.recyclerViewListener = recycleViewListener;
addOnScrollListener(new OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (recyclerViewListener == null) {
return;
}
recyclerViewListener.scrolling(dy);
if (!canScrollVertically(1)) {
recyclerViewListener.reachedBottom();
} else if (!canScrollVertically(-1)) {
recyclerViewListener.reachedTop();
}
}
});
}
I let you my aproximation. Works fine for me.
I hope it helps you.
/**
* Created by Daniel Pardo Ligorred on 03/03/2016.
*/
public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {
protected RecyclerView.LayoutManager layoutManager;
public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {
this.layoutManager = layoutManager;
this.init();
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);
}
private int getFirstVisibleItem(){
if(this.layoutManager instanceof LinearLayoutManager){
return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();
} else if (this.layoutManager instanceof StaggeredGridLayoutManager){
int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.
try{
return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];
}catch (Exception ex){
// Do stuff...
}
}
return 0;
}
public abstract void init();
protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);
}
#kushal #abdulaziz
Why not use this logic instead?
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
int totalItemCount, lastVisibleItemPosition;
if (dy > 0) {
totalItemCount = _layoutManager.getItemCount();
lastVisibleItemPosition = _layoutManager.findLastVisibleItemPosition();
if (!_isLastItem) {
if ((totalItemCount - 1) == lastVisibleItemPosition) {
LogUtil.e("end_of_list");
_isLastItem = true;
}
}
}
}
Try below:
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
/**
* Abstract Endless ScrollListener
*
*/
public abstract class EndlessScrollListener extends
RecyclerView.OnScrollListener {
// The minimum amount of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 10;
// The current offset index of data you have loaded
private int currentPage = 1;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// The total number of items in the data set after the last load
private int previousTotal = 0;
private int firstVisibleItem;
private int visibleItemCount;
private int totalItemCount;
private LayoutManager layoutManager;
public EndlessScrollListener(LayoutManager layoutManager) {
validateLayoutManager(layoutManager);
this.layoutManager = layoutManager;
}
public EndlessScrollListener(int visibleThreshold,
LayoutManager layoutManager, int startPage) {
validateLayoutManager(layoutManager);
this.visibleThreshold = visibleThreshold;
this.layoutManager = layoutManager;
this.currentPage = startPage;
}
private void validateLayoutManager(LayoutManager layoutManager)
throws IllegalArgumentException {
if (null == layoutManager
|| !(layoutManager instanceof GridLayoutManager)
|| !(layoutManager instanceof LinearLayoutManager)) {
throw new IllegalArgumentException(
"LayoutManager must be of type GridLayoutManager or LinearLayoutManager.");
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = layoutManager.getItemCount();
if (layoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
} else if (layoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) layoutManager)
.findFirstVisibleItemPosition();
}
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading
&& (totalItemCount - visibleItemCount) <= (firstVisibleItem + visibleThreshold)) {
// End has been reached do something
currentPage++;
onLoadMore(currentPage);
loading = true;
}
}
// Defines the process for actually loading more data based on page
public abstract void onLoadMore(int page);
}
I have created LoadMoreRecyclerView using Abdulaziz Noor Answer
LoadMoreRecyclerView
public class LoadMoreRecyclerView extends RecyclerView {
private boolean loading = true;
int pastVisiblesItems, visibleItemCount, totalItemCount;
//WrapperLinearLayout is for handling crash in RecyclerView
private WrapperLinearLayout mLayoutManager;
private Context mContext;
private OnLoadMoreListener onLoadMoreListener;
public LoadMoreRecyclerView(Context context) {
super(context);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public LoadMoreRecyclerView(Context context, #Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
private void init(){
mLayoutManager = new WrapperLinearLayout(mContext,LinearLayoutManager.VERTICAL,false);
this.setLayoutManager(mLayoutManager);
this.setItemAnimator(new DefaultItemAnimator());
this.setHasFixedSize(true);
}
#Override
public void onScrolled(int dx, int dy) {
super.onScrolled(dx, dy);
if(dy > 0) //check for scroll down
{
visibleItemCount = mLayoutManager.getChildCount();
totalItemCount = mLayoutManager.getItemCount();
pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition();
if (loading)
{
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount)
{
loading = false;
Log.v("...", "Call Load More !");
if(onLoadMoreListener != null){
onLoadMoreListener.onLoadMore();
}
//Do pagination.. i.e. fetch new data
}
}
}
}
#Override
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
}
public void onLoadMoreCompleted(){
loading = true;
}
public void setMoreLoading(boolean moreLoading){
loading = moreLoading;
}
public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
this.onLoadMoreListener = onLoadMoreListener;
}
}
WrapperLinearLayout
public class WrapperLinearLayout extends LinearLayoutManager
{
public WrapperLinearLayout(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
#Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
Log.e("probe", "meet a IOOBE in RecyclerView");
}
}
}
//Add it in xml like
<your.package.LoadMoreRecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</your.package.LoadMoreRecyclerView>
OnCreate or onViewCreated
mLoadMoreRecyclerView = (LoadMoreRecyclerView) view.findViewById(R.id.recycler_view);
mLoadMoreRecyclerView.setOnLoadMoreListener(new OnLoadMoreListener() {
#Override
public void onLoadMore() {
callYourService(StartIndex);
}
});
callYourService
private void callYourService(){
//callyour Service and get response in any List
List<AnyModelClass> newDataFromServer = getDataFromServerService();
//Enable Load More
mLoadMoreRecyclerView.onLoadMoreCompleted();
if(newDataFromServer != null && newDataFromServer.size() > 0){
StartIndex += newDataFromServer.size();
if (newDataFromServer.size() < Integer.valueOf(MAX_ROWS)) {
//StopLoading..
mLoadMoreRecyclerView.setMoreLoading(false);
}
}
else{
mLoadMoreRecyclerView.setMoreLoading(false);
mAdapter.notifyDataSetChanged();
}
}

How to sync scrolling of all horizontal nested RecyclerViews within a vertical RecyclerViews?

Background
Suppose I have a vertical RecyclerView, where each row is a horizontal RecyclerView.
What I'd like to do is that no matter which horizontal RecyclerViews you scroll, all of the others will scroll accordingly, and always be synced with the exact same scroll X coordinate
The problem
I actually did ok for the basic operation :
It works by having a scrolling listener that all horizontal RecyclerViews have, yet when one starts to scroll, it is the only one that will have it, while it also affects the others to scroll with it.
However, I have 2 main issues with what I did:
In some (horizontal) scrolling operations (maybe some gestures, like fling), the scrolling of the multiple RecyclerViews is out of sync, so some are in X coordinate that is different from the others.
When scrolling vertically, I couldn't succeed setting the X coordinate correctly. Not only that, but onBindViewHolder of the vertical RecyclerView doesn't get called when I expected it to be called (called when I scroll a lot, and not just when I see a used one being re-shown).
What I've tried
Here's the current code:
MainActivity.java
public class MainActivity extends AppCompatActivity {
int mCurX = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RecyclerView mainRecyclerView = (RecyclerView) findViewById(R.id.activity_main);
final LinearLayoutManager verticalLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mainRecyclerView.setLayoutManager(verticalLinearLayoutManager);
final LayoutInflater layoutInflater = LayoutInflater.from(this);
final OnScrollListener masterOnScrollListener = new OnScrollListener() {
RecyclerView masterRecyclerView = null;
#Override
public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
super.onScrollStateChanged(recyclerView, newState);
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
if (masterRecyclerView != null) {
masterRecyclerView = null;
final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
if (horizontalRecyclerView != recyclerView)
horizontalRecyclerView.addOnScrollListener(this);
}
}
break;
case RecyclerView.SCROLL_STATE_SETTLING:
//TODO fix out-of-sync scrolling issues, probably here
case RecyclerView.SCROLL_STATE_DRAGGING:
if (masterRecyclerView == null) {
masterRecyclerView = recyclerView;
final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
if (horizontalRecyclerView != recyclerView)
horizontalRecyclerView.removeOnScrollListener(this);
}
}
}
}
#Override
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
mCurX += dx;
final int firstVisibleItemPosition = verticalLinearLayoutManager.findFirstVisibleItemPosition();
final int lastVisibleItemPosition = verticalLinearLayoutManager.findLastVisibleItemPosition();
for (int i = firstVisibleItemPosition; i <= lastVisibleItemPosition; ++i) {
RecyclerView horizontalRecyclerView = (RecyclerView) mainRecyclerView.findViewHolderForAdapterPosition(i).itemView;
if (horizontalRecyclerView != recyclerView)
horizontalRecyclerView.scrollBy(dx, dy);
}
}
};
mainRecyclerView.setAdapter(new Adapter() {
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
RecyclerView horizontalRecyclerView = (RecyclerView) layoutInflater.inflate(R.layout.horizontal_recycler_view, parent, false);
horizontalRecyclerView.setLayoutManager(new LinearLayoutManager(MainActivity.this, LinearLayoutManager.HORIZONTAL, false));
horizontalRecyclerView.addOnScrollListener(masterOnScrollListener);
final ViewHolder horizontalViewHolder = new ViewHolder(horizontalRecyclerView) {
};
horizontalRecyclerView.setAdapter(new Adapter() {
#Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
return new ViewHolder(layoutInflater.inflate(R.layout.single_item, parent, false)) {
};
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
((TextView) holder.itemView).setText("horizontalRecyclerView:" + horizontalViewHolder.getAdapterPosition() + "\nitem:" + position);
}
#Override
public int getItemCount() {
return 100;
}
});
return horizontalViewHolder;
}
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
//TODO check why this isn't called for some cases
RecyclerView recyclerView = (RecyclerView) holder.itemView;
recyclerView.removeOnScrollListener(masterOnScrollListener);
//TODO scroll to correct location here. The below code doesn't seem to work at all
recyclerView.scrollToPosition(0);
recyclerView.scrollBy(mCurX,0);
recyclerView.addOnScrollListener(masterOnScrollListener);
recyclerView.getAdapter().notifyDataSetChanged();
}
#Override
public int getItemCount() {
return 40;
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
android:id="#+id/activity_main"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="lb.com.nestedallscrollingrecyclerviewtest.MainActivity"/>
horizontal_recycler_view.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp"/>
single_item.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="10dp"/>
The questions
What is wrong in the code that causes it to be out-of-scrolling sync?
Is it possible I also don't get a hold of all the RecyclerViews that I should?
How come the onBindViewHolder of the vertical RecyclerView doesn't get called when I expect it to?
How do I set the x-coordinate scrolling of a horizontal RecyclerView to be as the others, in onBindViewHolder of the vertical one?
I'm not sure if this could be a problem, but what should I do in case each item in each horizontal RecyclerView could be with a different width than the others ?
A bit late to the party but just putting it here in case anyone else stumbles upon the same issue. Please Note that this solution is written in Kotlin and you might have to convert it to Java if that is your language of choice.
Solution
There are a couple of issues that need to be taken into account.
Synchronise scrolling of horizontal recycler views
Retain offset when scrolling vertical recycler view
Add this code in the Adapter for your vertical recycler view
var horizontalRecyclerViews = mutableListOf<RecyclerView>()
var absoluteOffset: Int? = null //Used to solve issue number 2
// matchOffset is used to synchronise the offset of each horizontal recyclerview.
// It is called when a horizontal recyclerview is scrolled with that recyclerview's
// offset. It is also called when the vertical recycler view is scrolled but without
// an offset value (in which case, it uses the absoluteOffset which is set when
// the horizontal scrolling is stopped)
fun matchOffset(offset: Int? = absoluteOffset) {
offset?.let { offsetValue ->
horizontalRecyclerViews.forEach { recyclerView ->
val currentOffset = recyclerView.computeHorizontalScrollOffset()
if (offsetValue != currentOffset) {
recyclerView.scrollBy(offsetValue-currentOffset, 0)
}
}
}
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
...
...
...
val onTouchListener = object: RecyclerView.OnItemTouchListener {
override fun onTouchEvent(p0: RecyclerView, p1: MotionEvent) {
}
override fun onInterceptTouchEvent(p0: RecyclerView, p1: MotionEvent): Boolean {
if (p1.action == MotionEvent.ACTION_UP) {
// This value is used by the vertical recycler view
absoluteOffset = p0.computeHorizontalScrollOffset()
// Disable the fling scroll to make life easier
return true
}
return false
}
override fun onRequestDisallowInterceptTouchEvent(p0: Boolean) {
}
}
val onScrollListener = object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val value = recyclerView.computeHorizontalScrollOffset()
matchOffset(value)
}
}
...
...
...
//Clear scroll listeners on each bind to stop them from accumulating
horizontalRecyclerView.clearOnScrollListeners()
//Add touch and scroll listeners to horizontalRecyclerView
horizontalRecyclerView.addOnItemTouchListener(onTouchListener)
horizontalRecyclerView.addOnScrollListener(onScrollListener)
//Add each horizontal recyclerView into the mutableList
horizontalRecyclerViews.add(horizontalRecyclerView)
...
...
...
}
To wrap it up for the resolution of issue number 2, add the following scroll listener to your vertical recycler view
val onScrollListener = object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
//Cast the Adapter to access the matchOffset method
(recyclerView.adapter as? Adapter)?.matchOffset()
}
}
verticalRecyclerView.addOnScrollListener(onScrollListener)

Infinite scrolling using custom animations in Recycler View not working Android

I have use Wasabeef for Item animation in Recycler view and it works fine. I also want to add infinite scrolling for recycler, for this I am doing like this
public abstract class InfiniteScroller extends RecyclerView.OnScrollListener {
private int previousTotal = 0; // The total number of items in the dataset after the last load
private boolean loading = true; // True if we are still waiting for the last set of data to load.
private int visibleThreshold = 5; // The minimum amount of items to have below your current scroll position before loading more.
int firstVisibleItem, visibleItemCount, totalItemCount;
private int current_page = 1;
private LinearLayoutManager mLinearLayoutManager;
public InfiniteScroller(LinearLayoutManager linearLayoutManager) {
this.mLinearLayoutManager = linearLayoutManager;
}
public abstract void onLoadMore(int current_page);
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
// Do Nothing
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
visibleItemCount = recyclerView.getChildCount();
totalItemCount = mLinearLayoutManager.getItemCount();
firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
if (loading) {
if (totalItemCount > previousTotal) {
loading = false;
previousTotal = totalItemCount;
}
}
if (!loading && (totalItemCount - visibleItemCount)
<= (firstVisibleItem + visibleThreshold)) {
// End has been reached
// Do something
current_page++;
onLoadMore(current_page);
loading = true;
}
}
}
And this also works fine. Real challenge is when I combine item animation with infinte scroll doesn't work even though OnScroll listener is called and also loadMore() is called and it is adding item in the data source but not displaying it in recycler view.
Heres is the code.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
mRecylerView = (RecyclerView)view.findViewById(R.id.my_recycler_view);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecylerView.setLayoutManager(mLayoutManager);
initialize();
mAdapter = new MyAdapter(persons);
AlphaInAnimationAdapter alphaAdapter = new AlphaInAnimationAdapter(mAdapter);
ScaleInAnimationAdapter scaleAdapter = new ScaleInAnimationAdapter(alphaAdapter);
scaleAdapter.setDuration(1000);
scaleAdapter.setInterpolator(new OvershootInterpolator());
mRecylerView.setAdapter(scaleAdapter);
mRecylerView.setOnScrollListener(new InfiniteScroller(mLayoutManager) {
#Override
public void onLoadMore(int current_page) {
Log.i("Infinite Scroller", "Current page = " + current_page);
insertNewItem();
}
});
return view;
}
If I comment out animation lines it will scroll infinitely but not otherwise.
Please see the code and let me know what is going wrong.
So i found the problem myself and fixed it :)
For those who are having the same problem i will share my experience.
I was calling mAdpter.notifyItemInserted(position) but it should be scaleAdapter.notifyItemInserted(position).

How to hide and show the Android ActionBar like in Google Inbox app?

I wnat to show and hide the Android ActionBar when the user scrolls the screen. I found some examples, like this question in SO. But the code showed in this question and its answer makes the action hides after the list have scrolled some pixels, and I want to make this scroll like the Inbox App by Google, that is the action bar is pulled by the Layout according to the user scroll the screen up or down, in other words, I want to show/hide the action bar at the same time the user scroll down/up.
Someone face a problema like this? Some idea?
super simple:
ditch the ListView. ListView is the past. Use RecyclerView instead.
add a RecyclerView.OnScrollListener to it, to get pixel-by-pixel scroll.
use a Toolbar on your activity layout. So you can control the position of it.
call setTranslationY(val) on your Toolbar to "scroll" it with the RecyclerView.
a few links with the docs for the mentioned classes:
http://android-developers.blogspot.de/2014/10/appcompat-v21-material-design-for-pre.html
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.OnScrollListener.html
http://developer.android.com/reference/android/widget/Toolbar.html
https://developer.android.com/reference/android/view/View.html#setTranslationY(float)
I created a solution based answer of #Budius and this link
First add this class
public class HidingRecyclerViewScrollListener extends RecyclerView.OnScrollListener {
private final int UP = 0;
private final int DOWN = 1;
private final int margin = 5;
private View mView;
RecyclerView.LayoutManager mLayoutManager;
private float currentPoint = 0;
private int lastDirection = 1;
public HidingRecyclerViewScrollListener(View view, LinearLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
mView = view;
}
public HidingRecyclerViewScrollListener(View view, GridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
mView = view;
}
public HidingRecyclerViewScrollListener(View view, StaggeredGridLayoutManager layoutManager) {
this.mLayoutManager = layoutManager;
mView = view;
}
public int getFirstVisibleItem(int[] firstVisibleItemPositions) {
int minSize = 0;
for (int i = 0; i < firstVisibleItemPositions.length; i++) {
if (i == 0) {
minSize = firstVisibleItemPositions[i];
} else if (firstVisibleItemPositions[i] < minSize) {
minSize = firstVisibleItemPositions[i];
}
}
return minSize;
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
float nextPoint = currentPoint + dy;
if (nextPoint < 0) {
nextPoint = 0;
}
if (nextPoint > viewSize()) {
nextPoint = viewSize();
}
lastDirection = nextPoint >= currentPoint ? UP : DOWN;
currentPoint = nextPoint;
mView.setTranslationY(-currentPoint);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (lastDirection == UP) {
if (getFirstVisibleItem() > 0) {
currentPoint = viewSize();
}
}
if (lastDirection == DOWN) {
//Volta para origem
currentPoint = 0;
}
mView.animate().translationY(-currentPoint);
}
}
private int getFirstVisibleItem() {
int firstVisibleItem = 0;
if (mLayoutManager instanceof StaggeredGridLayoutManager) {
int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) mLayoutManager).findFirstVisibleItemPositions(null);
firstVisibleItem = getFirstVisibleItem(lastVisibleItemPositions);
} else if (mLayoutManager instanceof LinearLayoutManager) {
firstVisibleItem = ((LinearLayoutManager) mLayoutManager).findFirstVisibleItemPosition();
} else if (mLayoutManager instanceof GridLayoutManager) {
firstVisibleItem = ((GridLayoutManager) mLayoutManager).findFirstVisibleItemPosition();
}
return firstVisibleItem;
}
private int viewSize() {
return mView.getHeight() + margin;
}
}
Is simple to use, only add using addOnScrollListener of your recyclerView
recyclerView = (RecyclerView) view.findViewById(R.id.mRecyclerView );
layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.addOnScrollListener(new HidingRecyclerViewScrollListener(mView, layoutManager));
mView is the view you want to control
You can user this class with LinearLayoutManager, GridLayoutManager and StaggeredGridLayoutManager (but i never test with GridLayoutManager)
In RecyclerView use the code below to the list does not stay below the object you want to control, this code is simpler and better to test, but use a Hearder in RecyclerView as is said in the link above has a better result.
android:clipToPadding="false"
android:paddingTop="#dimen/outher_view_dimem"
If you using SwipeRefreshLayout use code below to show ProgressBar
swipeRefreshLayout.setProgressViewOffset(true, 0, mViewHeight + actionBarHeight);

Categories

Resources