Android: обнаружение, когда ScrollView прекращает прокрутку

Я использую ScrollView в Android и где видимая часть ScrollView имеет тот же размер, что и одна из ячеек внутри Scrollview . Каждая «ячейка» имеет одинаковую высоту. Так что я пытаюсь сделать, это защелкнуть позицию после ScrollView .

В настоящее время я обнаруживаю, когда пользователь коснулся ScrollView и когда они начали прокручивать и ScrollView его оттуда, но он довольно глючит. Он также должен работать, когда пользователь просто щелкает его, и он прокручивается, а затем замедляется.

На iPhone есть функция, которая как-то вроде didDecelerate и там я могу сделать любой код, который я хочу, когда ScrollView закончил прокрутку. Есть ли такая вещь с Android? Или есть какой-то код, на который я мог бы взглянуть, чтобы найти лучший способ сделать это?

Я просмотрел документы Android и не нашел ничего подобного.

Solutions Collecting From Web of "Android: обнаружение, когда ScrollView прекращает прокрутку"

Недавно мне пришлось реализовать описанную вами функцию. То, что я сделал, это проверить Runnable, если ScrollView остановил прокрутку, сравнив значение, возвращаемое getScrollY (), когда onTouchEvent сначала запускается со значением, возвращаемым после времени, определенного переменной newCheck .

См. Приведенный ниже код (рабочее решение):

 public class MyScrollView extends ScrollView{ private Runnable scrollerTask; private int initialPosition; private int newCheck = 100; private static final String TAG = "MyScrollView"; public interface OnScrollStoppedListener{ void onScrollStopped(); } private OnScrollStoppedListener onScrollStoppedListener; public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); scrollerTask = new Runnable() { public void run() { int newPosition = getScrollY(); if(initialPosition - newPosition == 0){//has stopped if(onScrollStoppedListener!=null){ onScrollStoppedListener.onScrollStopped(); } }else{ initialPosition = getScrollY(); MyScrollView.this.postDelayed(scrollerTask, newCheck); } } }; } public void setOnScrollStoppedListener(MyScrollView.OnScrollStoppedListener listener){ onScrollStoppedListener = listener; } public void startScrollerTask(){ initialPosition = getScrollY(); MyScrollView.this.postDelayed(scrollerTask, newCheck); } } 

Тогда у меня есть:

 scroll.setOnTouchListener(new OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { scroll.startScrollerTask(); } return false; } }); scroll.setOnScrollStoppedListener(new OnScrollStoppedListener() { public void onScrollStopped() { Log.i(TAG, "stopped"); } }); 

Кстати, я использовал несколько идей из других ответов, чтобы сделать это в своем приложении. Надеюсь это поможет. Любые вопросы, не стесняйтесь спрашивать. Приветствия.

Вот еще одно исправление ошибки IMHO, отсутствующей ошибки события OnEndScroll в ScrollView.

Его вдохновляет храбрый ответ. Просто отбросьте этот класс в свой проект (измените пакет, чтобы он соответствовал вашему) и используйте ниже xml

 package com.thecrag.components.ui; import android.content.Context; import android.util.AttributeSet; import android.widget.ScrollView; public class ResponsiveScrollView extends ScrollView { public interface OnEndScrollListener { public void onEndScroll(); } private boolean mIsFling; private OnEndScrollListener mOnEndScrollListener; public ResponsiveScrollView(Context context) { this(context, null, 0); } public ResponsiveScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ResponsiveScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void fling(int velocityY) { super.fling(velocityY); mIsFling = true; } @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY); if (mIsFling) { if (Math.abs(y - oldY) < 2 || y >= getMeasuredHeight() || y == 0) { if (mOnEndScrollListener != null) { mOnEndScrollListener.onEndScroll(); } mIsFling = false; } } } public OnEndScrollListener getOnEndScrollListener() { return mOnEndScrollListener; } public void setOnEndScrollListener(OnEndScrollListener mOnEndScrollListener) { this.mOnEndScrollListener = mOnEndScrollListener; } } 

Снова изменив имя пакета в соответствии с вашим проектом

 <com.thecrag.components.ui.ResponsiveScrollView android:id="@+id/welcome_scroller" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/welcome_scroll_command_help_container" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_below="@+id/welcome_header_text_thecrag" android:layout_margin="6dp"> .... </com.thecrag.components.ui.ResponsiveScrollView> 

Я подклассифицировал (горизонтально) ScrollView и сделал что-то вроде этого:

 @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { if (Math.abs(x - oldX) > SlowDownThreshold) { currentlyScrolling = true; } else { currentlyScrolling = false; if (!currentlyTouching) { //scrolling stopped...handle here } } super.onScrollChanged(x, y, oldX, oldY); } 

Я использовал значение 1 для SlowDownThreshold, поскольку он всегда кажется отличием последнего события onScrollChanged.

Чтобы сделать это правильно, при медленном перемещении, я должен был сделать это:

 @Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: currentlyTouching = true; } return super.onInterceptTouchEvent(event); } @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: currentlyTouching = false; if (!currentlyScrolling) { //I handle the release from a drag here return true; } } return false; } 

Мой подход определяет состояние прокрутки с помощью временной метки, измененной каждый раз, когда вызывается onScrollChanged (). Очень легко определить, когда начинается и заканчивается прокрутка. Вы также можете изменить порог (я использую 100 мс) для фиксации чувствительности.

 public class CustomScrollView extends ScrollView { private long lastScrollUpdate = -1; private class ScrollStateHandler implements Runnable { @Override public void run() { long currentTime = System.currentTimeMillis(); if ((currentTime - lastScrollUpdate) > 100) { lastScrollUpdate = -1; onScrollEnd(); } else { postDelayed(this, 100); } } } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (lastScrollUpdate == -1) { onScrollStart(); postDelayed(new ScrollStateHandler(), 100); } lastScrollUpdate = System.currentTimeMillis(); } private void onScrollStart() { // do something } private void onScrollEnd() { // do something } } 

Мой подход к этому вопросу заключается в использовании таймера для проверки следующих 2 «событий».

1) onScrollChanged () перестает быть вызванным

2) Палец пользователя поднимается из прокрутки

 public class CustomScrollView extends HorizontalScrollView { public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); } Timer ntimer = new Timer(); MotionEvent event; @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { checkAgain(); super.onScrollChanged(l, t, oldl, oldt); } public void checkAgain(){ try{ ntimer.cancel(); ntimer.purge(); } catch(Exception e){} ntimer = new Timer(); ntimer.schedule(new TimerTask() { @Override public void run() { if(event.getAction() == MotionEvent.ACTION_UP){ // ScrollView Stopped Scrolling and Finger is not on the ScrollView } else{ // ScrollView Stopped Scrolling But Finger is still on the ScrollView checkAgain(); } } },100); } @Override public boolean onTouchEvent(MotionEvent event) { this.event = event; return super.onTouchEvent(event); } } 

Я думаю, что это произошло в прошлом. AFAIK, вы не можете легко это обнаружить. Мое предложение состоит в том, что вы посмотрите на ScrollView.java (так мы делаем вещи на Android- ScrollView.java 🙂 ) и ScrollView.java , как вы можете расширить класс, чтобы обеспечить функциональность, которую вы ищете. Это то, что я постараюсь первым:

  @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { if (mScroller.isFinished()) { // do something, for example call a listener } } 

Вот еще одно решение, довольно простое и чистое, на мой взгляд, естественно вдохновленное ответами выше. В основном, когда пользователь завершил проверку жестов, если getScrollY () все еще меняется после небольшой задержки (здесь 50 мс).

 public class ScrollViewWithOnStopListener extends ScrollView { OnScrollStopListener listener; public interface OnScrollStopListener { void onScrollStopped(int y); } public ScrollViewWithOnStopListener(Context context) { super(context); } public ScrollViewWithOnStopListener(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_UP: checkIfScrollStopped(); } return super.onTouchEvent(ev); } int initialY = 0; private void checkIfScrollStopped() { initialY = getScrollY(); this.postDelayed(new Runnable() { @Override public void run() { int updatedY = getScrollY(); if (updatedY == initialY) { //we've stopped if (listener != null) { listener.onScrollStopped(getScrollY()); } } else { initialY = updatedY; checkIfScrollStopped(); } } }, 50); } public void setOnScrollStoppedListener(OnScrollStopListener yListener) { listener = yListener; } } 

Попробуйте взглянуть на этот вопрос здесь, на StackOverflow – это не совсем то же самое, что и ваш вопрос, но он дает представление о том, как вы можете управлять событием прокрутки ScrollView .

В основном вам нужно создать свой собственный CustomScrollView , расширив ScrollView и переопределив onScrollChanged(int x, int y, int oldx, int oldy) . Затем вам нужно ссылаться на это в своем файле макета вместо стандартного ScrollView например com.mypackage.CustomScrollView .

Для простого случая, как вы описали, вы, вероятно, можете уйти с переопределяющим методом fling в своем пользовательском представлении прокрутки. Метод Fling вызывается для выполнения «замедления» каждый раз, когда пользователь поднимает палец с экрана.

Итак, что вы должны сделать, это примерно так:

  1. Подкласс ScrollView.

     public class MyScrollView extends ScrollView { private Scroller scroller; private Runnable scrollerTask; //... public MyScrollView(Context context, AttributeSet attrs) { super(context, attrs); scroller = new Scroller(getContext()); //or OverScroller for 3.0+ scrollerTask = new Runnable() { @Override public void run() { scroller.computeScrollOffset(); scrollTo(0, scroller.getCurrY()); if (!scroller.isFinished()) { MyScrollView.this.post(this); } else { //deceleration ends here, do your code } } }; //... } } 
  2. Метод подкласса и НЕ НАЙДИТЕ реализацию суперкласса.

     @Override public void fling(int velocityY) { scroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0, container.getHeight()); post(scrollerTask); //add any extra functions you need from android source code: //show scroll bars //change focus //etc. } 
  3. Fling не будет срабатывать, если пользователь перестанет прокручиваться, прежде чем поднимать палец (speedY == 0). Если вы хотите перехватить подобные события, переопределите onTouchEvent.

     @Override public boolean onTouchEvent(MotionEvent ev) { boolean eventConsumed = super.onTouchEvent(ev); if (eventConsumed && ev.getAction() == MotionEvent.ACTION_UP) { if (scroller.isFinished()) { //do your code } } return eventConsumed; } 

ПРИМЕЧАНИЕ Хотя это и работает, переопределение метода fling может быть плохой идеей. Он публичный, но его едва ли предназначен для подкласса. Сейчас он делает 3 вещи – он инициирует запуск для частного mScroller, обрабатывает возможные изменения фокуса и показывает полосы прокрутки. Это может измениться в будущем выпуске Android. Например, частный экземпляр mScroller изменил свой класс с Scroller на OvershootScroller между 2.3 и 3.0. Вы должны иметь в виду все эти небольшие различия. В любом случае будьте готовы к непредвиденным последствиям в будущем.

Я сделал некоторые улучшения в ответе ZeroG. Главным образом отмена вызовов избыточной задачи и реализация всего этого как частного OnTouchListener, поэтому весь код обнаружения прокрутки будет находиться в одном месте.

Вставьте следующий код в свою собственную реализацию ScrollView:

 private class ScrollFinishHandler implements OnTouchListener { private static final int SCROLL_TASK_INTERVAL = 100; private Runnable mScrollerTask; private int mInitialPosition = 0; public ScrollFinishHandler() { mScrollerTask = new Runnable() { public void run() { int newPosition = getScrollY(); if(mInitialPosition - newPosition == 0) {//has stopped onScrollStopped(); // Implement this on your main ScrollView class }else{ mInitialPosition = getScrollY(); ExpandingLinearLayout.this.postDelayed(mScrollerTask, SCROLL_TASK_INTERVAL); } } }; } @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { startScrollerTask(); } else { stopScrollerTask(); } return false; } } 

А затем в вашей реализации ScrollView:

 setOnTouchListener( new ScrollFinishHandler() ); 

Здесь есть несколько отличных ответов, но мой код может обнаруживать, когда прокрутка останавливается без расширения класса ScrollView. Каждый экземпляр представления может вызвать getViewTreeObserver (). При удерживании этого экземпляра ViewTreeObserver вы можете добавить OnScrollChangedListener, используя функцию addOnScrollChangedListener ().

Объявите следующее:

 private ScrollView scrollListener; private volatile long milesec; private Handler scrollStopDetector; private Thread scrollcalled = new Thread() { @Override public void run() { if (System.currentTimeMillis() - milesec > 200) { //scroll stopped - put your code here } } }; 

И в вашем onCreate (или другом месте) добавьте:

  scrollListener = (ScrollView) findViewById(R.id.scroll); scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { milesec = System.currentTimeMillis(); scrollStopDetector.postDelayed(scrollcalled, 200); } }); 

Вам может потребоваться более длительное или медленное время между этими проверками, но при прокрутке этого списка вызывается очень быстро, поэтому он будет работать очень быстро.

Вот мое решение, которое включает отслеживание прокрутки и завершение прокрутки:

 public class ObservableHorizontalScrollView extends HorizontalScrollView { public interface OnScrollListener { public void onScrollChanged(ObservableHorizontalScrollView scrollView, int x, int y, int oldX, int oldY); public void onEndScroll(ObservableHorizontalScrollView scrollView); } private boolean mIsScrolling; private boolean mIsTouching; private Runnable mScrollingRunnable; private OnScrollListener mOnScrollListener; public ObservableHorizontalScrollView(Context context) { this(context, null, 0); } public ObservableHorizontalScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ObservableHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.getAction(); if (action == MotionEvent.ACTION_MOVE) { mIsTouching = true; mIsScrolling = true; } else if (action == MotionEvent.ACTION_UP) { if (mIsTouching && !mIsScrolling) { if (mOnScrollListener != null) { mOnScrollListener.onEndScroll(this); } } mIsTouching = false; } return super.onTouchEvent(ev); } @Override protected void onScrollChanged(int x, int y, int oldX, int oldY) { super.onScrollChanged(x, y, oldX, oldY); if (Math.abs(oldX - x) > 0) { if (mScrollingRunnable != null) { removeCallbacks(mScrollingRunnable); } mScrollingRunnable = new Runnable() { public void run() { if (mIsScrolling && !mIsTouching) { if (mOnScrollListener != null) { mOnScrollListener.onEndScroll(ObservableHorizontalScrollView.this); } } mIsScrolling = false; mScrollingRunnable = null; } }; postDelayed(mScrollingRunnable, 200); } if (mOnScrollListener != null) { mOnScrollListener.onScrollChanged(this, x, y, oldX, oldY); } } public OnScrollListener getOnScrollListener() { return mOnScrollListener; } public void setOnScrollListener(OnScrollListener mOnEndScrollListener) { this.mOnScrollListener = mOnEndScrollListener; } } 

Мое решение является разновидностью отличного решения Lin Yu Cheng, а также обнаруживает, что прокрутка началась и остановилась.

Шаг 1. Определите HorizontalScrollView и OnScrollChangedListener:

 CustomHorizontalScrollView scrollView = (CustomHorizontalScrollView) findViewById(R.id.horizontalScrollView); horizontalScrollListener = new CustomHorizontalScrollView.OnScrollChangedListener() { @Override public void onScrollStart() { // Scrolling has started. Insert your code here... } @Override public void onScrollEnd() { // Scrolling has stopped. Insert your code here... } }; scrollView.setOnScrollChangedListener(horizontalScrollListener); 

Шаг 2. Добавьте класс CustomHorizontalScrollView:

 public class CustomHorizontalScrollView extends HorizontalScrollView { public interface OnScrollChangedListener { // Developer must implement these methods. void onScrollStart(); void onScrollEnd(); } private long lastScrollUpdate = -1; private int scrollTaskInterval = 100; private Runnable mScrollingRunnable; public OnScrollChangedListener mOnScrollListener; public CustomHorizontalScrollView(Context context) { this(context, null, 0); init(context); } public CustomHorizontalScrollView(Context context, AttributeSet attrs) { this(context, attrs, 0); init(context); } public CustomHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { // Check for scrolling every scrollTaskInterval milliseconds mScrollingRunnable = new Runnable() { public void run() { if ((System.currentTimeMillis() - lastScrollUpdate) > scrollTaskInterval) { // Scrolling has stopped. lastScrollUpdate = -1; //CustomHorizontalScrollView.this.onScrollEnd(); mOnScrollListener.onScrollEnd(); } else { // Still scrolling - Check again in scrollTaskInterval milliseconds... postDelayed(this, scrollTaskInterval); } } }; } public void setOnScrollChangedListener(OnScrollChangedListener onScrollChangedListener) { this.mOnScrollListener = onScrollChangedListener; } public void setScrollTaskInterval(int scrollTaskInterval) { this.scrollTaskInterval = scrollTaskInterval; } //void onScrollStart() { // System.out.println("Scroll started..."); //} //void onScrollEnd() { // System.out.println("Scroll ended..."); //} @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (mOnScrollListener != null) { if (lastScrollUpdate == -1) { //CustomHorizontalScrollView.this.onScrollStart(); mOnScrollListener.onScrollStart(); postDelayed(mScrollingRunnable, scrollTaskInterval); } lastScrollUpdate = System.currentTimeMillis(); } } } 
  this.getListView().setOnScrollListener(new OnScrollListener(){ @Override public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if( firstVisibleItem + visibleItemCount >= totalItemCount ) // Last item is shown... } 

Надеюсь, что этот сниппет поможет 🙂