How can i move to next object in recyclerview on button click - android

I want to move to next object in recyclerview when button is clicked. I tried using scrollToPosition but i can't figure out how can i get current position of the object. If i put a number in there then it works, but then it only jumps to that position only.
Code i am using to scroll
layoutManager.scrollToPosition(2);

LinearLayoutManager - methods like findLastCompletelyVisibleItemPosition() and checking the size of your AdapterItems and scrolling to the next (+1 if findLast < adapter.itemsSize). Very simple logic.
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
Logic for button clicking:
btnClick.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (layoutManager.findLastCompletelyVisibleItemPosition() < (adapter.getItemCount() - 1)) {
layoutManager.scrollToPosition(layoutManager.findLastCompletelyVisibleItemPosition() + 1);
}
}
});
And in your extended Adapter for RecyclerView, you should have something like this:
#Override
public int getItemCount() {
return yourData != null ? yourData.length() : 0;
}

The ViewHolder has a getAdapterPosition function returning its position

Related

Automatically scroll to bottom of RecyclerView

I have a fragment that has a RecyclerView inside its layout file. The RecyclerView holds messages in a chat. So naturally, I need the RecyclerView to scroll to the bottom when the chat fragment gets opened.
I tried scrolling directly on the RecyclerView:
var mRecyclerView = view.FindViewById<RecyclerView>
mRecyclerView.ScrollToPosition(mMessages.Count-1);
Second method:
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(Application.Context);
mRecyclerView.SetLayoutManager(mLinearLayoutManager);
mLinearLayoutManager.ScrollToPosition(mMessages.Count - 1);
Third method:
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(Application.Context);
mRecyclerView.SetLayoutManager(mLinearLayoutManager);
mLinearLayoutManager.ScrollToPositionWithOffset(mMessages.Count - 1, 0);
Unfortunately, nothing happens in either case. Any suggestions would be greatly appreciated!
Please use smoothScrollToPosition to fix your issue.
I always use smoothScrollToPosition for redirecting to any position.
Make sure, mMessages size is good as you thinking.
Example,
RecyclerView rv = (RecyclerView)findViewById(R.id.recyclerView);
rv.smoothScrollToPosition(mMessages.count-1);
You can set setStackFromEnd=true that will set the view to show the last element, the layout direction will remain the same.
After edit I have updated: just like this:
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(Application.Context);
mLinearLayoutManager.setStackFromEnd(true);
mRecyclerView.SetLayoutManager(mLinearLayoutManager);
mRecyclerView.scrollToPosition(mMessages.Count-1);
please check the documentation.
Edit: the problem is you are calling scrollToPosition without setting any layout manager to the recyclerview.
Considering to the scrollToPosition function in RecyclerView class, your case makes sense.
/**
* Convenience method to scroll to a certain position.
*
* RecyclerView does not implement scrolling logic, rather forwards the call to
* {#link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
* #param position Scroll to this adapter position
* #see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
*/
public void scrollToPosition(int position) {
if (mLayoutFrozen) {
return;
}
stopScroll();
if (mLayout == null) {
Log.e(TAG, "Cannot scroll to position a LayoutManager set. " +
"Call setLayoutManager with a non-null argument.");
return;
}
mLayout.scrollToPosition(position);
awakenScrollBars();
}
Cannot scroll to position a LayoutManager set. Call setLayoutManager with a non-null argument.
It's Simple
change your code with
RecyclerView recyclerView = findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setStackFromEnd(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(mAdapter);
recyclerView.smoothScrollToPosition(mAdapter.getItemCount());
If You have given ScrollView or NestedScrollView in the parent of RecyclerView then it will not work. Because it only works when RecyclerView doesn't have any parent layout like ScrollView. hope it will fix your error.
I believe you are not calling it in right way. You have to pass LinearLayoutManager as well along with recycler view to make it work. Here how I have made it work
mLayoutManager.smoothScrollToPosition(mRecyclerView, null, array.size() - 1);
I found the issue, set RecyclerView scrollToPosition or smoothScrollToPosition after setting the LayoutManager.
RecyclerView recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.scrollToPosition(mMessages.count - 1);
Hopes it help you.
If you want to directly scroll in the last item you can just use this code.
binding.recyclerView.smoothScrollToPosition(adapter.itemCount-1)
If you want to add continuous scrolling for the recyclerview, you can use this class.
public class AutoScrollRecyclerView extends RecyclerView {
private static long delayTime = 45;// How long after the interval to perform scrolling
AutoPollTask autoPollTask;// Scroll thread
private boolean running; // Is it rolling?
private boolean canRun;// Can it be automatically scrolled, depending on whether the data exceeds the screen
public AutoScrollRecyclerView(Context context) {
super(context);
}
public AutoScrollRecyclerView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
autoPollTask = new AutoPollTask(this);// Instantiate the scroll refresh thread
}
static class AutoPollTask implements Runnable {
private final WeakReference<AutoScrollRecyclerView> mReference;
// Use weak references to hold external class references to prevent memory leaks
public AutoPollTask(AutoScrollRecyclerView reference) {
this.mReference = new WeakReference<>(reference);
}
#Override
public void run() {
AutoScrollRecyclerView recyclerView = mReference.get();// Get the recyclerview object
if (recyclerView != null && recyclerView.running && recyclerView.canRun) {
recyclerView.scrollBy(2, 2);// Note the difference between scrollBy and scrollTo
//delayed to send
recyclerView.postDelayed(recyclerView.autoPollTask, delayTime);
}
}
}
#Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
/*if (running)
stop();*/
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
/*if (canRun)
start();*/
break;
}
return super.onTouchEvent(e);
}
//Open: If it is running, stop first -> then open
public void start() {
if (running)
stop();
canRun = true;
running = true;
postDelayed(autoPollTask, delayTime);
}
public void stop() {
running = false;
removeCallbacks(autoPollTask);
}
}
If You have given ScrollView or NestedScrollView in the parent of recyclerView then try this:
parentNestedScrollView.smoothScrollTo(0, parentNestedScrollView.bottom)
Sometimes this may not work, as an attempt to update the scroll position will be made before the adapter displays the new item, so we need to use delay:
popupView.postDelayed({
parentNestedScrollView.smoothScrollTo(0, parentNestedScrollView.bottom)
}, 200)

RecyclerView scrolls to top on notifyDataSetChanged in chat screen

I am trying to create messaging kind of screen using recyclerView which will start from bottom and will loadMore data when user reached top end of chat. But I am facing this weird issue.
My recyclerView scrolls to top on calling notifyDataSetChanged. Due to this onLoadMore gets called multiple times.
Here is my code:
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.VERTICAL);
llm.setStackFromEnd(true);
recyclerView.setLayoutManager(llm);
** In Adapter
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks) {
mLoadMoreCallbacks.onLoadMore();
}
** In Activity
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
chatMessagesAdapter.notifyDataSetChanged();
}
It's just that recyclerView scrolls to top. If scrolling to top stops, this issue will be resolved. Please help me to figure out the cause of this issue. Thanks in advance.
I think you shouldn't use onBindViewHolder that way, remove that code, the adapter should only bind model data, not listen scrolling.
I usually do the "onLoadMore" this way:
In the Activity:
private boolean isLoading, totallyLoaded; //
RecyclerView mMessages;
LinearLayoutManager manager;
ArrayList<Message> messagesArray;
MessagesAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
mMessages.setHasFixedSize(true);
manager = new LinearLayoutManager(this);
manager.setStackFromEnd(true);
mMessages.setLayoutManager(manager);
mMessages.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (manager.findFirstVisibleItemPosition() == 0 && !isLoading && !totallyLoaded) {
onLoadMore();
isLoading = true;
}
}
});
messagesArray = new ArrayList<>();
adapter = new MessagesAdapter(messagesArray, this);
mMessages.setAdapter(adapter);
}
#Override
public void onLoadMore() {
//get more messages...
messagesArray.addAll(0, moreMessagesArray);
adapter.notifyItemRangeInserted(0, (int) moreMessagesArray.size();
isLoading = false;
}
This works perfeclty for me, and the "totallyLoaded" is used if the server doesn't return more messages, to stop making server calls. Hope it helps you.
You see, it's natural for List to scroll to the most top item when you insert new Items. Well you going in the right direction but I think you forgot adding setReverseLayout(true).
Here the setStackFromEnd(true) just tells List to stack items starting from bottom of the view but when used in combination with the setReverseLayout(true) it will reverse order of items and views so the newest item is always shown at the bottom of the view.
Your final layoutManager would seems something like this:
mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(mLayoutManager);
DON'T call notifyDataSetChanged() on the RecyclerView. Use the new methods like notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted(), etc...
And if u use notifyItemRangeInserted()--
don't call setAdapter() method after that..!
This is my way to avoid scrollview move to top
Instead of using notifyDataSetChanged(), I use notifyItemRangeChanged();
List<Object> tempList = new ArrayList<>();
tempList.addAll(mList);
mList.clear();
mList.addAll(tempList);
notifyItemRangeChanged(0, mList.size());
Update:
For another reason, Your another view in the top is focusing so it will jump to top when you call any notifies, so remove all focuses by adding android:focusableInTouchMode="true" in the GroupView.
I do not rely on onBindViewHolder for these kind of things. It can be called multiple times for a position. For the lists which has load more option maybe you should use something like this after your recyclerview inflated.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if ((((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0)) {
if (args.listModel.hasMore && null != mLoadMoreCallback && !loadMoreStarted) {
mLoadMoreCallbacks.onLoadMore();
}
}
}
});
Hope it helps.
I suggest you to use notifyItemRangeInserted method of RecyclerView.Adapter for LoadMore operations. You add a set of new items to your list so you do not need to notify whole dataset.
notifyItemRangeInserted(int positionStart, int itemCount)
Notify any registered observers that the currently reflected itemCount
items starting at positionStart have been newly inserted.
For more information:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html
You need to nofity the item in specific range like below:
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
List<Messages> messegaes=getFromDB();
chatMessagesAdapter.setMessageItemList(messages);
// Notify adapter with appropriate notify methods
int curSize = chatMessagesAdapter.getItemCount();
chatMessagesAdapter.notifyItemRangeInserted(curSize,messages.size());
}
Checkout Firebase Friendlychat source-code on Github.
It behaves like you want, specially at:
mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
int friendlyMessageCount = mFirebaseAdapter.getItemCount();
int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition();
// If the recycler view is initially being loaded or the user is at the bottom of the list, scroll
// to the bottom of the list to show the newly added message.
if (lastVisiblePosition == -1 ||
(positionStart >= (friendlyMessageCount - 1) && lastVisiblePosition == (positionStart - 1))) {
mMessageRecyclerView.scrollToPosition(positionStart);
}
}
});
You have this issue because every time your condition be true you call loadMore method even loadMore was in running state, for solving this issue you must put one boolean value in your code and check that too.
check my following code to get more clear.
1- declare one boolean value in your adapter class
2- set it to true in your condition
3- set it to false after you've got data from database and notified your adapter.
so your code must be like as following code:
public class YourAdapter extend RecylerView.Adapter<.....> {
private boolean loadingDataInProgress = false;
public void setLoadingDataInProgress(boolean loadingDataInProgress) {
this.loadingDataInProgress = loadingDataInProgress
}
....
// other code
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks && !loadingDataInProgress){
loadingDataInProgress = true;
mLoadMoreCallbacks.onLoadMore();
}
......
//// other adapter code
}
in Activity :
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
chatMessagesAdapter.notifyDataSetChanged();
chatMessagesAdapter. setLoadingDataInProgress(false);
}
This must fix your problem but I prefer to handle loadMore inside Activity or Presenter class with set addOnScrollListener on RecyclerView and check if findFirstVisibleItemPosition in LayoutManager is 0 then load data.
I've wrote one library for pagination, feel free to use or custom it.
PS: As other user mentioned don't use notifyDataSetChanged because this will refresh all view include visible views that you don't want to refresh those, instead use notifyItemRangeInsert, in your case you must notify from 0 to size of loaded data from database.
In your case as you load from top, notifyDataSetChanged will change scroll position to top of new loaded data, so you MUST use notifyItemRangeInsert to get good feel in your app
You need to nofity the item in specific range
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
List<Messages> messegaes=getFromDB();
chatMessagesAdapter.setMessageItemList(messages);
// Notify adapter with appropriate notify methods
int curSize = chatMessagesAdapter.getItemCount();
chatMessagesAdapter.notifyItemRangeInserted(curSize,messages.size());
}

Recyclerview scrollToPosition does not work

I have tried everything from the answers to the questions on stackoverflow, but I still could not figure this out. In one of my recyclerview, I was able to scroll to the desired position after a click of a button. On a different recyclerview (it was built similar to the first one), I was not able to scroll to the desired position.
This is the code for the recyclerview that worked:
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
Button button = (Button) view.findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
newFeedRecyclerView.smoothScrollToPosition(2);
}
});
FeedRecyclerAdapter adapter = new FeedRecyclerAdapter(view.getContext(), allRows);
newFeedRecyclerView.setHasFixedSize(true);
newFeedRecyclerView.setLayoutManager(layoutManager);
newFeedRecyclerView.addItemDecoration(new VerticalSpaceItemDecorator(30));
newFeedRecyclerView.setAdapter(adapter);
And this is for the recyclerview that did not work:
allImageRecycleView.addItemDecoration(new VerticalSpaceItemDecorator(30));
allImageRecycleView.setNestedScrollingEnabled(false);
allImageRecycleView.setHasFixedSize(true);
LinearLayoutManager layoutManager = new >LinearLayoutManagerWithSmoothScroller(getContext());
Button button = (Button)view.findViewById(R.id.button3);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
allImageRecycleView.smoothScrollToPosition(2);
}
});
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
allImageRecycleView.setLayoutManager(layoutManager);
allImageRecycleView.setAdapter(allImageRecyclerAdapter);
Thank you so much for reading this.
Try with this
layoutManager.scrollToPosition(index);
Use
layoutManager.scrollToPositionWithOffset(int position, int offset)
scrollToPositionWithOffset(int position, int offset) Scroll to the
specified adapter position with the given offset from resolved layout
start.
src : https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html
May not be relevant to your case but in general:
I find that the problem why scrollToPosition , and others API, does not work is because the dataset is not yet ready. We should call scrollToPosition when the recyclerView has received dataset. To do that, we can use AdapterDataObserver.
val adapter = binding.recyclerView.adapter!!
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
binding.recyclerView.scrollToPosition(positionStart)
adapter.unregisterAdapterDataObserver(this)
}
Similarly, you can override other methods of Class AdapterDataObserver, if needed.

Different card size

How can I make it so that in the recycleView there are differents size cards? (1x1, 1x2 and 2x1 where 1 is the card length)
enter image description here
You can create two view holders. One of them holds the two cards that are in the same row, the other holds the full row one. It would definitely look like the image you posted. For implementing the recycler view with multiple view holders check out this.
You can use GridLayoutManager with different span count.
Here is some example.
In activity:
//Initialize recyclerView and adapter before
GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
if (adapter.isHeader(position)) {
//Returns span count 2 if method isHeader() returns true.
//You can use your own logic here.
return mLayoutManager.getSpanCount()
} else {
return 1;
}
}
}
});
And add this method to your adapter class:
public boolean isHeader(int position) {
return position == 0;//you can use some other logic in here
}

RecyclerView StaggeredGridLayoutManager reordering issue

I'm trying to display three (at least that's the case I have an issue with) items in a RecyclerView with a StaggeredGridLayoutManager with two columns. The first item is spanned across the two rows. Here's how it looks like:
Now, I'm moving the item "Item 2" to top. Here's the code I call, in the adapter (it's a sample I wrote to demonstrate the issue I have in a more complex project):
private int findById(int id) {
for (int i = 0; i < items.size(); ++i) {
if (items.get(i).title.equals("Item " + id)) {
return i;
}
}
return -1;
}
// Moving the item "Item 2" with id = 2 and position = 0
public void moveItem(int id, int position) {
final int idx = findById(id);
final Item item = items.get(idx);
if (position != idx) {
items.remove(idx);
items.add(position, item);
notifyItemMoved(idx, position);
//notifyDataSetChanged();
}
}
After that, the array is fine: [Item 2, Item 1, Item 3]. However, the view is far from fine:
If I touch the RecyclerView (enough to trigger the overscroll effect if there's not enough items to scroll), Item 2 move to the left, where I expected to see it in the first place (with a nice animation):
As you maybe saw in the code, I tried to replace notifyItemMoved(idx, position) by a call to notifyDataSetChanged(). It works, but the change is not animated.
I wrote a complete sample to demonstrate this and put it on GitHub. It's nearly minimal (there are options to move the item and toggle their spanning).
I don't see what I can be doing wrong. Is this a bug with StaggeredGridLayoutManager? I would like to avoid notifyDataSetChanged() as I would like to keep consistency regarding the animations.
Edit: after some digging, there's no need for a fully-spanned item to show the issue. I removed the full-span. When I try to move Item 2 to position 0, it doesn't move: Item 1 goes after it, and Item 3 is moved on the right, so I have: empty cell, Item 2, new line, Item 1, Item 3. I still have the correct layout after a scroll.
What's more interesting is that I don't have the issue with a GridLayoutManager. I need a full-span item so it's not a solution, but I guess it's indeed a bug in the StaggeredGridLayoutManager…
I don't have a complete answer, but I can point you to both a workaround and the bug report (that I believe is related).
The trick to updating the layout so that it looks like your second screenshot, is to call invalidateSpanAssignments() on the StaggeredGridLayoutManger (sglm) after you've called notifyItemMoved(). The "challenge" is that if you call it immediately after nIM(), it won't run. If you delay the call for a few ms, it will. So, in your referenced code for MainActivity, I've made your sglm a private field:
private StaggeredGridLayoutManager sglm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adapter = new Adapter();
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
sglm = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(sglm);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
}
And down in the switch block, reference it in a handler:
case R.id.move_sec_top:
adapter.moveItem(2, 0);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
sglm.invalidateSpanAssignments();
}
}, 100);
return true;
The result is that your animation still runs, the layout ends up the way you want it. This is a real kludge, but it does work. I believe this is the same bug that I found and reported at the following link:
https://code.google.com/p/android/issues/detail?id=93156
While my "symptom" and required call were different, the underlying issue seems to be identical.
Good luck!
EDIT: No need to postDelayed, simply posting will do the trick:
case R.id.move_sec_top:
adapter.moveItem(2, 0);
new Handler().post(new Runnable() {
#Override
public void run() {
sglm.invalidateSpanAssignments();
}
});
return true;
My original theory was that the call was blocked until the layout pass was over, but I believe that is not the case. Instead, I now think that if you call invalidateSpanAssignments() immediately, it actually executes too soon (before the layout changes have completed). So, the post above (without delay) simply adds the call to the end of the rendering queue where it happens after the layout.
Well I have done this way.
StaggeredGridLayoutManager gaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
gaggeredGridLayoutManager.setGapStrategy(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
recyclerView.setLayoutManager(gaggeredGridLayoutManager);
dataList = YourDataList (Your Code for Arraylist);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerAdapter = new DataAdapter(dataList, recyclerView);
recyclerView.setAdapter(recyclerAdapter);
// Magic line
recyclerView.addOnScrollListener(new ScrollListener());
Create class for Custom RecyclerView Scroll Listener.
private class ScrollListener extends RecyclerView.OnScrollListener {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
gaggeredGridLayoutManager.invalidateSpanAssignments();
}
}
Hope this will help you.

Categories

Resources