Android listView查找滚动的像素数量

Shi*_*own 12 android scroll

我有一个listView.当我滚动并停在特定的地方.

如何获得滚动的像素数量(从顶部开始)?

我尝试过使用get listView.getScrollY(),但它返回0.

Zso*_*any 19

我有同样的问题.

我不能使用View.getScrollY()因为它总是返回0而我不能使用OnScrollListener.onScroll(...),因为它适用于不带像素的位置.我无法继承ListView并覆盖onScrollChanged(...),因为它的参数值始终为0. Meh.

我想知道的是孩子(即listview的内容)向上或向下滚动的数量.所以我想出了一个解决方案.我跟踪其中一个孩子(或者你可以说其中一个"行")并跟随其垂直位置变化.

这是代码:

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;
}

}
Run Code Online (Sandbox Code Playgroud)

几个笔记:

  • 我们重写onScrollChanged(...)因为它在滚动列表视图时被调用(只是它的参数是无用的)
  • 然后我们从中间选择一个孩子(排)(不一定是中间的孩子)
  • 每次滚动发生时,我们都会根据被跟踪孩子的先前位置(getTop())计算垂直移动
  • 当一个孩子被跟踪不安全时(例如,在可能被重用的情况下),我们会停止跟踪


Jin*_*n35 7

你不能从列表顶部获得像素(因为那时你需要从列表顶部布局所有视图 - 可能有很多项目).但是你可以获得第一个可见项目的像素:int pixels = listView.getChildAt(0).getTop();它通常为零或负数 - 显示listView顶部和列表中第一个视图顶部之间的差异


Bud*_*ius 5

编辑: 我在这堂课上有所改进,以避免由于视野太大而且没有正确获得音轨而导致音轨丢失的一些时刻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://stackoverflow.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;
   }
}
Run Code Online (Sandbox Code Playgroud)

原始答案:

首先,我要感谢@ zsolt-safrany的回答,这对我来说是件好事,对他来说是完全的荣誉.

但后来我想在他的答案上提出我的改进(仍然是他的答案,只是一些改进)

改进:

  • 它是一个单独的"手势检测器"类,可以添加到AbsListView通过调用扩展的任何类中.setOnScrollListener(),因此它是一种更灵活的方法.

  • 它使用滚动状态的变化来预先分配被跟踪的子项,因此它不会"浪费"一次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://stackoverflow.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);
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 让我想知道:Android的开发人员为什么不在API中为应用创建者提供这样的功能?为什么他们默认不将ScrollView的onScrollChanged()方法公开? (2认同)