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.