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查找滚动的像素量的主要内容,如果未能解决你的问题,请参考以下文章

如何获取滚动视图的滚动量

停止 ListView 滚动动画

Android ListView scrollby 剪辑列表中的项目

获取和设置 winForms ListView 滚动位置

解决RecyclerView&ListView滚动到顶部或者底部边界出现阴影问题

Android - 在 ListView 中滚动时滚动出/折叠元素