Android listView find the amount of pixels scrolle

2019-04-19 15:04发布

问题:

I have a listView. When I scroll and stops in a particular place.

How can I get the amount of pixels I scrolled(from top)?

I have tried using get listView.getScrollY(), but it returns 0.

回答1:

I had the same problem.

I cannot use View.getScrollY() because it always returns 0 and I cannot use OnScrollListener.onScroll(...) because it works with positions not with pixels. I cannot subclass ListView and override onScrollChanged(...) because its parameter values are always 0. Meh.

All I want to know is the amount the children (i.e. content of listview) got scrolled up or down. So I came up with a solution. I track one of the children (or you can say one of the "rows") and follow its vertical position change.

Here is the code:

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

}

Couple of notes:

  • we override onScrollChanged(...) because it gets called when the listview is scrolled (just its parameters are useless)
  • then we choose a child (row) from the middle (doesn't have to be precisely the child in the middle)
  • every time scrolling happens we calculate vertical movement based on previous position (getTop()) of tracked child
  • we stop tracking a child when it is not safe to be tracked (e.g. in cases where it might got reused)


回答2:

You cant get pixels from top of list (because then you need to layout all views from top of list - there can be a lot of items). But you can get pixels of first visible item: int pixels = listView.getChildAt(0).getTop(); it generally will be zero or negative number - shows difference between top of listView and top of first view in list



回答3:

edit: I've improved in this class to avoid some moments that the track was losing due to views being too big and not properly getting a getTop()

This new solution uses 4 tracking points:

  • first child, bottom
  • middle child, top
  • middle child, bottom
  • last child, top

that makes sure we always have a isSafeToTrack equals to 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;
   }
}

original answer:

First I want to thank @zsolt-safrany for his answer, that was great stuff, total kudos for him.

But then I want to present my improvement on his answer (still is pretty much his answer, just a few improvements)

Improvements:

  • It's a separate "gesture detector" type of class that can be added to any class that extends AbsListView by calling .setOnScrollListener(), so it's a more flexible approach.

  • It's using the change in scroll state to pre-allocate the tracked child, so it doesn't "waste" one onScroll pass to allocate its position.

  • It re-calculate the tracked child on every onScroll pass to avoiding missing random onScroll pass to recalculate child. (this could be make more efficient by caching some heights and only re-calculate after certain amount of scroll).

hope it helps

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


回答4:

Try to implement 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) {
            }
        });