Niedawno musiałem zaimplementować opisaną przez Ciebie funkcję. To, co zrobiłem, to sprawdzenie Runnable, czy ScrollView przestał przewijać, porównując wartość zwróconą przez getScrollY (), gdy onTouchEvent jest po raz pierwszy wyzwalany z wartością zwracaną po czasie zdefiniowanym przez zmienną newCheck .
Zobacz kod poniżej (działające rozwiązanie):
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){
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);
}
}
Wtedy ja mam:
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");
}
});
Przy okazji, wykorzystałem kilka pomysłów z innych odpowiedzi, aby to zrobić w mojej aplikacji. Mam nadzieję że to pomoże. Wszelkie pytania zachęcamy do zadawania. Twoje zdrowie.
Oto kolejna poprawka IMHO, brakujący błąd zdarzenia OnEndScroll w ScrollView.
Inspiruje go hałaśliwa odpowiedź. Po prostu upuść tę klasę do swojego projektu (zmień pakiet na swój własny) i użyj poniższego 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; } }
ponownie zmieniając nazwę pakietu, aby pasowała do twojego projektu
<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>
źródło
2
aby0.5f
odzwierciedlać oryginałScrollView
„sMAX_SCROLL_FACTOR
wartość.Podklasowałem (Horizontal) ScrollView i zrobiłem coś takiego:
@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); }
Użyłem wartości 1 dla SlowDownThreshold, ponieważ zawsze wydaje się, że jest to różnica ostatniego zdarzenia onScrollChanged.
Aby to zachowywało się poprawnie podczas powolnego przeciągania, musiałem zrobić to:
@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; }
źródło
Moje podejście polega na określeniu stanu przewijania za pomocą znacznika czasu zmienianego za każdym razem, gdy wywoływana jest metoda onScrollChanged (). Bardzo łatwo jest określić, kiedy zaczyna się i kończy przewijanie. Możesz także zmienić próg (używam 100 ms), aby naprawić czułość.
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 } }
źródło
Oto kolejne rozwiązanie, moim zdaniem dość proste i przejrzyste, naturalnie inspirowane powyższymi odpowiedziami. Zasadniczo po zakończeniu gestu użytkownika sprawdź, czy getScrollY () nadal się zmienia, po krótkim opóźnieniu (tutaj 50 ms).
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; } }
źródło
Moje podejście do tego pytania polega na użyciu licznika czasu, aby sprawdzić następujące 2 „zdarzenia”.
1) onScrollChanged () przestał być wywoływany
2) Palec użytkownika zostaje podniesiony z widoku przewijania
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); } }
źródło
Moje rozwiązanie jest odmianą świetnego rozwiązania Lin Yu Chenga i wykrywa również rozpoczęcie i zatrzymanie przewijania.
Krok 1. Zdefiniuj HorizontalScrollView i 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);
Krok 2. Dodaj klasę 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(); } } }
źródło
Spróbuj rzucić okiem na to pytanie w StackOverflow - nie jest dokładnie takie samo jak twoje pytanie, ale daje wyobrażenie o tym, jak możesz zarządzać zdarzeniem przewijania pliku
ScrollView
.Po prostu musisz stworzyć własny
CustomScrollView
, rozszerzającScrollView
i zastępująconScrollChanged(int x, int y, int oldx, int oldy)
. Następnie musisz odwołać się do tego w swoim pliku układu zamiast do standardowego,ScrollView
takiego jakcom.mypackage.CustomScrollView
.źródło
W przypadku prostego przypadku, takiego jak opisany, prawdopodobnie możesz uciec z nadpisaniem metody rzucania w niestandardowym widoku przewijania. Metoda Fling jest wywoływana w celu wykonania „spowolnienia” za każdym razem, gdy użytkownik podniesie palec z ekranu.
Więc co powinieneś zrobić, to coś takiego:
Podklasa 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 } } }; //... } }
Podklasy odrzucania metody i NIE wywołuj implementacji nadklasy.
@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. }
Rzut nie zostanie uruchomiony, jeśli użytkownik przestanie przewijać przed podniesieniem palca (prędkość Y == 0). Jeśli chcesz przechwytywać również tego rodzaju zdarzenia, zastąp 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; }
UWAGA Chociaż to działa, nadpisywanie metody rzutowania może być złym pomysłem. Jest publiczny, ale ledwo jest przeznaczony do tworzenia podklas. W tej chwili robi 3 rzeczy - inicjuje rzut do prywatnego mScrollera, obsługuje ewentualne zmiany fokusa i pokazuje paski przewijania. Może się to zmienić w przyszłej wersji Androida. Na przykład prywatna instancja mScroller zmieniła swoją klasę z Scroller na OvershootScroller z 2.3 do 3.0. Musisz pamiętać o tych wszystkich małych różnicach. W każdym razie bądź przygotowany na nieprzewidziane konsekwencje w przyszłości.
źródło
Jest tutaj kilka świetnych odpowiedzi, ale mój kod może wykryć zatrzymanie przewijania bez konieczności rozszerzania klasy ScrollView. każda instancja widoku może wywołać metodę getViewTreeObserver (). Trzymając to wystąpienie ViewTreeObserver, możesz dodać OnScrollChangedListener za pomocą funkcji addOnScrollChangedListener ().
oświadczam, co następuje:
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 } } };
aw swoim onCreate (lub innym miejscu) dodaj:
scrollListener = (ScrollView) findViewById(R.id.scroll); scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { milesec = System.currentTimeMillis(); scrollStopDetector.postDelayed(scrollcalled, 200); } });
możesz potrzebować dłuższego lub wolniejszego czasu między tymi sprawdzeniami, ale podczas przewijania ten listner jest wywoływany bardzo szybko, więc będzie działał bardzo szybko.
źródło
Oto moje rozwiązanie, które obejmuje śledzenie przewijania i zakończenie przewijania:
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; } }
źródło
Myślę, że to wydarzyło się w przeszłości. AFAIK, nie możesz tego łatwo wykryć. Moja sugestia jest taka, abyś przyjrzał się temu
ScrollView.java
(w ten sposób robimy rzeczy w Androidzie :)) i wymyślił, jak możesz rozszerzyć klasę, aby zapewnić funkcjonalność, której szukasz. Oto, czego spróbuję najpierw:@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { if (mScroller.isFinished()) { // do something, for example call a listener } }
źródło
onScrollChanged
,scrollTo
lub jakakolwiek inna metoda przewijania związane. Alternatywnie możesz po prostu skopiować całyScrollView
kod i utworzyć to poleprotected
. Nie polecałbym tego jednak.to jest stary wątek, ale chciałbym dodać krótsze rozwiązanie, które wymyśliłem:
buttonsScrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> handler.removeCallbacksAndMessages(null) handler.postDelayed({ //YOUR CODE TO BE EXECUTED HERE },1000) }
Naturalnie występuje opóźnienie 1000 milisekund. Dostosuj to, jeśli potrzebujesz.
źródło
Wprowadziłem kilka ulepszeń w odpowiedzi ZeroG. Głównie anulowanie nadmiernych wywołań zadań i zaimplementowanie całości jako prywatnego OnTouchListener, aby cały kod wykrywania przewijania był w jednym miejscu.
Wklej następujący kod do własnej implementacji 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; } }
A następnie w Twojej implementacji ScrollView:
setOnTouchListener( new ScrollFinishHandler() );
źródło
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... }
Mam nadzieję, że fragment pomoże :)
źródło