Item inserted at first position is not visible until scrolling up - android

Scenario:
my adapter supports two different view types: A & B
the adapter is notified about 100 items of type A - everything is rendered correctly
the adapter is notified about one item of type B insertion using
notifyItemRangeInserted at position 0 - this item is "invisible"
by default and in order to see it I have to manually scroll up.
How can I get this first item of type B "automatically" visible?

You can use this line of code after notify item of type B:
yourRecyclerView.smoothScrollToPosition(0);

using https://stackoverflow.com/a/54899984/8144663 only wont resolve your issue.
You will need to call smoothScrollToPosition() in the next frame like,
recyclerview.post(new Runnable() {
#Override
public void run() {
recycleview.smoothScrollToPosition(n);
}
});

Related

Horizontal RecyclerView with DiffUtil issue

I have a RecyclerView with horizontal items. These items have fix 150dp height and 100dp width.
When the Fragment is loaded, than I set default items to this view.
After that, I make a Room getImages() call, when the result arrive than I rebuild this RecyclerView.
As you see in the GIF :
Default images loaded (2 with play button, 3 with pro image)
Add getImages() result
The result is inserted before the 1st default item
Play button cards have fix id actually ("-1") and pro have another fix ("0") and every image item have unique id.
The new item is added before the first item, and the user don't see it. The RecyclerView won't scroll to first item.
There is any solution push the first item to right when a new one added to 1st place??
I tried the smoothScrollToPosition but it won't work after submitList() and maybe where is any better solution.
All you have to do is use Recyclerview's smooth scroller
smoothScroller = new LinearSmoothScroller(this) {
#Override
protected int getHorizontalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_ANY;
}
};
then when you want to scroll to first position of recyclerview
smoothScroller.setTargetPosition(0);
horizontalLayoutManagaer.startSmoothScroll(smoothScroller);
Try to use scrollToPosition(0) instead.
If that won't work, try whether adding a small delay is fixing it, like so:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
recyclerView.scrollToPosition(position); // Or maybe `smoothScrollToPosition()`
}
}, 300); // Try a delay of 200 or 300 ms

android listview make an item visible without using scroll

I have a listview with 150+ items, I need to make one visible from code. I currently use smoothscrolltoposition but when the desired item is far away from the current visible item it takes several seconds to arrive.
Is there anyway to simply get rid of the smooth scrolling and simply make the item visible directly?
Thanks,
Ignacio
You can use postdelayed for smooth scroll
listview.postDelayed(new Runnable() {
#Override
public void run() {
// smoothscrolltoposition
}
}, 100);
After several testing and reading the thread suggested by audi , I got this solution:
Strangely, the trick is in reassign the adapter to the listview, not even it is needed to recreate it, just reassign.
listView.Adapter = adapter;
listView.FastScrollEnabled = true;
listView.SetSelection(index);
adapter.NotifyDataSetChanged();

Android: Detect if a ListView has the scrollbar (after setting new data)

I have an ArrayAdapter linked to a ListView.
mListView.setAdapter(mArrayAdapter);
Whenever I reset the ArrayList data to the ArrayAdapter:
mArrayAdapter.clear();
mArrayAdapter.addAll(mArrayList);
mArrayAdapter.notifyDataSetChanged()
the ListView gets correctly updated
however, if just after the above three lines, I call my custom method mListView.hasScrollbar() to detect whether the listview has a scrollbar or not, I get a null lastVisibleItem:
public boolean hasScrollbar() {
View lastVisibleItem = (View) getChildAt(getChildCount() - 1);
if (lastVisibleItem.getBottom()>=getHeight()) {
return true;
}
return false;
}
does it mean that the listview is still refreshing?
My main question is:
how can I test if the listview has the scrollbar after resetting the adapter with new data?
thank you for any help!
Using getLastVisiblePosition / getFirstVisiblePosition is a valid method of detecting wether you have scrolling or not within the list view (aslong as you compare it to getCount() and do your math ofc).
The problem you have as you already guess is that you are attempting to check out of sync.
In order to sync your query when the adapter already filled your List Data and updated changes, you need to issue a post request to the list, which will stack that petition to the message queue of the adapter.
yourAdapter.notifyDataSetChanged();
yourAdapter.getListView().post(new Runnable() {
#Override public void run() {
//your code here
}
});
Make sure to call that after notifySetDataChanged() of course. Because you want the list to update before the check.
I think your question equals to "how to tell if list view contains items need to displayed on more than one screen"?
So my suggestion is to use listView.getFirstVisiblePosition() and getLastVisiblePosition() to tell it.

Is there a way to prevent the listview from scrolling to its top position when its adapter's data is changed?

I'm having a bit of trouble preserving the scroll position of a list view when changing it's adapter's data.
What I'm currently doing is to create a custom ArrayAdapter (with an overridden getView method) in the onCreate of a ListFragment, and then assign it to its list:
mListAdapter = new CustomListAdapter(getActivity());
mListAdapter.setNotifyOnChange(false);
setListAdapter(mListAdapter);
Then, when I receive new data from a loader that fetches everything periodically, I do this in its onLoadFinished callback:
mListAdapter.clear();
mListAdapter.addAll(data.items);
mListAdapter.notifyDataSetChanged();
The problem is, calling clear() resets the listview's scroll position. Removing that call preserves the position, but it obviously leaves the old items in the list.
What is the proper way to do this?
As you pointed out yourself, the call to 'clear()' causes the position to be reset to the top.
Fiddling with scroll-position, etc. is a bit of a hack to get this working.
If your CustomListAdapter subclasses from ArrayAdapter, this could be the issue:
The call to clear(), calls 'notifyDataSetChanged()'. You can prevent this:
mListAdapter.setNotifyOnChange(false); // Prevents 'clear()' from clearing/resetting the listview
mListAdapter.clear();
mListAdapter.addAll(data.items);
// note that a call to notifyDataSetChanged() implicitly sets the setNotifyOnChange back to 'true'!
// That's why the call 'setNotifyOnChange(false) should be called first every time (see call before 'clear()').
mListAdapter.notifyDataSetChanged();
I haven't tried this myself, but try it :)
Check out: Maintain/Save/Restore scroll position when returning to a ListView
Use this to save the position in the ListView before you call .clear(), .addAll(), and . notifyDataSetChanged().
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
After updating the ListView adapter, the Listview's items will be changed and then set the new position:
mList.setSelectionFromTop(index, top);
Basically you can save you position and scroll back to it, save the ListView state or the entire application state.
Other helpful links:
Save Position:
How to save and restore ListView position in Android
Save State:
Android ListView y position
Regards,
Please let me know if this helps!
There is one more use-case I came across recently (Android 8.1) - caused by bug in Android code. If I use mouse-wheel to scroll list view - consecutive adapter.notifyDataSetChanged() resets scroll position to zero. Use this workaround until bug gets fixed in Android
listView.onTouchModeChanged(true); // workaround
adapter.notifyDataSetChanged();
More details is here: https://issuetracker.google.com/u/1/issues/130103876
In your Expandable/List Adapter, put this method
public void refresh(List<MyDataClass> dataList) {
mDataList.clear();
mDataList.addAll(events);
notifyDataSetChanged();
}
And from your activity, where you want to update the list, put this code
if (mDataListView.getAdapter() == null) {
MyDataAdapter myDataAdapter = new MyDataAdapter(mContext, dataList);
mDataListView.setAdapter(myDataAdapter);
} else {
((MyDataAdapter)mDataListView.getAdapter()).refresh(dataList);
}
In case of Expandable List View, you will use
mDataListView.getExpandableListAdapter() instead of
mDataListView.getAdapter()

Android: Spinner dropdown with selected item at top position

I have an issue with the Spinner in Android. Selecting an item from the dropdown will adjust the offset of that dropdown the next time it is opened. So for example if I choose item 100 in a 500 item dropdown, the next time I open the dropdown, item 100 will be at the top of the list. This is the behaviour I want.
There seems to be an issue when I combine the selector functionality with calling setSelection(int). With the following steps I seem to have broken the offset system on dropdown spinners.
Open the Spinner and select the second item.
Open the Spinner again and this time dismiss it without selecting anything.
Call setSelection(int) on the Spinner with a value greater than 2.
Open the Spinner a third time. Note that the offset is the same as back in Step 1.
I've taken a look at the code in Spinner and AdapterView, but I can't see anything public calls that I've missed. Is this a bug in Spinner or a bug in my code?
Did you try public void setSelection (int position, boolean animate)? I haven't tried it, but I think passing true as the second parameter should make the list scroll to the selected position. The other alternative is to calculate the scroll offset (item height x selected item position) and call setDropDownVerticalOffset.
Update: I tried modifying the Spinner example in API demos to use setSelection(7, true) and it seems to work when following the 4 steps you provided in your question. I just added a Handler and modified showToast as follows:
private final Handler handler = new Handler();
void showToast(CharSequence msg) {
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
handler.postDelayed(new Runnable(){
public void run() {
Toast.makeText(Spinner1.this, "auto setting", Toast.LENGTH_SHORT).show();
Spinner s2 = (Spinner) findViewById(R.id.spinner2);
s2.setSelection(7, true);
}
}, 5000);
}
I tested as follows:
open the second spinner and picking 'Venus' (the second selection).
open the second spinner, then press back to dismiss
after 5 seconds, the postDelayed call causes 'Neptune' (the seventh selection) to be selected
open the spinner and the offset is correct
I think you can solve that problem with sending List to Adapter. When an item selected, sort your List then use notifyDataSetChanged() function of adapter. When you called setSelection(int) function again sort your List and use notifyDataSetChanged() function.

Categories

Resources