Recyclerview scrollToPosition does not work - android

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.

Related

Animate single view in recyclerview after button click

I've got a button and a recycler view. The button refreshes the list. I want to animate one of the text views in my recycler view when it gets updated. Not the whole recyclerview, not the whole row - just one view (in every row).
I tried putting the animation in onBindViewHolder. But this starts the animation on scrolling and when i add a list entry:
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
Animation animation = AnimationUtils.loadAnimation(context, R.anim.rv_animation_clockwise);
((ViewHolderItem)holder).tv.startAnimation(animation);
Then i tried adding a TextChangedListener to my text view. But this has the same effect as putting it straight into onBindViewHolder:
((ViewHolderItem)holder).tv.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
My last attempt was using findViewHolderForAdapterPosition. But it just doesn't do anything.
This is the refresh method which my button calls. It's in the RecyclerViewAdapter.
recyclerview is an instance variable which i set in onAttachedToRecyclerView:
RecyclerView recyclerView;
#Override
public void onAttachedToRecyclerView(#NonNull RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
this.recyclerView = recyclerView;
}
void refresh (List<Entry> al){
this.al = al;
notifyDataSetChanged();
((ViewHolderItem)recyclerView.findViewHolderForAdapterPosition(0)).tv.startAnimation(animation);
bump
You should implement your own ItemAnimator and set it to your RecyclerView. There is some useful information here: https://hackmd.io/#nesquena/r1IEQ-jAl?type=view
Thanks, i'll check out ItemAnimator later.
What i ended up doing yesterday was to add in a small delay between my adapter.refresh() and the animation. Like they suggested here. Weirdly this delay seems to be necessary even if notifyDataSetChanged(); is called after the animation. I don't think it's a good solution but it works for now.
final Animation animation = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.rv_animation_clockwise);
recyclerView.postDelayed(new Runnable()
{
#Override
public void run()
{
for (int i = 0; i < al.size(); i++) {
if (recyclerView.findViewHolderForAdapterPosition(i)!=null) {
((RecyclerViewAdapter.ViewHolderItem) recyclerView.findViewHolderForAdapterPosition(i)).tv.startAnimation(animation);
}
}
}
},50);

RecyclerView ItemTouchHelper doesn't remove item

I'm trying to implement swipe to archive note in RecyclerView.
It was working fine but after I added these codes to refresh the RecyclerView from onResume(), Swiping although does archive the Note, but the item doesn't get removed and stays at a state you can see in image below:
This is what I do in onResume() :
#Override
protected void onResume() {
super.onResume();
notes = noteDAO.getAllNotes();
noteAdapter = new NoteAdapter(notes,this);
recyclerView.setAdapter(noteAdapter);
}
ItemTouchHelper onSwiped():
#Override
public void onSwiped(#NonNull RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
noteAdapter.deleteItem(position,rv);
}
deleteItem method in Adapter:
public void deleteItem(int position, RecyclerView rv) {
noteDAO = DBInjector.provideNoteDao(context);
recentlyDeletedNote = notes.get(position);
recentlyDeletedNotePosition = position;
recentlyDeletedNote.setArchive(true);
notes.remove(position);
noteDAO.archiveNote(recentlyDeletedNote);
notifyItemRemoved(position);
}
I have tried a lot of solutions, the only reason why it is not updating the view is that on updating recycler views, views are getting updated especially when ItemTouchHelper is used. As I didn't have much choice I used
recreate()
function for refreshing the whole activity and the error went off.
PS: This is not the ideal solution, it is just workaround fix.

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

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

RecyclerView scroll in incorrect direction when new element is inserted

I use smoothScrollToPosition to scroll RecyclerView. It does scroll every time a new entry is inserted; but to the top, not to the bottom, which is the direction i want.
list_chat = (RecyclerView) findViewById(R.id.list_chat);
//Set up Layout Manager
linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setStackFromEnd(true);
list_chat.setLayoutManager(linearLayoutManager);
//set adapter
list_chat.setAdapter(adapter);
//set scroll
list_chat.post(new Runnable() {
#Override
public void run() {
list_chat.smoothScrollToPosition(adapter.getItemCount());
}
});
The adapter is from Firebase
adapter = new FirebaseRecyclerAdapter<ChatItem, ChatRecylerViewHolder>(ChatItem.class,R.layout.chat_item
,ChatRecylerViewHolder.class,queryChat ) {
#Override
protected void populateViewHolder(ChatRecylerViewHolder viewHolder, ChatItem model, int position) {
viewHolder.tvAuthorChat.setText(model.chatAuthor);
viewHolder.tvContentChat.setText(model.chatContent);
}
};
You do notice you are using linearLayoutManager.setStackFromEnd(true); this mean you first position is at the bottom. I suggest you a better option.
RecycleView didn't work the way listView work, you can scroll it with your layout manager something like this
linearLayoutManager.scrollToPositionWithOffset(position,offset);
Which position is the position you want to scroll to, offset is the offset within the current position. You could just use with one parameter as well.
linearLayoutManager.scrollToPosition(position);
Ok. I found the answer.
First, the old problem with my question: i thought list_chat.post is called whenever an item is inserted (turn out that is wrong). The reason for it keeps scrolling top is linearLayoutManager.setStackFromEnd(true);
Thus, the question comes down to Where to call the scrolling ?
The answer is : Since adapter manages data, it makes sense to guess that adapter will notify the insertion.
Here is the code
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
list_chat.smoothScrollToPosition(adapter.getItemCount());
}
});

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