Android listView набирает количество пикселей, прокручиваемых
У меня есть listView. Когда я прокручиваю и останавливаюсь в определенном месте.
Как я могу получить количество пикселей, которые я прокрутил (сверху)?
Я попытался использовать get listView.getScrollY(), но он возвращает 0.
Ответы
Ответ 1
У меня была та же проблема.
Я не могу использовать View.getScrollY(), потому что он всегда возвращает 0, и я не могу использовать OnScrollListener.onScroll(...), потому что он работает с позициями не с пикселями. Я не могу подклассировать ListView и переопределить onScrollChanged (...), потому что его значения параметров всегда равны 0. Meh.
Все, что я хочу знать, это количество, за которое прокручиваются дети (т.е. содержимое списка) вверх или вниз. Поэтому я придумал решение. Я отслеживаю одного из детей (или вы можете сказать один из "строк" ) и следуйте его изменению по вертикали.
Вот код:
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()) отслеживаемого дочернего
- мы перестаем отслеживать ребенка, когда его небезопасно отслеживать (например, в тех случаях, когда он может быть использован повторно).
Ответ 2
Вы не можете получить пиксели из верхней части списка (потому что тогда вам нужно отобразить все виды сверху списка - может быть много элементов). Но вы можете получить пиксели первого видимого элемента: int pixels = listView.getChildAt(0).getTop();
он обычно будет нулевым или отрицательным числом - показывает разницу между вершиной listView и верхней частью первого представления в списке
Ответ 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://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;
}
}
оригинальный ответ:
Прежде всего, я хочу поблагодарить @zsolt-safrany за его ответ, это был отличный материал, полное признание для него.
Но потом я хочу представить свое улучшение в его ответе (все еще в значительной степени его ответ, всего несколько улучшений)
Улучшения:
-
Это отдельный тип класса жестов, который может быть добавлен к любому классу, который расширяет AbsListView
, вызывая .setOnScrollListener()
, поэтому это более гибкий подход.
-
Он использует изменение состояния прокрутки для предварительного выделения отслеживаемого дочернего элемента, поэтому он не "тратит" один onScroll
pass, чтобы выделить его позицию.
-
Он пересчитывает отслеживаемый ребенок на каждом проходе onScroll
, чтобы избежать пропущенного случайного прохода SSProll для пересчета дочернего элемента. (это может быть более эффективным путем кеширования некоторых высот и только пересчета после определенного количества прокрутки).
надеюсь, что это поможет
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
Попробуйте реализовать 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) {
}
});