Overdraw-optimize RecyclerView layouts - android

So I have a RecyclerView that has multiple view types that all have a different rendering background-wise. Naturally I want to avoid overdraw for all these components, so I give my RecyclerView and all views up in the hierarchy no background at all.
This works fine as is - until I start animating items in and out. The DefaultItemAnimator of course nicely blends items in and out and therefor opens a "hole" in the RecyclerView where the background of it shortly becomes visible.
Ok, I thought, lets try something - let's give the RecyclerView only a background when animations are actually running, but otherwise remove the background, so scrolling works smoothly at high FPS rates. However, this is actually harder than I originally thought, since there is no specific "animations will start" and corresponding "animations will end" signal in RecyclerView nor the ItemAnimator or related classes.
What I recently tried was to combine an AdapterDataObserver with an ItemAnimatorFinishedListener like this, but without success:
RecyclerView.ItemAnimator.ItemAnimatorFinishedListener finishListener =
new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
#Override
public void onAnimationsFinished() {
recycler.setBackgroundResource(0);
}
};
recycler.getAdapter().registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
start();
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
start();
}
#Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
start();
}
private void start() {
recycler.setBackgroundResource(R.color.white);
if (!recycler.getItemAnimator().isRunning()) {
return;
}
recycler.getItemAnimator().isRunning(finishListener);
}
}
);
The issue here is that the adapter's range callbacks are ran way earlier than the actual animations run, because the animations will not be scheduled before the next requestLayout() happens internally in the RecyclerView, i.e. recycler.getItemAnimator().isRunning() in my start() method always returns false, so the white background is never removed.
So before I start experimenting with an additional ViewTreeObserver.OnGlobalLayoutListener and bring that into the mix - has anybody found a proper, working (easier?!) solution to this problem?

Ok, I went further down the road and included a ViewTreeObserver.OnGlobalLayoutListener - this seems to be working:
/**
* This is a utility class that monitors a {#link RecyclerView} for changes and temporarily
* gives the view a background so we do not see any artifacts while items are animated in or
* out of the view, and, at the same time prevent the overdraw that would occur when we'd
* give the {#link RecyclerView} a permanent opaque background color.
* <p>
* Created by Thomas Keller <me#thomaskeller.biz> on 12.05.16.
*/
public class RecyclerBackgroundSaver {
private RecyclerView mRecyclerView;
#ColorRes
private int mBackgroundColor;
private boolean mAdapterChanged = false;
private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener
= new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// ignore layout changes until something actually changed in the adapter
if (!mAdapterChanged) {
return;
}
mRecyclerView.setBackgroundResource(mBackgroundColor);
// if no animation is running (which should actually only be the case if
// we change the adapter without animating anything, like complete dataset changes),
// do not do anything either
if (!mRecyclerView.getItemAnimator().isRunning()) {
return;
}
// remove this view tree observer, i.e. do not react on further layout changes for
// one and the same dataset change and give control to the ItemAnimatorFinishedListener
mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
mRecyclerView.getItemAnimator().isRunning(finishListener);
}
};
RecyclerView.ItemAnimator.ItemAnimatorFinishedListener finishListener
= new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
#Override
public void onAnimationsFinished() {
// the animation ended, reset the adapter changed flag so the next change kicks off
// the cycle again and add the layout change listener back
mRecyclerView.setBackgroundResource(0);
mAdapterChanged = false;
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
};
RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
mAdapterChanged = true;
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
mAdapterChanged = true;
}
#Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
mAdapterChanged = true;
}
};
public RecyclerBackgroundSaver(RecyclerView recyclerView, #ColorRes int backgroundColor) {
mRecyclerView = recyclerView;
mBackgroundColor = backgroundColor;
}
/**
* Enables the background saver, i.e for the next item change, the RecyclerView's background
* will be temporarily set to the configured background color.
*/
public void enable() {
checkNotNull(mRecyclerView.getAdapter(), "RecyclerView has no adapter set, yet");
mRecyclerView.getAdapter().registerAdapterDataObserver(mAdapterDataObserver);
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
}
/**
* Disables the background saver, i.e. for the next animation,
* the RecyclerView's parent background will again shine through.
*/
public void disable() {
mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(mGlobalLayoutListener);
if (mRecyclerView.getAdapter() != null) {
mRecyclerView.getAdapter().unregisterAdapterDataObserver(mAdapterDataObserver);
}
}
}

Related

Android RecyclerView with gridlayout smoothScrollTo() speed

I have a recyclerview with gridlayoutmanager.
If I run the code
recycler.smoothScrollTo(adapter.getItemCount())
the recycler scrolls really fast to the last element. I tried some solutions on Stackoverflow to make the scrolling slower, but all apply to Linearlayoutmanager not Gridlayoutmanager.
Any help?
I cannot say for sure what your problem is. But I am lucky enough to have a very simple GridLayoutManager recyclerview demo out there, very small sample project. I created a so branch and added a button that does the same you do.
Look it up: https://github.com/Gryzor/GridToShowAds/compare/so?expand=1
.setOnClickListener { mainRecyclerView.smoothScrollToPosition(data.size) }
And that alone just works.
Check the source code, it's a very simple sample for something unrelated, but happens to have a RV with a Grid Layout :)
UPDATE
What you actual want is to control the Speed at which the recyclerView scrolls. Ok.
It's not the RecyclerView that drives the scroll, it's actually the LayoutManager that does. How so?
If you look at RV's source code...
public void smoothScrollToPosition(int position) {
...
mLayout.smoothScrollToPosition(this, mState, position);
}
So it ends up calling mLayout. What is this?
#VisibleForTesting LayoutManager mLayout;
So, your LayoutManager#smoothScroll... method is used.
Decompiling now GridLayoutManager for science:
#Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
startSmoothScroll(linearSmoothScroller);
}
note: this method is actually in LinearLayoutManager because GridLayoutManager is a subclass and it doesn't override the method
A LinearSmoothScroller!; no parameter to specify the speed though...
Look at it:
public class LinearSmoothScroller extends RecyclerView.SmoothScroller {
private static final boolean DEBUG = false;
private static final float MILLISECONDS_PER_INCH = 25f;
private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
...
}
This class has a start() method described as:
* Starts a smooth scroll for the given target position.
So who calls this?
The mLayout.smoothScrollToPosition method does at the end in the startSmoothScroll(...) call.
public void startSmoothScroll(SmoothScroller smoothScroller) {
Starts a smooth scroll using the provided {#link SmoothScroller}.
mSmoothScroller.start(mRecyclerView, this);
So... in lieu of all this, the answer to your question is:
You need to create your extension of GridLayoutManager by subclassing it, and in it, override the smoothScrollToPosition method, to provide your own Scroller logic.
Thread carefully though, LayoutManagers are not the "simplest" classes of all time and they can be quite complicated to master.
Good luck! :)
My simple working solution currently is still implementing a timer then working with it.
final CountDownTimer scrollUp_timer = new CountDownTimer(50000, 30) {
#Override
public void onTick(long millisUntilFinished) {
if (layoutManager != null && layoutManager.findFirstVisibleItemPosition() != 0) searchRecyclerView.smoothScrollToPosition(layoutManager.findFirstVisibleItemPosition()-1);
}
#Override
public void onFinish() {
try{
}catch(Exception e){
// log
}
}
};
scrollUp.setOnDragListener(new View.OnDragListener() {
#Override
public boolean onDrag(View view, DragEvent dragEvent) {
layoutManager = ((GridLayoutManager)searchRecyclerView.getLayoutManager());
int action = dragEvent.getAction();
if (action == DragEvent.ACTION_DRAG_ENTERED) {
scrollUp_timer.start();
} else if (action == DragEvent.ACTION_DRAG_EXITED) {
searchRecyclerView.scrollBy(0,0);
scrollUp_timer.cancel();
}
return true;
}
});
You can extend:
class CSCustomRecyclerSmoothScroller(context: Context, speed: Float = 0.2f)
: LinearSmoothScroller(context) {
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float = speed
}
And use it like:
val shortAnimationDuration =
view.resources.getInteger(android.R.integer.config_shortAnimTime)
val scroller = CSCustomRecyclerSmoothScroller(this, speed = 0.15)
scroller.targetPosition = position
view.postDelayed({
layoutManager.startSmoothScroll(scroller)
}, shortAnimationDuration.toLong())
postDelayed can be necessary in some cases but maybe not in all.
I use similar code with GridLayoutManager I just tried to extract relevant parts from my way of writing things.

PagingLibrary with recyclerView can not stop loadAfter()

loadAfter() can't stop even though I didn't swipe the RecyclerView.
It keeps calling onBindView->loadAfter()
I tried to debug, find these:
method loadAroundInternal in class ContiguousPagedList
int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index, mStorage.getLeadingNullCount() + mStorage.getStorageCount());
index keep ++ while getBindView in recyclerView is being bigger
the appendItem is always >0
I'm not sure, but I think maybe
network->appendPage->notifyInserted->bindView->loadAfter->network
then the recyclerView adapter execute onBindView.But the bindViewHolder which position should't call called. So it can't stop.
I don't know whether I'm right or not.And I don't know how to fix it.
below are some relevant code I wrote:
#Override
public void onBindViewHolder(#NonNull BaseViewHolder holder, int position) {
VB binding = DataBindingUtil.getBinding(holder.itemView);
setData(binding, getItem(position));
binding.executePendingBindings();
}
In my own PagingDataSource which extends PageKeyedDataSource
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, T> callback) {
singleCreator.createSingle(params.key, params.requestedLoadSize)
.subscribe(new SingleObserver<Page<T>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Page<T> tPage) {
callback.onResult(tPage.list, tPage.pageNumber + 1);
}
#Override
public void onError(Throwable e) {
}
});
}
public interface SingleCreator<T> {
SingleSubscribeProxy<Page<T>> createSingle(int page, int pageSize);
}
If other things need, I will edit the question
=====================================
Add message:I found if I set an absolute value for recyclerView's height, such as 100dp, It works fine. Butwork with problem when wrap_content and match_parent.
So the problem now is:when 2 recyclerViews in nestedScrollView and with a wrap_content or match_parent, the bindView can't stop loadAfter() which is a method of paging library.
So the reason why It happened
When a network result backs, the recycleView refresh.
Since it's in a nestedScrollView, it has no exact height.
Then onSizeChanged->getItem->network.
For me, the solution is
write a custom linearLayout which implements NestedScrollingParent2;

How do I check when my ListView has finished redrawing?

I have a ListView. I updated its adapter, and call notifydatasetchanged(). I want to wait until the list finishes drawing and then call getLastVisiblePosition() on the list to check the last item.
Calling getLastVisiblePosition() right after notifydatasetchanged() doesn't work because the list hasnt finished drawing yet.
Hopefully this can help:
Setup an addOnLayoutChangeListener on the listview
Call .notifyDataSetChanged();
This will fire off the OnLayoutChangeListener when completed
Remove the listener
Perform code on update (getLastVisiblePosition() in your case)
mListView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mListView.removeOnLayoutChangeListener(this);
Log.e(TAG, "updated");
}
});
mAdapter.notifyDataSetChanged();
I think this implementation can solve the problem.
// draw ListView in UI thread
mListAdapter.notifyDataSetChanged();
// enqueue a message to UI thread
mListView.post(new Runnable() {
#Override
public void run() {
// this will be called after drawing completed
}
});
You can implement callback, after the notifyDataSetChanged() it will be called and do the job you need. It means the data has been loaded to the adapter and at next draw cycle will be visible on the GUI. This mechanism is built-in in Android. See DataSetObserver.
Implement his callback and register it into the list adapter, don't forget to unregister it, whenevr you no longer need it.
/**
* #see android.widget.ListAdapter#setAdapter(ListAdapter)
*/
#Override
public void setAdapter(android.widget.ListAdapter adapter) {
super.setAdapter(adapter);
adapter.registerDataSetObserver(new DataSetObserver() {
#Override
public void onChanged() {
onListChanged();
// ... or do something else here...
super.onChanged();
}
});
}

How to add dividers to LinearLayoutICS?

Background
I'm trying to switch my alternative "App Manager" app from ActionBarSherlock library to the support library Google has created, since it gets more updates (ActionBarSherlock is no longer being developed, link here ) and I think it should cover a lot of functionality.
The problem
All went well (or so it seems), except for a class named ICSLinearLayout on ActionBarSherlock I've used to show dividers on, that is now called LinearLayoutICS .
It just doesn't show the dividers:
Note: before you ask "why don't you just use a GridView?", here's the reason, and also this, in case I'd ever want to add headers.
The code
The code is about the same as I've used for ActionBarSherlock:
rowLayout=new LinearLayoutICS(_context,null);
rowLayout.setMeasureWithLargestChildEnabled(true);
rowLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
rowLayout.setDividerDrawable(_context.getResources().getDrawable(R.drawable.list_divider_holo_dark));
rowLayout.setOrientation(LinearLayout.HORIZONTAL);
... // add views, layout params, etc...
The question
How can I use this class in order to support showing dividers on all supported OS versions of the support library?
What is wrong with the code I've written?
OK, it seems setShowDividers and setDividerDrawable cannot be used because LinearLayoutICS doesn't have them .
Not only that, but Lint didn't warn me about it being used.
So, what I ended up with is copying LinearLayoutICS code (from here, hope it's the latest version) and some of the original LinearLayout code, to make something that does work. I hope it doesn't have any bugs.
Long live open source ... :)
Sadly setMeasureWithLargestChildEnabled isn't available for old APIs, so I think the ActionBarSherlock way is still better in case that's something you wish to use.
EDIT: the setMeasureWithLargestChildEnabled method doesn't work on ActionBarSherlock.
Here's the code, for those who wish to use. I hope next time the library gets updated, I will remember to check this issue again.
public class LinearLayoutICS extends LinearLayout
{
private Drawable mDivider;
private int mDividerWidth,mDividerHeight;
private int mShowDividers;
private int mDividerPadding;
public LinearLayoutICS(final Context context,final AttributeSet attrs)
{
super(context,attrs);
// the R is from "android.support.v7.appcompat.R" .
final TypedArray a=context.obtainStyledAttributes(attrs,R.styleable.LinearLayoutICS);
mDivider=a.getDrawable(R.styleable.LinearLayoutICS_divider);
if(mDivider!=null)
{
mDividerWidth=mDivider.getIntrinsicWidth();
mDividerHeight=mDivider.getIntrinsicHeight();
}
else mDividerHeight=mDividerWidth=0;
mShowDividers=a.getInt(R.styleable.LinearLayoutICS_showDividers,SHOW_DIVIDER_NONE);
mDividerPadding=a.getDimensionPixelSize(R.styleable.LinearLayoutICS_dividerPadding,0);
a.recycle();
setWillNotDraw(mDivider==null);
}
#Override
protected void onDraw(final Canvas canvas)
{
if(getOrientation()==VERTICAL)
drawDividersVertical(canvas);
else drawDividersHorizontal(canvas);
}
#Override
protected void measureChildWithMargins(final View child,final int parentWidthMeasureSpec,final int widthUsed,final int parentHeightMeasureSpec,final int heightUsed)
{
if(mDivider!=null)
{
final int childIndex=indexOfChild(child);
final int count=getChildCount();
final LayoutParams params=(LayoutParams)child.getLayoutParams();
// To display the dividers in-between the child views, we modify their margins
// to create space.
if(getOrientation()==VERTICAL)
{
if(hasDividerBeforeChildAt(childIndex))
params.topMargin=mDividerHeight;
else if(childIndex==count-1&&hasDividerBeforeChildAt(count))
params.bottomMargin=mDividerHeight;
}
else if(hasDividerBeforeChildAt(childIndex))
params.leftMargin=mDividerWidth;
else if(childIndex==count-1&&hasDividerBeforeChildAt(count))
params.rightMargin=mDividerWidth;
}
super.measureChildWithMargins(child,parentWidthMeasureSpec,widthUsed,parentHeightMeasureSpec,heightUsed);
}
void drawDividersVertical(final Canvas canvas)
{
final int count=getChildCount();
for(int i=0;i<count;i++)
{
final View child=getChildAt(i);
if(child!=null&&child.getVisibility()!=GONE&&hasDividerBeforeChildAt(i))
{
final LayoutParams lp=(LayoutParams)child.getLayoutParams();
drawHorizontalDivider(canvas,child.getTop()-lp.topMargin);
}
}
if(hasDividerBeforeChildAt(count))
{
final View child=getChildAt(count-1);
int bottom=0;
if(child==null)
bottom=getHeight()-getPaddingBottom()-mDividerHeight;
else bottom=child.getBottom();
drawHorizontalDivider(canvas,bottom);
}
}
void drawDividersHorizontal(final Canvas canvas)
{
final int count=getChildCount();
for(int i=0;i<count;i++)
{
final View child=getChildAt(i);
if(child!=null&&child.getVisibility()!=GONE&&hasDividerBeforeChildAt(i))
{
final LayoutParams lp=(LayoutParams)child.getLayoutParams();
drawVerticalDivider(canvas,child.getLeft()-lp.leftMargin);
}
}
if(hasDividerBeforeChildAt(count))
{
final View child=getChildAt(count-1);
int right=0;
if(child==null)
right=getWidth()-getPaddingRight()-mDividerWidth;
else right=child.getRight();
drawVerticalDivider(canvas,right);
}
}
void drawHorizontalDivider(final Canvas canvas,final int top)
{
mDivider.setBounds(getPaddingLeft()+mDividerPadding,top,getWidth()-getPaddingRight()-mDividerPadding,top+mDividerHeight);
mDivider.draw(canvas);
}
void drawVerticalDivider(final Canvas canvas,final int left)
{
mDivider.setBounds(left,getPaddingTop()+mDividerPadding,left+mDividerWidth,getHeight()-getPaddingBottom()-mDividerPadding);
mDivider.draw(canvas);
}
/**
* Determines where to position dividers between children.
*
* #param childIndex Index of child to check for preceding divider
* #return true if there should be a divider before the child at childIndex
* #hide Pending API consideration. Currently only used internally by the system.
*/
protected boolean hasDividerBeforeChildAt(final int childIndex)
{
if(childIndex==0)
return (mShowDividers&SHOW_DIVIDER_BEGINNING)!=0;
else if(childIndex==getChildCount())
return (mShowDividers&SHOW_DIVIDER_END)!=0;
else if((mShowDividers&SHOW_DIVIDER_MIDDLE)!=0)
{
boolean hasVisibleViewBefore=false;
for(int i=childIndex-1;i>=0;i--)
if(getChildAt(i).getVisibility()!=GONE)
{
hasVisibleViewBefore=true;
break;
}
return hasVisibleViewBefore;
}
return false;
}
#Override
public int getDividerPadding()
{
return mDividerPadding;
}
#Override
public void setDividerPadding(final int dividerPadding)
{
mDividerPadding=dividerPadding;
}
#Override
public void setShowDividers(final int showDividers)
{
if(mShowDividers!=showDividers)
requestLayout();
mShowDividers=showDividers;
}
#Override
public void setDividerDrawable(final Drawable divider)
{
if(divider==mDivider)
return;
mDivider=divider;
if(divider!=null)
{
mDividerWidth=divider.getIntrinsicWidth();
mDividerHeight=divider.getIntrinsicHeight();
}
else
{
mDividerWidth=0;
mDividerHeight=0;
}
setWillNotDraw(divider==null);
requestLayout();
}
#Override
public Drawable getDividerDrawable()
{
return mDivider;
}
}

android: smoothScrollToPosition() not working correctly

I'm trying to smoothly scroll to last element of a list after adding an element to the arrayadapter associated with the listview.
The problem is that it just scrolls to a random position
arrayadapter.add(item);
//DOES NOT WORK CORRECTLY:
listview.smoothScrollToPosition(arrayadapter.getCount()-1);
//WORKS JUST FINE:
listview.setSelection(arrayadapter.getCount()-1);
You probably want to tell the ListView to post the scroll when the UI thread can handle it (which is why yours it not scrolling properly). SmoothScroll needs to do a lot of work, as opposed to just go to a position ignoring velocity/time/etc. (required for an "animation").
Therefore you should do something like:
getListView().post(new Runnable() {
#Override
public void run() {
getListView().smoothScrollToPosition(pos);
}
});
(Copied from my answer: smoothScrollToPositionFromTop() is not always working like it should)
This is a known bug. See https://code.google.com/p/android/issues/detail?id=36062
However, I implemented this workaround that deals with all edge cases that might occur:
First call smothScrollToPositionFromTop(position) and then, when scrolling has finished, call setSelection(position). The latter call corrects the incomplete scrolling by jumping directly to the desired position. Doing so the user still has the impression that it is being animation-scrolled to this position.
I implemented this workaround within two helper methods:
smoothScrollToPosition()
public static void smoothScrollToPosition(final AbsListView view, final int position) {
View child = getChildAtPosition(view, position);
// There's no need to scroll if child is already at top or view is already scrolled to its end
if ((child != null) && ((child.getTop() == 0) || ((child.getTop() > 0) && !view.canScrollVertically(1)))) {
return;
}
view.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
view.setOnScrollListener(null);
// Fix for scrolling bug
new Handler().post(new Runnable() {
#Override
public void run() {
view.setSelection(position);
}
});
}
}
#Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) { }
});
// Perform scrolling to position
new Handler().post(new Runnable() {
#Override
public void run() {
view.smoothScrollToPositionFromTop(position, 0);
}
});
}
getChildAtPosition()
public static View getChildAtPosition(final AdapterView view, final int position) {
final int index = position - view.getFirstVisiblePosition();
if ((index >= 0) && (index < view.getChildCount())) {
return view.getChildAt(index);
} else {
return null;
}
}
You should use setSelection() method.
Use LayoutManager to smooth scroll
layoutManager.smoothScrollToPosition(recyclerView, new RecyclerView.State(), position);
final Handler handler = new Handler();
//100ms wait to scroll to item after applying changes
handler.postDelayed(new Runnable() {
#Override
public void run() {
listView.smoothScrollToPosition(selectedPosition);
}}, 100);
The set selection method mentioned by Lars works, but the animation was too jumpy for our purposes as it skips whatever was left. Another solution is to recall the method repeatedly until the first visible position is your index. This is best done quickly and with a limit as it will fight the user scrolling the view otherwise.
private void DeterminedScrollTo(Android.Widget.ListView listView, int index, int attempts = 0) {
if (listView.FirstVisiblePosition != index && attempts < 10) {
attempts++;
listView.SmoothScrollToPositionFromTop (index, 1, 100);
listView.PostDelayed (() => {
DeterminedScrollTo (listView, index, attempts);
}, 100);
}
}
Solution is in C# via. Xamarin but should translate easily to Java.
Do you call arrayadapter.notifyDataSetChanged() after you called arrayadapter.add()? Also to be sure, smoothScrollToPosition and setSelection are methods available in ListView not arrayadapter as you have mentioned above.
In any case see if this helps:
smoothScrollToPosition after notifyDataSetChanged not working in android
Solution in kotlin:
fun AbsListView.scrollToIndex(index: Int, duration: Int = 150) {
smoothScrollToPositionFromTop(index, 0, duration)
postDelayed({
setSelection(index)
post { smoothScrollToPositionFromTop(index, 0, duration) }
}, duration.toLong())
}
PS: Looks like its quite messed up on Android SDK side so this is kind of best you can get, if you don't want to calculate your view offset manually. Maybe best easy way is to set duration to 0 for long list to avoid any visible jump.
I had some issues when calling just setSelection in some positions in GridView so this really seems to me as solution instead of using that.
use this in android java, it work for me:
private void DeterminedScrollTo(int index, int attempts) {
if (listView.getFirstVisiblePosition() != index && attempts < 10) {
attempts++;
if (listView.canScrollVertically(pos))
listView.smoothScrollToPositionFromTop(index, 1, 200);
int finalAttempts = attempts;
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
#Override
public void run() {
DeterminedScrollTo(index, finalAttempts);
}
}, 200);
}
}

Categories

Resources