OnScrollListener 重复调用
OnScrollListener called repeatedly
我有 2 个列表视图控件,每个控件显示列表的一部分数据。它们在 UI 片段中彼此上下排列。显示的数据是汽车的轮胎信息,因此顶部列表视图显示前轮数据,下方列表视图显示后轮数据(中间有汽车图像)。列表视图被配置为使每个视图中的数据列表镜像另一个视图中的数据。因此,顶部列表的顶部行与底部列表中的底部行具有相同的数据集。当用户滚动其中一个列表时,另一个列表将以镜像方式滚动。这使用户能够滚动数据,使他们感兴趣的数据直接位于汽车图像的上方和下方(顶部列表的底行,底部列表的顶行)
为此,我在一个列表视图上响应 OnScrollListener-OnScroll 事件,进行位置计算并在另一个列表视图上调用 listview.setSelectionFromTop(position, sety) 以更新其位置。
当我第一次发布该应用程序时,这在 Android 4.xx 中运行良好。现在在 Android 7 及更高版本中,似乎正在发生竞争条件,即在一个列表视图上设置位置会触发另一个列表视图上的 OnScroll,并且在一系列永无止境的 OnScroll 事件中,事情从那里开始变得糟糕。
我已经尝试在 OnScroll 函数上添加一个标志,这样如果它在来自另一个列表视图的 OnScroll 调用中,它就不会触发。这不起作用,因为调用 setSelectionFromTop 触发的 OnScroll 事件似乎是异步的,因此它们发生在标记区域之外。
我注意到的一件事是,列表视图的大小似乎很重要,它们的大小必须完全相同。在原始布局 (Android 4.x) 中,我设置了它们的 layoutHeight=120dp 来固定它们的大小。但是,当前的 android 似乎不遵守 layoutHeight 值,结果 UI 将它们显示为不同的大小,这可能是解开谜题的关键。
这里是其中一个listview的onScroll代码,另一个的代码是一样的,只是把lvTop换成lvBottom
OnScrollListener lTop=new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (fInBottomScrollListener)
return;
fInTopScrollListener=true;
// Get position of this lv top
View cTop = lvTop.getChildAt(0);
View cBottom = lvBottom.getChildAt(0);
if (cTop==null || cBottom==null) return;
int topOffset = cTop.getTop();
int itemHeight = cTop.getHeight();
int firstVisPos = lvTop.getFirstVisiblePosition();
int setposition = totalItemCount - (visibleItemCount - (topOffset!=0 ? 1:0)) - firstVisPos-1;
int sety= -(itemHeight + (topOffset>0 ? 0:topOffset));
lvBottom.setSelectionFromTop(setposition, sety);
fInTopScrollListener=false;
}
};
我在日志中看到了一个连续的 onScroll 事件列表,当 运行 在 Android 4.X 时,此列表在滚动完成后停止,而在 7.x更重要的是,它继续并且滚动基本上在两个列表视图上被冻结,直到我刷新片段
我认为你计算的位置和偏移量不正确,所以 2 个 ListViews 由于一个小的偏移量误差而跳动。
对于具有相同高度的 ListView:
AbsListView.OnScrollListener lTop = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInBottomScrollListener) {
fInTopScrollListener = true;
View cTop = lvTop.getChildAt(visibleItemCount - 1);
if (cTop == null) return;
int setposition = totalItemCount - visibleItemCount - firstVisibleItem;
int sety = view.getHeight() - cTop.getBottom();
lvBottom.setSelectionFromTop(setposition, sety);
fInTopScrollListener = false;
}
}
};
AbsListView.OnScrollListener lBottom = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInTopScrollListener) {
fInBottomScrollListener = true;
View cBottom = lvBottom.getChildAt(visibleItemCount - 1);
if (cBottom == null) return;
int setposition = totalItemCount - visibleItemCount - firstVisibleItem;
int sety = view.getHeight() - cBottom.getBottom();
lvTop.setSelectionFromTop(setposition, sety);
fInBottomScrollListener = false;
}
}
};
已更新:
这是另一种方法,无论列表视图的高度是否相同:
final private static int SCROLL_DELAY = 100;
long timestampScroll;
AbsListView.OnScrollListener lTop = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInBottomScrollListener) {
fInTopScrollListener = true;
View cTop = lvTop.getChildAt(0);
View cBot = lvBottom.getChildAt(0);
if (cTop == null || cBot == null) return;
int lvTopMovableRange = (cTop.getHeight() + lvTop.getDividerHeight()) * totalItemCount
- lvTop.getDividerHeight() - lvTop.getHeight();
int lvBotMovableRange = (cBot.getHeight() + lvBottom.getDividerHeight()) * totalItemCount
- lvBottom.getDividerHeight() - lvBottom.getHeight();
int lvTopPointer = (cTop.getHeight() + lvTop.getDividerHeight()) * firstVisibleItem
- cTop.getTop();
int lvBotPointer = (int)((float)(lvTopMovableRange - lvTopPointer)/lvTopMovableRange*lvBotMovableRange);
int setposition = lvBotPointer / (cBot.getHeight() + lvBottom.getDividerHeight());
int sety = -lvBotPointer + setposition * (cBot.getHeight() + lvBottom.getDividerHeight());
timestampScroll = System.currentTimeMillis();
lvBottom.setSelectionFromTop(setposition, sety);
lvTop.postDelayed(new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - timestampScroll > (SCROLL_DELAY - 10)) fInTopScrollListener = false;
}
}, SCROLL_DELAY);
}
}
};
AbsListView.OnScrollListener lBottom = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInTopScrollListener) {
fInBottomScrollListener = true;
View cTop = lvTop.getChildAt(0);
View cBot = lvBottom.getChildAt(0);
if (cTop == null || cBot == null) return;
int lvTopMovableRange = (cTop.getHeight() + lvTop.getDividerHeight()) * totalItemCount
- lvTop.getDividerHeight() - lvTop.getHeight();
int lvBotMovableRange = (cBot.getHeight() + lvBottom.getDividerHeight()) * totalItemCount
- lvBottom.getDividerHeight() - lvBottom.getHeight();
int lvBotPointer = (cBot.getHeight() + lvBottom.getDividerHeight()) * firstVisibleItem
- cBot.getTop();
int lvTopPointer = (int)((float)(lvBotMovableRange - lvBotPointer)/lvBotMovableRange*lvTopMovableRange);
int setposition = lvTopPointer / (cTop.getHeight() + lvTop.getDividerHeight());
int sety = -lvTopPointer + setposition * (cTop.getHeight() + lvTop.getDividerHeight());
timestampScroll = System.currentTimeMillis();
lvTop.setSelectionFromTop(setposition, sety);
lvBottom.postDelayed(new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - timestampScroll > (SCROLL_DELAY - 10)) fInBottomScrollListener = false;
}
}, SCROLL_DELAY);
}
}
};
希望对您有所帮助!
我有 2 个列表视图控件,每个控件显示列表的一部分数据。它们在 UI 片段中彼此上下排列。显示的数据是汽车的轮胎信息,因此顶部列表视图显示前轮数据,下方列表视图显示后轮数据(中间有汽车图像)。列表视图被配置为使每个视图中的数据列表镜像另一个视图中的数据。因此,顶部列表的顶部行与底部列表中的底部行具有相同的数据集。当用户滚动其中一个列表时,另一个列表将以镜像方式滚动。这使用户能够滚动数据,使他们感兴趣的数据直接位于汽车图像的上方和下方(顶部列表的底行,底部列表的顶行)
为此,我在一个列表视图上响应 OnScrollListener-OnScroll 事件,进行位置计算并在另一个列表视图上调用 listview.setSelectionFromTop(position, sety) 以更新其位置。
当我第一次发布该应用程序时,这在 Android 4.xx 中运行良好。现在在 Android 7 及更高版本中,似乎正在发生竞争条件,即在一个列表视图上设置位置会触发另一个列表视图上的 OnScroll,并且在一系列永无止境的 OnScroll 事件中,事情从那里开始变得糟糕。
我已经尝试在 OnScroll 函数上添加一个标志,这样如果它在来自另一个列表视图的 OnScroll 调用中,它就不会触发。这不起作用,因为调用 setSelectionFromTop 触发的 OnScroll 事件似乎是异步的,因此它们发生在标记区域之外。
我注意到的一件事是,列表视图的大小似乎很重要,它们的大小必须完全相同。在原始布局 (Android 4.x) 中,我设置了它们的 layoutHeight=120dp 来固定它们的大小。但是,当前的 android 似乎不遵守 layoutHeight 值,结果 UI 将它们显示为不同的大小,这可能是解开谜题的关键。
这里是其中一个listview的onScroll代码,另一个的代码是一样的,只是把lvTop换成lvBottom
OnScrollListener lTop=new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (fInBottomScrollListener)
return;
fInTopScrollListener=true;
// Get position of this lv top
View cTop = lvTop.getChildAt(0);
View cBottom = lvBottom.getChildAt(0);
if (cTop==null || cBottom==null) return;
int topOffset = cTop.getTop();
int itemHeight = cTop.getHeight();
int firstVisPos = lvTop.getFirstVisiblePosition();
int setposition = totalItemCount - (visibleItemCount - (topOffset!=0 ? 1:0)) - firstVisPos-1;
int sety= -(itemHeight + (topOffset>0 ? 0:topOffset));
lvBottom.setSelectionFromTop(setposition, sety);
fInTopScrollListener=false;
}
};
我在日志中看到了一个连续的 onScroll 事件列表,当 运行 在 Android 4.X 时,此列表在滚动完成后停止,而在 7.x更重要的是,它继续并且滚动基本上在两个列表视图上被冻结,直到我刷新片段
我认为你计算的位置和偏移量不正确,所以 2 个 ListViews 由于一个小的偏移量误差而跳动。
对于具有相同高度的 ListView:
AbsListView.OnScrollListener lTop = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInBottomScrollListener) {
fInTopScrollListener = true;
View cTop = lvTop.getChildAt(visibleItemCount - 1);
if (cTop == null) return;
int setposition = totalItemCount - visibleItemCount - firstVisibleItem;
int sety = view.getHeight() - cTop.getBottom();
lvBottom.setSelectionFromTop(setposition, sety);
fInTopScrollListener = false;
}
}
};
AbsListView.OnScrollListener lBottom = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInTopScrollListener) {
fInBottomScrollListener = true;
View cBottom = lvBottom.getChildAt(visibleItemCount - 1);
if (cBottom == null) return;
int setposition = totalItemCount - visibleItemCount - firstVisibleItem;
int sety = view.getHeight() - cBottom.getBottom();
lvTop.setSelectionFromTop(setposition, sety);
fInBottomScrollListener = false;
}
}
};
已更新:
这是另一种方法,无论列表视图的高度是否相同:
final private static int SCROLL_DELAY = 100;
long timestampScroll;
AbsListView.OnScrollListener lTop = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInBottomScrollListener) {
fInTopScrollListener = true;
View cTop = lvTop.getChildAt(0);
View cBot = lvBottom.getChildAt(0);
if (cTop == null || cBot == null) return;
int lvTopMovableRange = (cTop.getHeight() + lvTop.getDividerHeight()) * totalItemCount
- lvTop.getDividerHeight() - lvTop.getHeight();
int lvBotMovableRange = (cBot.getHeight() + lvBottom.getDividerHeight()) * totalItemCount
- lvBottom.getDividerHeight() - lvBottom.getHeight();
int lvTopPointer = (cTop.getHeight() + lvTop.getDividerHeight()) * firstVisibleItem
- cTop.getTop();
int lvBotPointer = (int)((float)(lvTopMovableRange - lvTopPointer)/lvTopMovableRange*lvBotMovableRange);
int setposition = lvBotPointer / (cBot.getHeight() + lvBottom.getDividerHeight());
int sety = -lvBotPointer + setposition * (cBot.getHeight() + lvBottom.getDividerHeight());
timestampScroll = System.currentTimeMillis();
lvBottom.setSelectionFromTop(setposition, sety);
lvTop.postDelayed(new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - timestampScroll > (SCROLL_DELAY - 10)) fInTopScrollListener = false;
}
}, SCROLL_DELAY);
}
}
};
AbsListView.OnScrollListener lBottom = new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (!fInTopScrollListener) {
fInBottomScrollListener = true;
View cTop = lvTop.getChildAt(0);
View cBot = lvBottom.getChildAt(0);
if (cTop == null || cBot == null) return;
int lvTopMovableRange = (cTop.getHeight() + lvTop.getDividerHeight()) * totalItemCount
- lvTop.getDividerHeight() - lvTop.getHeight();
int lvBotMovableRange = (cBot.getHeight() + lvBottom.getDividerHeight()) * totalItemCount
- lvBottom.getDividerHeight() - lvBottom.getHeight();
int lvBotPointer = (cBot.getHeight() + lvBottom.getDividerHeight()) * firstVisibleItem
- cBot.getTop();
int lvTopPointer = (int)((float)(lvBotMovableRange - lvBotPointer)/lvBotMovableRange*lvTopMovableRange);
int setposition = lvTopPointer / (cTop.getHeight() + lvTop.getDividerHeight());
int sety = -lvTopPointer + setposition * (cTop.getHeight() + lvTop.getDividerHeight());
timestampScroll = System.currentTimeMillis();
lvTop.setSelectionFromTop(setposition, sety);
lvBottom.postDelayed(new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - timestampScroll > (SCROLL_DELAY - 10)) fInBottomScrollListener = false;
}
}, SCROLL_DELAY);
}
}
};
希望对您有所帮助!