如四楼google issuetracker页面中提到的:<a href="https://issuetracker.google.com/issues/36952786" rel="nofollow noreferrer">https://issuetracker.google.com/issues /36952786
前面给出的解决方法,“现在的解决方法是在开始滚动时侦听 SCROLL_STATE_IDLE,并再次将 smoothScrollToPositionFromTop 到相同的位置。” 也不总是有效。
实际上,使用 SCROLL_STATE_IDLE 调用 onScrollStateChanged 并不一定意味着滚动已经完成。结果,它仍然不能保证 Listview 每次都滚动到正确的位置,尤其是当列表项视图不是都在同一高度时。
经过研究,我发现了另一种完全正确且合理的方法。众所周知,Listview 提供了一个方法 scrollListBy(int y),它使我们能够立即将 Listview 向上滚动 y 个像素。然后,在计时器的帮助下,我们可以自己流畅、正确地滚动列表。
我们需要做的第一件事是计算每个列表项视图的高度,包括屏幕外的视图。由于之前已经知道列表数据和子视图的类型,因此计算每个列表项视图的高度是可行的。因此,给定一个平滑滚动到的目标位置,我们可以计算它在 y 方向上的滚动距离。另外,计算应该在ListView初始化完成后进行。
第二件事是将计时器和 scrollListBy(int) 方法结合起来。其实我们可以使用android.os.Handler的sendEmptyMessageDelayed()方法。因此,解决方案可以是:
/**
* Created by CaiHaozhong on 2017/9/29.
*/
public class ListViewSmoothScroller {
private final static int MSG_ACTION_SCROLL = 1;
private final static int MSG_ACTION_ADJUST = 2;
private ListView mListView = null;
/* The accumulated height of each list item view */
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);
}
}
/* scroll to a target position smoothly */
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);
}
/* call after initializing ListView */
public void doMeasureOnLayoutChange(){
if(mListView == null){
return;
}
int headerCount = mListView.getHeaderViewsCount();
/* if no list item */
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);
}
/* calculate the accumulated height of each list item */
protected void computeAccumulateHeight(View firstListItemView){
int len = listdata.size();// count of list item
mItemAccumulateHeight = new int[len + 2];
mItemAccumulateHeight[0] = 0;
mItemAccumulateHeight[1] = mHeaderViewHeight;
int currentHeight = mHeaderViewHeight;
for(int i = 2; i < len + 2; i++){
currentHeight += getItemHeight(firstListItemView);
mItemAccumulateHeight[i] = currentHeight;
}
}
/* get height of a list item. You may need to pass the listdata of the list item as parameter*/
protected int getItemHeight(View firstListItemView){
// Considering the structure of listitem View and the list data in order to calculate the height.
}
}
完成初始化 ListView 后,我们调用 doMeasureOnLayoutChange() 方法。之后,我们可以通过smoothScrollToPosition(int pos)方法滚动ListView。我们可以像这样调用 doMeasureOnLayoutChange() 方法:
mListAdapter.notifyDataSetChanged();
mListView.post(new Runnable() {
@Override
public void run() {
mListViewSmoothScroller.doMeasureOnLayoutChange();
}
});
最后,我们的 ListView 可以平滑地滚动到目标位置,更重要的是,正确地滚动。