我有以下代码来计算列表更新前的列表滚动位置并返回到原始位置。我想根据列表视图中可见的最后一项进行对齐。
mList
是我试图保持滚动位置的列表。isScrolling
是用户手动滚动列表视图时返回的函数。mAssocPage.mListPosition
是一个包含滚动信息的类。.selectionBase
是mAssocPage.arrItems
中的对象,用作计算滚动位置的基础。.nPosExtra
是列表视图中最后一个可见项的最高值。此函数在mList的OnScrollListener中调用,因此将始终包含listview的位置。
public synchronized void calculateListPosition(){
int nItemIndex=mList.getLastVisiblePosition()-mAdapter.getPaddingCount()-1;
//Is there something to align?
if(0<=nItemIndex && nItemIndex<mAssocPage.arrItems.size()){
mAssocPage.mListPosition.setAtBottom(false);
mAssocPage.mListPosition.selectionBase=mAssocPage.arrItems.get(nItemIndex);
mAssocPage.mListPosition.nPosExtra=mList.getChildAt(mList.getChildCount()-1).getTop();
}else{ // Force to top
mAssocPage.mListPosition.selectionBase=null;
mAssocPage.mListPosition.nPosExtra=0;
mAssocPage.mListPosition.setAtBottom(nItemIndex>=mAssocPage.arrItems.size());
}
}
如果列表视图位于顶部,如果用户没有滚动列表视图,则滚动返回功能会平滑地向上滚动到顶部。 +1用于delta
是因为mList有标题。在这种情况下,arrItem不会丢失任何项目,因此保证0&lt; = nPos&lt; = mList.arrItems.size()。
public void returnListPosition(){
if(mAssocPage.mListPosition.bInitialized){
int delta=mAdapter.getPaddingCount()+1;
if(mAssocPage.mListPosition.isAtBottom()){
mList.setSelection(mAssocPage.arrItems.size()+delta);
}else{
int nPos=mAssocPage.mListPosition.selectionBase==null?0:Collections.binarySearch(mAssocPage.arrItems, mAssocPage.mListPosition.selectionBase);
if(nPos<0){
nPos=-nPos+1;
mList.setSelection(nPos+delta);
}else{
mList.setSelectionFromTop(nPos+delta, mAssocPage.mListPosition.nPosExtra);
}
if(mAssocPage.mListPosition.isAtTop() && !isScrolling()){ // Move to top
mList.smoothScrollToPosition(0);
mAssocPage.mListPosition.nPosExtra=0;
if(mAssocPage.arrItems.size()>0)
mAssocPage.mListPosition.selectionBase=mAssocPage.arrItems.get(0);
}
}
}
}
mAssocList中的以下函数将通过替换为修改过的arrItems(arrToReplace)来修改旧的arrItems,如果列表可见,它将滚动列表。为了确保一次调用一个函数,我使用了synchronized
。
mAssocView
是使用以上两个函数扩展View
的类。notifyAssocView
会调用适配器的notifyDataSetChanged
。将始终使用带有一个参数的函数调用该函数。
来自https://github.com/GaulSori/artwave/blob/master/src/gaulsori/artwave/items/SinglePage.java
private void applyTempEditor(final ArrayList<SpecialListItem> arrToReplace){
applyTempEditor(arrToReplace, false);
}
private void applyTempEditor(final ArrayList<SpecialListItem> arrToReplace, boolean withoutSync){
if(!withoutSync){
synchronized(this){
applyTempEditor(arrToReplace, true);
}
return;
}
if(mAssocView==null || mAssocView.getHandler()==null){
arrItems=arrToReplace;
}else if(Utils.isUiThread()){
// FIXME Sometimes position returning does NOT work
arrItems=arrToReplace;
notifyAssocView();
mAssocView.returnListPosition();
}else{
final Semaphore semp=new Semaphore(1);
semp.drainPermits();
if(mAssocView.getHandler().post(new Runnable() { @Override public void run() {
applyTempEditor(arrToReplace, true);
semp.release();
}})==false)
throw new RuntimeException("?????");
semp.acquireUninterruptibly(); // Wait for UI is actually changed
}
}
applyTempEditor
经常在非UI线程中调用,滚动位置有时会保留,有时不会。但话又说回来,如果UI更改是以编程方式锁定的(如果用户正在触摸屏幕),则更改将被推迟,直到arrItems的修改完成,并且在用户释放手指之后,列表视图的位置将被保留。
isUiLocked()
表示用户是否正在点击屏幕。以下代码将在多个非UI线程中频繁运行。
if(isUiLocked()){
return;
}
synchronized(this){
arrItemsTempEditor=new ArrayList<SpecialListItem>(arrItems);
applyNewItems(true, true, arrItemsTempEditor); // Modify arrItemsTempEditor ONLY
applyTempEditor(arrItemsTempEditor); // Apply to arrItems
}
notifyAssocView();
我尝试在returnListPosition
中包装mList.post(new Runnable(){ @Override public void run(){ ... } });
,但我发现没有区别 - 列表有时不会滚动。我检查了每次通过在applyTempEditor中的returnListPosition和android.util.Log.d("artwave", "setSelectionFromTop")
中添加android.util.Log.d("artwave", "update")
来调用该函数。由于mlistPosition未在onScroll之外修改,因此滚动位置将返回到原始位置最终,但看到滚动跳转仍然很烦人。 setSelectionFromTop有时可以工作的原因是什么?
答案 0 :(得分:0)
我添加了以下功能:
public void setListChanged(){
mListChanged=true;
}
确保在下一次applyPageStatus()调用时重置列表的适配器。
public void applyPageStatus(){
if(!Utils.isUiThread()){ // UI thread
mHandler.sendEmptyMessage(HandlerMessages.MESSAGE_SINGLE_PAGE_APPLY_STATUS);
return;
}
...
if(mListChanged){
mList.setAdapter(null);
mList.setAdapter(mAdapter);
returnListPosition();
}
}
通过删除适配器并重置,listview将立即对其子项进行更改。认为这是低效的,因为每次操作都需要处理很多视图,直到我有另一个解决方案,我将不得不坚持这个。
只有这个解决方案的后备是,如果列表正在投掷,它将会停止。