Android 将项目添加到ListView,保持滚动位置,并且看不到滚动跳转
我正在构建一个类似于GoogleHangouts聊天界面的界面。新消息将添加到列表的底部。向上滚动到列表顶部将触发加载以前的消息历史记录。当历史记录来自网络时,这些消息会被添加到列表的顶部,并且不会从触发加载时用户停止的位置触发任何类型的滚动。换句话说,“加载指示器”显示在列表顶部: 然后将其替换为任何加载的历史记录 我有所有这些工作。。。除了一件我不得不通过反思来完成的事情。在向连接到ListView的适配器添加项目时,有很多问题和答案只涉及保存和恢复滚动位置。我的问题是,当我做以下事情时(简化但应不言自明): 它有用吗?对是不是很可怕?对它将来会起作用吗?谁知道呢?有更好的办法吗这是我的问题。 我如何做到这一点而不反思? 答案可能是“编写您自己的Android 将项目添加到ListView,保持滚动位置,并且看不到滚动跳转,android,android-listview,Android,Android Listview,我正在构建一个类似于GoogleHangouts聊天界面的界面。新消息将添加到列表的底部。向上滚动到列表顶部将触发加载以前的消息历史记录。当历史记录来自网络时,这些消息会被添加到列表的顶部,并且不会从触发加载时用户停止的位置触发任何类型的滚动。换句话说,“加载指示器”显示在列表顶部: 然后将其替换为任何加载的历史记录 我有所有这些工作。。。除了一件我不得不通过反思来完成的事情。在向连接到ListView的适配器添加项目时,有很多问题和答案只涉及保存和恢复滚动位置。我的问题是,当我做以下事情时
ListView
,它可以处理这个问题。”我只想问您是否已经完成了
编辑:根据Luksprog的评论/回答无需反思的工作解决方案。
建议使用onpredrawstener()
。迷人的!我以前使用过ViewTreeObserver,但从未使用过。经过一番周折之后,下面这类事情似乎运行得相当完美
public void addNewItems(List<Item> items) {
final int positionToSave = listView.getFirstVisiblePosition();
adapter.addAll(items);
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(positionToSave);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if(listView.getFirstVisiblePosition() == positionToSave) {
listView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
else {
return false;
}
}
});
}
public void addNewItems(列表项){
final int positionToSave=listView.getFirstVisiblePosition();
adapter.addAll(项目);
post(新的Runnable(){
@凌驾
公开募捐{
listView.setSelection(位置保存);
}
});
listView.getViewTreeObserver().addOnPreDrawListener(新的OnPreDrawListener()){
@凌驾
公共布尔onPreDraw(){
if(listView.getFirstVisiblePosition()==positionToSave){
listView.getViewTreeObserver().removeOnPreDrawListener(此);
返回true;
}
否则{
返回false;
}
}
});
}
非常酷。正如我在评论中所说,一个
onpredrawstener
可能是解决这个问题的另一个选择。使用侦听器的想法是跳过显示两种状态之间的ListView
(添加数据后,将选择设置到正确位置后)。在OnPreDrawListener
(使用listViewReference.getViewTreeObserver().addOnPreDrawListener(listener);
设置)中,您将检查ListView
的当前可见位置,并根据ListView
应显示的位置对其进行测试。如果这些不匹配,则使侦听器的方法返回false
跳过该帧,并将列表视图上的选择设置到正确的位置。设置正确的选择将再次触发绘图侦听器,这一次位置将匹配,在这种情况下,您需要注销OnPreDrawlistener
,并返回true
,在找到类似的解决方案之前,我一直在绞尽脑汁。
在添加一组项目之前,必须保存第一个可见项目的顶部距离,在添加项目之后,执行setSelectionFromTop()
代码如下:
// save index and top position
int index = mList.getFirstVisiblePosition();
View v = mList.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// for (Item item : items){
mListAdapter.add(item);
}
// restore index and top position
mList.setSelectionFromTop(index, top);
对于我来说,它的工作没有任何跳跃,它的列表中有大约500个项目:)
我从这篇SO帖子中获取了这段代码:问题作者建议的代码有效,但很危险。
例如,此条件:
listView.getFirstVisiblePosition() == positionToSave
如果未更改任何项目,则可能始终为真
在当前元素的上方和下方添加了任意数量的元素的情况下,我对这个aproach有一些问题。因此,我提出了一个略微改进的版本:
/* This listener will block any listView redraws utils unlock() is called */
private class ListViewPredrawListener implements OnPreDrawListener {
private View view;
private boolean locked;
private ListViewPredrawListener(View view) {
this.view = view;
}
public void lock() {
if (!locked) {
locked = true;
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
public void unlock() {
if (locked) {
locked = false;
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
@Override
public boolean onPreDraw() {
return false;
}
}
/* Method inside our BaseAdapter */
private updateList(List<Item> newItems) {
int pos = listView.getFirstVisiblePosition();
View cell = listView.getChildAt(pos);
String savedId = adapter.getItemId(pos); // item the user is currently looking at
savedPositionOffset = cell == null ? 0 : cell.getTop(); // current item top offset
// Now we block listView drawing until after setSelectionFromTop() is called
final ListViewPredrawListener predrawListener = new ListViewPredrawListener(listView);
predrawListener.lock();
// We have no idea what changed between items and newItems, the only assumption
// that we make is that item with savedId is still in the newItems list
items = newItems;
notifyDataSetChanged();
// or for ArrayAdapter:
//clear();
//addAll(newItems);
listView.post(new Runnable() {
@Override
public void run() {
// Now we can finally unlock listView drawing
// Note that this code will always be executed
predrawListener.unlock();
int newPosition = ...; // Calculate new position based on the savedId
listView.setSelectionFromTop(newPosition, savedPositionOffset);
}
});
}
/*此侦听器将阻止调用的任何listView重画utils unlock()*/
私有类ListViewPredrawListener实现OnPreDrawListener{
私人视野;
私有布尔锁;
私有ListViewPredrawListener(视图){
this.view=视图;
}
公共无效锁(){
如果(!已锁定){
锁定=真;
view.getViewTreeObserver().addOnPreDrawListener(此);
}
}
公开作废解锁(){
如果(已锁定){
锁定=错误;
view.getViewTreeObserver().removeOnPreDrawListener(此);
}
}
@凌驾
公共布尔onPreDraw(){
返回false;
}
}
/*方法在我们的BaseAdapter中*/
私有更新列表(列出新项){
int pos=listView.getFirstVisiblePosition();
查看单元格=listView.getChildAt(pos);
String savedId=adapter.getItemId(pos);//用户当前正在查看的项目
savedPositionOffset=cell==null?0:cell.getTop();//当前项顶部偏移量
//现在我们阻止listView绘图,直到调用setSelectionFromTop()之后
最终ListViewPredrawListener predrawListener=新ListViewPredrawListener(listView);
predrawstener.lock();
//我们不知道项目和新项目之间发生了什么变化,这是唯一的假设
//我们要做的是,带有savedId的项仍然在newItems列表中
项目=新项目;
notifyDataSetChanged();
//或对于ArrayAdapter:
//清除();
//addAll(新项目);
post(新的Runnable(){
@凌驾
公开募捐{
//现在我们终于可以解锁listView图形了
//请注意,此代码将始终执行
predrawstener.unlock();
int newPosition=…;//基于savedId计算新位置
listView.setSelectionFromTop(新建位置、保存位置偏移);
}
});
}
我想您可以尝试使用listView.getFirstVisiblePosition() == positionToSave
/* This listener will block any listView redraws utils unlock() is called */
private class ListViewPredrawListener implements OnPreDrawListener {
private View view;
private boolean locked;
private ListViewPredrawListener(View view) {
this.view = view;
}
public void lock() {
if (!locked) {
locked = true;
view.getViewTreeObserver().addOnPreDrawListener(this);
}
}
public void unlock() {
if (locked) {
locked = false;
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
@Override
public boolean onPreDraw() {
return false;
}
}
/* Method inside our BaseAdapter */
private updateList(List<Item> newItems) {
int pos = listView.getFirstVisiblePosition();
View cell = listView.getChildAt(pos);
String savedId = adapter.getItemId(pos); // item the user is currently looking at
savedPositionOffset = cell == null ? 0 : cell.getTop(); // current item top offset
// Now we block listView drawing until after setSelectionFromTop() is called
final ListViewPredrawListener predrawListener = new ListViewPredrawListener(listView);
predrawListener.lock();
// We have no idea what changed between items and newItems, the only assumption
// that we make is that item with savedId is still in the newItems list
items = newItems;
notifyDataSetChanged();
// or for ArrayAdapter:
//clear();
//addAll(newItems);
listView.post(new Runnable() {
@Override
public void run() {
// Now we can finally unlock listView drawing
// Note that this code will always be executed
predrawListener.unlock();
int newPosition = ...; // Calculate new position based on the savedId
listView.setSelectionFromTop(newPosition, savedPositionOffset);
}
});
}