As mentioned on the google issuetracker page on the fourth floor: https://issuetracker.google.com/issues/36952786
The workaround described earlier: "The workaround at the moment is listening to SCROLL_STATE_IDLE when the scroll starts and smoothScrollToPositionFromTop back to the same position." will not always work.
In fact, calling onScrollStateChanged using SCROLL_STATE_IDLE does not necessarily mean that the scrolling is complete. As a result, he still cannot guarantee that the Listview scrolls to the correct position each time, especially if the views of the list items are not the same.
After researching, I found a different approach that works perfectly correctly and reasonably. As you know, Listview provides the scrollListBy (int y) method, which allows us to instantly scroll through the List with y pixels. Then, using the timer, we can scroll the list smoothly and correctly ourselves.
The first thing we need to do is calculate the height of each view of the list item, including views off the screen. Since the list data and types of child views are already known earlier, you can calculate the height of each view of the list item. Thus, for a given position for scrolling to smooth, we can calculate its scroll distance in the y direction. In addition, the calculation should be performed after the initialization of the ListView.
Secondly, it is a combination of a timer and the scrollListBy (int) method. In fact, we can use the sendEmptyMessageDelayed () method android.os.Handler. So the solution could be:
public class ListViewSmoothScroller { private final static int MSG_ACTION_SCROLL = 1; private final static int MSG_ACTION_ADJUST = 2; private ListView mListView = null; protected int[] mItemAccumulateHeight = null; protected int mTimeStep = 20; protected int mHeaderViewHeight; private int mPos; private Method mTrackMotionScrollMethod = null; protected int mScrollUnit = 0; protected int mTotalMove = 0; protected int mTargetScrollDis = 0; private Handler mMainHandler = new Handler(Looper.getMainLooper()){ public void handleMessage(Message msg) { int what = msg.what; switch (what){ case MSG_ACTION_SCROLL: { int scrollDis = mScrollUnit; if(mTotalMove + mScrollUnit > mTargetScrollDis){ scrollDis = mTargetScrollDis - mTotalMove; } if(Build.VERSION.SDK_INT >= 19) { mListView.scrollListBy(scrollDis); } else{ if(mTrackMotionScrollMethod != null){ try { mTrackMotionScrollMethod.invoke(mListView, -scrollDis, -scrollDis); }catch(Exception ex){ ex.printStackTrace(); } } } mTotalMove += scrollDis; if(mTotalMove < mTargetScrollDis){ mMainHandler.sendEmptyMessageDelayed(MSG_ACTION_SCROLL, mTimeStep); }else { mMainHandler.sendEmptyMessageDelayed(MSG_ACTION_ADJUST, mTimeStep); } break; } case MSG_ACTION_ADJUST: { mListView.setSelection(mPos); break; } } } }; public ListViewSmoothScroller(Context context, ListView listView){ mListView = listView; mScrollUnit = Tools.dip2px(context, 60); mPos = -1; try { mTrackMotionScrollMethod = AbsListView.class.getDeclaredMethod("trackMotionScroll", int.class, int.class); }catch (NoSuchMethodException ex){ ex.printStackTrace(); mTrackMotionScrollMethod = null; } if(mTrackMotionScrollMethod != null){ mTrackMotionScrollMethod.setAccessible(true); } } public void smoothScrollToPosition(int pos){ if(mListView == null) return; if(mItemAccumulateHeight == null || pos >= mItemAccumulateHeight.length){ return ; } mPos = pos; mTargetScrollDis = mItemAccumulateHeight[pos]; mMainHandler.sendEmptyMessage(MSG_ACTION_SCROLL); } public void doMeasureOnLayoutChange(){ if(mListView == null){ return; } int headerCount = mListView.getHeaderViewsCount(); if(mListView.getChildCount() < headerCount + 1){ return ; } mHeaderViewHeight = 0; for(int i = 0; i < headerCount; i++){ mHeaderViewHeight += mListView.getChildAt(i).getHeight(); } View firstListItemView = mListView.getChildAt(headerCount); computeAccumulateHeight(firstListItemView); } protected void computeAccumulateHeight(View firstListItemView){ int len = listdata.size();
After initializing our ListView, we call the doMeasureOnLayoutChange () method. After that, we can scroll through the ListView using the smoothScrollToPosition (int pos) method. We can call the doMeasureOnLayoutChange () method as follows:
mListAdapter.notifyDataSetChanged(); mListView.post(new Runnable() { @Override public void run() { mListViewSmoothScroller.doMeasureOnLayoutChange(); } });
Finally, our ListView can scroll to the target position smoothly and, more importantly, correctly.