Android listView查找滚动的像素量
Posted
技术标签:
【中文标题】Android listView查找滚动的像素量【英文标题】:Android listView find the amount of pixels scrolled 【发布时间】:2011-12-12 07:20:04 【问题描述】:我有一个列表视图。当我滚动并停在特定位置时。
如何获取我滚动的像素数量(从顶部)?
我曾尝试使用 get listView.getScrollY(),但它返回 0。
【问题讨论】:
这里是how 【参考方案1】:我遇到了同样的问题。
我不能使用 View.getScrollY() 因为它总是返回 0 并且我不能使用 OnScrollListener.onScroll(...) 因为它适用于位置而不是像素。我不能继承 ListView 并覆盖 onScrollChanged(...),因为它的参数值始终为 0。嗯。
我只想知道孩子(即列表视图的内容)向上或向下滚动的数量。所以我想出了一个解决方案。我跟踪其中一个孩子(或者你可以说是“行”之一)并跟踪它的垂直位置变化。
代码如下:
public class ObservableListView extends ListView
public static interface ListViewObserver
public void onScroll(float deltaY);
private ListViewObserver mObserver;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public ObservableListView(Context context, AttributeSet attrs)
super(context, attrs);
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt)
super.onScrollChanged(l, t, oldl, oldt);
if (mTrackedChild == null)
if (getChildCount() > 0)
mTrackedChild = getChildInTheMiddle();
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = getPositionForView(mTrackedChild);
else
boolean childIsSafeToTrack = mTrackedChild.getParent() == this && getPositionForView(mTrackedChild) == mTrackedChildPrevPosition;
if (childIsSafeToTrack)
int top = mTrackedChild.getTop();
if (mObserver != null)
float deltaY = top - mTrackedChildPrevTop;
mObserver.onScroll(deltaY);
mTrackedChildPrevTop = top;
else
mTrackedChild = null;
private View getChildInTheMiddle()
return getChildAt(getChildCount() / 2);
public void setObserver(ListViewObserver observer)
mObserver = observer;
几个笔记:
我们重写 onScrollChanged(...) 因为它在列表视图滚动时被调用(只是它的参数没用) 然后我们从中间选择一个孩子(行)(不必正好是中间的孩子) 每次滚动发生时,我们都会根据被跟踪子项的先前位置 (getTop()) 计算垂直移动 当孩子被跟踪不安全时(例如可能被重复使用),我们会停止跟踪孩子【讨论】:
如果添加速度计算(例如,delta_pos/delta_time),请记住,当滚动停止时,视图会停止调用 onScrollChanged,因此在最后一次滚动调用和下一个的第一个电话;只是忽略 if(delta_time 我使用了你的方式,但最初它给了我 0 值你能告诉我为什么会发生这种情况我也在使用 PullToRefresh 所以你能指导我吗 listview初始化时会调用onScroll
监听器,不是使用的触摸【参考方案2】:
您无法从列表顶部获取像素(因为您需要从列表顶部布局所有视图 - 可能有很多项目)。但是您可以获得第一个可见项目的像素:int pixels = listView.getChildAt(0).getTop();
它通常为零或负数 - 显示 listView 顶部和列表中第一个视图顶部之间的差异
【讨论】:
感谢 Jin35,这让我可以使用第一个可见位置和子高度找到偏移量。【参考方案3】:编辑:
我在这门课上进行了改进,以避免由于视图太大而导致赛道丢失的一些时刻,并且没有正确获得getTop()
这个新的解决方案使用 4 个跟踪点:
第一个孩子,底部 中间孩子,顶部 中间孩子,底部 最后一个孩子,顶部这确保我们总是有一个 isSafeToTrack
等于 true
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://***.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener
private final PixelScrollListener listener;
private TrackElement[] trackElements =
new TrackElement(0), // top view, bottom Y
new TrackElement(1), // mid view, bottom Y
new TrackElement(2), // mid view, top Y
new TrackElement(3);// bottom view, top Y
public PixelScrollDetector(PixelScrollListener listener)
this.listener = listener;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING)
for (TrackElement t : trackElements)
t.syncState(view);
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
boolean wasTracked = false;
for (TrackElement t : trackElements)
if (!wasTracked)
if (t.isSafeToTrack(view))
wasTracked = true;
if (listener != null)
listener.onScroll(view, t.getDeltaY());
t.syncState(view);
else
t.reset();
else
t.syncState(view);
public static interface PixelScrollListener
public void onScroll(AbsListView view, float deltaY);
private static class TrackElement
private final int position;
private TrackElement(int position)
this.position = position;
void syncState(AbsListView view)
if (view.getChildCount() > 0)
trackedChild = getChild(view);
trackedChildPrevTop = getY();
trackedChildPrevPosition = view.getPositionForView(trackedChild);
void reset()
trackedChild = null;
boolean isSafeToTrack(AbsListView view)
return (trackedChild != null) &&
(trackedChild.getParent() == view) && (view.getPositionForView(trackedChild) == trackedChildPrevPosition);
int getDeltaY()
return getY() - trackedChildPrevTop;
private View getChild(AbsListView view)
switch (position)
case 0:
return view.getChildAt(0);
case 1:
case 2:
return view.getChildAt(view.getChildCount() / 2);
case 3:
return view.getChildAt(view.getChildCount() - 1);
default:
return null;
private int getY()
if (position <= 1)
return trackedChild.getBottom();
else
return trackedChild.getTop();
View trackedChild;
int trackedChildPrevPosition;
int trackedChildPrevTop;
原答案:
首先我要感谢 @zsolt-safrany 的回答,这真是太棒了,对他表示敬意。
但是我想介绍我对他的答案的改进(仍然是他的答案,只是一些改进)
改进:
这是一个单独的“手势检测器”类型的类,可以通过调用.setOnScrollListener()
添加到任何扩展AbsListView
的类中,因此它是一种更灵活的方法。
它使用滚动状态的变化来预先分配被跟踪的孩子,所以它不会“浪费”一个onScroll
传递来分配它的位置。
它会在每个onScroll
传递中重新计算被跟踪的孩子,以避免丢失随机的 onScroll 传递来重新计算孩子。 (这可以通过缓存一些高度来提高效率,并且只在一定量的滚动后重新计算)。
希望对你有帮助
import android.view.View;
import android.widget.AbsListView;
/**
* Created by budius on 16.05.14.
* This improves on Zsolt Safrany answer on stack-overflow (see link)
* by making it a detector that can be attached to any AbsListView.
* http://***.com/questions/8471075/android-listview-find-the-amount-of-pixels-scrolled
*/
public class PixelScrollDetector implements AbsListView.OnScrollListener
private final PixelScrollListener listener;
private View mTrackedChild;
private int mTrackedChildPrevPosition;
private int mTrackedChildPrevTop;
public PixelScrollDetector(PixelScrollListener listener)
this.listener = listener;
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
// init the values every time the list is moving
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL ||
scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING)
if (mTrackedChild == null)
syncState(view);
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
if (mTrackedChild == null)
// case we don't have any reference yet, try again here
syncState(view);
else
boolean childIsSafeToTrack = (mTrackedChild.getParent() == view) && (view.getPositionForView(mTrackedChild) == mTrackedChildPrevPosition);
if (childIsSafeToTrack)
int top = mTrackedChild.getTop();
if (listener != null)
float deltaY = top - mTrackedChildPrevTop;
listener.onScroll(view, deltaY);
// re-syncing the state make the tracked child change as the list scrolls,
// and that gives a much higher true state for `childIsSafeToTrack`
syncState(view);
else
mTrackedChild = null;
private void syncState(AbsListView view)
if (view.getChildCount() > 0)
mTrackedChild = getChildInTheMiddle(view);
mTrackedChildPrevTop = mTrackedChild.getTop();
mTrackedChildPrevPosition = view.getPositionForView(mTrackedChild);
private View getChildInTheMiddle(AbsListView view)
return view.getChildAt(view.getChildCount() / 2);
public static interface PixelScrollListener
public void onScroll(AbsListView view, float deltaY);
【讨论】:
让我纳闷:为什么 Android 的开发者不直接在 API 中为应用创建者提供这样的功能呢?他们为什么不默认公开 ScrollView 的 onScrollChanged() 方法? 我不知道。我们必须使用的东西真的很糟糕,但如果他们现在改变,他们会迟到 5 年。也许在支持库中发布一些东西。无论如何,只是为了让您知道我用更好的解决方案编辑了答案。 这是一段很棒的代码,但是我想问一下,是否可以使用您的代码检测listView的过度滚动量? 我不会再建议任何人使用此代码。 ListView 是一个编码不佳的小部件,上面的代码是对其局限性的破解。现在我们有了更好和改进的基于适配器的小部件RecyclerView
。在那里你可以直接拨打addOnScrollListener(l);
【参考方案4】:
尝试实现OnScrollListener:
list.setOnScrollListener(new OnScrollListener()
@Override
public void onScrollStateChanged(AbsListView view, int scrollState)
int last = view.getLastVisiblePosition();
break;
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount)
);
【讨论】:
view.getLastVisiblePosition().. 返回位置。有没有办法获取像素? @Jojo 为什么要获取像素而不是位置? 如果列表视图中的项目滚动了一半,那么我想要滚动的数量。 @PareshMayani 如果 listview 滚动 5px 或 10 px 那么我想将 headerView 添加到 listview 其他行为类似于 PullToRefresh。你能指导我吗以上是关于Android listView查找滚动的像素量的主要内容,如果未能解决你的问题,请参考以下文章
Android ListView scrollby 剪辑列表中的项目