Hi I have vertical list (Custom ArrayAdapter) where each element has a horizontal RecyclerView, to give me a table like format.
What I want to do is, if one row is scrolled horizontally, rest rows too should be. I tried putting them just inside horizontal scroll view and various tricks available, but got no luck.
If possible, I would also like this synchronization keeping first element fixed.
Make an instance of RecyclerView.OnScrollListener. This instance will be common to all your horizontal recycler views. When onScrolled( ) gets called, scroll all the views except the one on which this got called.
Adding more to Little Child's answer, it was trickier than expected, I had to create a custom listener that was in my main Activity that was called by onScroll. This is because onScroll was in a getView method and did not get access to other views in listview, but my mainactivty could access them.
In my listadapter, I added
public interface RecycleListener {
public void onScroll(int scroll_x);
}
Then in getView
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
listener.onScroll(recyclerView.computeHorizontalScrollOffset());
}
});
and finally in MainActivity
dayAdapter.setRecycleListener(new DayAdapter.RecycleListener() {
int busy = 0;
#Override
public void onScroll(int scroll_x)
{
if(busy == 1) return;
busy = 1;
for(int i = 0;i<days.size();i++)
{
View view = lv.getChildAt(i);
if(view == null) continue;
RecyclerView r = (RecyclerView) view.findViewById(R.id.timetable_row_recycler);
LinearLayoutManager layoutManager = ( LinearLayoutManager) r.getLayoutManager();
layoutManager.scrollToPositionWithOffset(0,0-scroll_x);
}
busy = 0;
}
});
Note I found out that you can easily get scroll position by using int scroll_x = recyclerView.computeHorizontalScrollOffset() however there is no direct method to appy it back. The method layoutManager.scrollToPosition(i) scrolls to ith element and not the scroll position, so the only correct possible way is
layoutManager.scrollToPositionWithOffset(0,0-scroll_x);
or (in case of custom layoutmanager)
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(0,0-scroll_x);
We basically apply negative offset to the 0th position, so instead of scrolling to offset before 0th, it scrolls to our desired scroll_x.
UPDATE
this worked but caused a lot of lag, so later I ended up switching to android.widget.TableLayout, and inserted rows (android.widget.TableRow) like I was trying with recyclerview.
Related
I was trying to achieve a layout where there is carousel at the top and below it is a grid layout of images. The activity has a RecyclerView and the item of that RecyclerView(MainRV) itself is another RecyclerView(ChildRV). I made the first child of the MainRV use a LinearlayoutManager with Horizontal Scroll. The second child is using a GridLayoutManager. The ChildRV renders actual items.
The problem is, I want to implement lazy loading for the second child of the MainRV (grid view). But, When I try to add onScrollListener to the child, it is not getting triggered.
What I have tried:
I tried setting setNestedScrollingEnabled on both MainRV and ChildRV. Still it did not trigger the onScrollListener.
I implemented the onScrollListener on the MainRV and tried to get the second child after scroll state becomes IDLE. But when I call the findLastVisibleItemPosition() on the gridLayoutManager, it gives the position of the last item even though I am at the top of the grid view. Below is the snippet of the onScrollListener
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
RecyclerView recyclerView1 = (RecyclerView) recyclerView.getChildAt(1);
GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView1.getLayoutManager();
Log.e("Scrolled activity pos", "" + gridLayoutManager.findLastVisibleItemPosition());
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
The output of the above code gives the position of the last item which is 49 in my case.
Any help or suggestion would be greatly appreciated.
The issue here was resolved when I removed the "static" qualifier on the ViewHolder for the nested RecyclerView. For some reason, the reuse of the view to bind the list items was giving out false results when looking for the lastVisiblItem. If someone can explain why then I would be really greatful. I could not figure it out.
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());
}
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());
}
});
How does one refresh the data displayed in RecyclerView (calling notifyDataSetChanged on its adapter) and make sure that the scroll position is reset to exactly where it was?
In case of good ol' ListView all it takes is retrieving getChildAt(0), checking its getTop() and calling setSelectionFromTop with the same exact data afterwards.
It doesn't seem to be possible in case of RecyclerView.
I guess I'm supposed to use its LayoutManager which indeed provides scrollToPositionWithOffset(int position, int offset), but what's the proper way to retrieve the position and the offset?
layoutManager.findFirstVisibleItemPosition() and layoutManager.getChildAt(0).getTop()?
Or is there a more elegant way to get the job done?
I use this one.^_^
// Save state
private Parcelable recyclerViewState;
recyclerViewState = recyclerView.getLayoutManager().onSaveInstanceState();
// Restore state
recyclerView.getLayoutManager().onRestoreInstanceState(recyclerViewState);
It is simpler, hope it will help you!
I have quite similar problem. And I came up with following solution.
Using notifyDataSetChanged is a bad idea. You should be more specific, then RecyclerView will save scroll state for you.
For example, if you only need to refresh, or in other words, you want each view to be rebinded, just do this:
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
EDIT: To restore the exact same apparent position, as in, make it look exactly like it did, we need to do something a bit different (See below how to restore the exact scrollY value):
Save the position and offset like this:
LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
int firstItem = manager.findFirstVisibleItemPosition();
View firstItemView = manager.findViewByPosition(firstItem);
float topOffset = firstItemView.getTop();
outState.putInt(ARGS_SCROLL_POS, firstItem);
outState.putFloat(ARGS_SCROLL_OFFSET, topOffset);
And then restore the scroll like this:
LinearLayoutManager manager = (LinearLayoutManager) mRecycler.getLayoutManager();
manager.scrollToPositionWithOffset(mStatePos, (int) mStateOffset);
This restores the list to its exact apparent position. Apparent because it will look the same to the user, but it will not have the same scrollY value (because of possible differences in landscape/portrait layout dimensions).
Note that this only works with LinearLayoutManager.
--- Below how to restore the exact scrollY, which will likely make the list look different ---
Apply an OnScrollListener like so:
private int mScrollY;
private RecyclerView.OnScrollListener mTotalScrollListener = new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
mScrollY += dy;
}
};
This will store the exact scroll position at all times in mScrollY.
Store this variable in your Bundle, and restore it in state restoration to a different variable, we'll call it mStateScrollY.
After state restoration and after your RecyclerView has reset all its data reset the scroll with this:
mRecyclerView.scrollBy(0, mStateScrollY);
That's it.
Beware, that you restore the scroll to a different variable, this is important, because the OnScrollListener will be called with .scrollBy() and subsequently will set mScrollY to the value stored in mStateScrollY. If you do not do this mScrollY will have double the scroll value (because the OnScrollListener works with deltas, not absolute scrolls).
State saving in activities can be achieved like this:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(ARGS_SCROLL_Y, mScrollY);
}
And to restore call this in your onCreate():
if(savedState != null){
mStateScrollY = savedState.getInt(ARGS_SCROLL_Y, 0);
}
State saving in fragments works in a similar way, but the actual state saving needs a bit of extra work, but there are plenty of articles dealing with that, so you shouldn't have a problem finding out how, the principles of saving the scrollY and restoring it remain the same.
Keep scroll position by using #DawnYu answer to wrap notifyDataSetChanged() like this:
val recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
adapter.notifyDataSetChanged()
recyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
Yes you can resolve this issue by making the adapter constructor only one time, I am explaining the coding part here :
if (appointmentListAdapter == null) {
appointmentListAdapter = new AppointmentListAdapter(AppointmentsActivity.this);
appointmentListAdapter.addAppointmentListData(appointmentList);
appointmentListAdapter.setOnStatusChangeListener(onStatusChangeListener);
appointmentRecyclerView.setAdapter(appointmentListAdapter);
} else {
appointmentListAdapter.addAppointmentListData(appointmentList);
appointmentListAdapter.notifyDataSetChanged();
}
Now you can see I have checked the adapter is null or not and only initialize when it is null.
If adapter is not null then I am assured that I have initialized my adapter at least one time.
So I will just add list to adapter and call notifydatasetchanged.
RecyclerView always holds the last position scrolled, therefore you don't have to store last position, just call notifydatasetchanged, recycler view always refresh data without going to top.
Thanks
Happy Coding
The top answer by #DawnYu works, but the recyclerview will first scroll to the top, then go back to the intended scroll position causing a "flicker like" reaction which isn't pleasant.
To refresh the recyclerView, especially after coming from another activity, without flickering, and maintaining the scroll position, you need to do the following.
Ensure you are updating you recycler view using DiffUtil. Read more about that here: https://www.journaldev.com/20873/android-recyclerview-diffutil
Onresume of your activity, or at the point you want to update your activity, load data to your recyclerview. Using the diffUtil, only the updates will be made on the recyclerview while maintaining it position.
Hope this helps.
Here is an option for people who use DataBinding for RecyclerView.
I have var recyclerViewState: Parcelable? in my adapter. And I use a BindingAdapter with a variation of #DawnYu's answer to set and update data in the RecyclerView:
#BindingAdapter("items")
fun setRecyclerViewItems(
recyclerView: RecyclerView,
items: List<RecyclerViewItem>?
) {
var adapter = (recyclerView.adapter as? RecyclerViewAdapter)
if (adapter == null) {
adapter = RecyclerViewAdapter()
recyclerView.adapter = adapter
}
adapter.recyclerViewState = recyclerView.layoutManager?.onSaveInstanceState()
// the main idea is in this call with a lambda. It allows to avoid blinking on data update
adapter.submitList(items.orEmpty()) {
adapter.recyclerViewState?.let {
recyclerView.layoutManager?.onRestoreInstanceState(it)
}
}
}
Finally, the XML part looks like:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/possible_trips_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
app:items="#{viewState.yourItems}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
I was making a mistake like this, maybe it will help someone :)
If you use recyclerView.setAdapter every time new data come, it calls the adapter clear() method every time you use it, which causes the recyclerview to refresh and start over. To get rid of this, you need to use adapter.notiftyDatasetChanced().
1- You need to save scroll position like this
rvProduct.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
recyclerViewState = rvProduct.getLayoutManager().onSaveInstanceState(); // save recycleView state
}
});
2- And after you call notifyDataSetChanged then onRestoreInstanceState like this example
productsByBrandAdapter.addData(productCompareList);
productsByBrandAdapter.notifyDataSetChanged();
rvProduct.getLayoutManager().onRestoreInstanceState(recyclerViewState); // restore recycleView state
I have not used Recyclerview but I did it on ListView. Sample code in Recyclerview:
setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
rowPos = mLayoutManager.findFirstVisibleItemPosition();
It is the listener when user is scrolling. The performance overhead is not significant. And the first visible position is accurate this way.
Create Extention and use it entirely your App, if you are using DiffUtil you don't need to add adapter.notifyDataSetChanged()
fun RecyclerView.reStoreState(){
val recyclerViewState = this.layoutManager?.onSaveInstanceState()
this.layoutManager?.onRestoreInstanceState(recyclerViewState)
}
Then use it like this below
yourRecyclerView.reStoreState()
adapter.submitList(yourData)
yourRecyclerView.adapter = adapter
#BindingAdapter("items")
fun <T> RecyclerView.setItems(items: List<T>?) {
(adapter as? ListAdapter<T, *>)?.submitList(items) {
layoutManager?.onSaveInstanceState().let {
layoutManager?.onRestoreInstanceState(it)
}
}
}
mMessageAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onChanged() {
mLayoutManager.smoothScrollToPosition(mMessageRecycler, null, mMessageAdapter.getItemCount());
}
});
The solution here is to keep on scrolling recyclerview when new message comes.
The onChanged() method detects the action performed on recyclerview.
That's working for me in Kotlin.
Create the Adapter and hand over your data in the constructor
class LEDRecyclerAdapter (var currentPole: Pole): RecyclerView.Adapter<RecyclerView.ViewHolder>() { ... }
change this property and call notifyDataSetChanged()
adapter.currentPole = pole
adapter.notifyDataSetChanged()
The scroll offset doesn't change.
If you have one or more EditTexts inside of a recyclerview items, disable the autofocus of these, putting this configuration in the parent view of recyclerview:
android:focusable="true"
android:focusableInTouchMode="true"
I had this issue when I started another activity launched from a recyclerview item, when I came back and set an update of one field in one item with notifyItemChanged(position) the scroll of RV moves, and my conclusion was that, the autofocus of EditText Items, the code above solved my issue.
best.
Just return if the oldPosition and position is same;
private int oldPosition = -1;
public void notifyItemSetChanged(int position, boolean hasDownloaded) {
if (oldPosition == position) {
return;
}
oldPosition = position;
RLog.d(TAG, " notifyItemSetChanged :: " + position);
DBMessageModel m = mMessages.get(position);
m.setVideoHasDownloaded(hasDownloaded);
notifyItemChanged(position, m);
}
I had this problem with a list of items which each had a time in minutes until they were 'due' and needed updating. I'd update the data and then after, call
orderAdapter.notifyDataSetChanged();
and it'd scroll to the top every time. I replaced that with
for(int i = 0; i < orderArrayList.size(); i++){
orderAdapter.notifyItemChanged(i);
}
and it was fine. None of the other methods in this thread worked for me. In using this method though, it made each individual item flash when it was updated so I also had to put this in the parent fragment's onCreateView
RecyclerView.ItemAnimator animator = orderRecycler.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
}
I am trying to do a endless listview with the Commonsware Endless Adapter (https://github.com/commonsguy/cwac-endless), which works fine by default.
What I am trying to do is a basic chat application.
New items should appear at the end and the chat history should be loadable by the endless adapter when the user scrolls to the top.
This by itself is not hard to do. If the ArrayAdapter contains the data s.t. newest items are at position 0, then simply using android:stackFromBottom in the XML declaration of the ListView will put the beginning of the list at the end.
To make sure that the 'load more' inicator is located at the top, I override getItem, getView etc. to provide it with the reversed positions (count-1-position).
My Problem: When the EndlessAdapter adds new data at the end of the List, the "load more" indicator remains visible, causing it to endlessly fetch more data until there is no more to fetch.
Expected is that it loads one batch and the user then needs to scroll down (here:up) to load further elements.
What am I missing?
Personally, I'd consider pull-to-refresh for this scenario, rather than EndlessAdapter.
That being said, if you are seeing additional rows appear, but the pending view is still there, then your modified getView() is not working properly. The additional rows appearing would indicated that notifyDataSetChanged() itself is functioning (otherwise, those rows would not show up). So, if the pending view is still shown, then getView() presumably is returning the pending view (position 0, I'd guess). In fact, I have no idea how you can get a reverse EndlessAdapter to work, as the first row should always be the pending view and should always be loading data, until you run out of data (and, in the case of a chat, that's possibly never the case).
Hence, again, I'd use pull-to-refresh, or you are going to have to switch to a different "endless" scheme that is paying attention to scroll events, rather than just waiting for the pending view to be displayed, as the trigger to fetch more data.
Do not care on notifyDataSetChanged() help.
I implemented an adapter to return Integer.MAX_VALUE as count of elements and especially calculate index in a cache.
Here is some snippet:
<ListView
android:id="#+id/logContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stackFromBottom="true" />
......
logContainer.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if ((logAdapter.getCachedCount() - (Integer.MAX_VALUE - firstVisibleItem)) <= LINES_TO_TRIGGER_PRELOAD && !logAdapter.isPreloading()) {
logAdapter.preloadAtBackground();
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
});
class LogAdapter extends BaseAdapter {
private ArrayList<String> logList = new ArrayList<String>();
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView logLineView = new TextView(LogViewDialog.this);
int idx = logList.size() - (Integer.MAX_VALUE - position);
logLineView.setText(logList.get(idx));
return logLineView;
}
.........