Recientemente tuve que implementar la función que describiste. Lo que hice fue tener un Runnable comprobando si ScrollView había dejado de desplazarse comparando el valor devuelto por getScrollY () cuando onTouchEvent se activa por primera vez con el valor devuelto después de un tiempo definido por la variable newCheck .
Vea el código a continuación (solución de trabajo):
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);
}
}
Luego tengo:
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");
}
});
Por cierto, utilicé algunas ideas de otras respuestas para hacer esto en mi aplicación. Espero que esto ayude. Cualquier pregunta no dude en preguntar. Salud.
Aquí hay otra solución para, en mi humilde opinión, el error del evento OnEndScroll faltante en ScrollView.
Está inspirado en una respuesta ambigua . Simplemente coloque esta clase en su proyecto (cambie el paquete para que coincida con el suyo) y use el siguiente 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; } }
nuevamente cambiando el nombre del paquete para que coincida con su proyecto
<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>
fuente
2
para0.5f
reflejar el originalScrollView
s'MAX_SCROLL_FACTOR
valor.Subclasé (Horizontal) ScrollView e hice algo como esto:
@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); }
Usé un valor de 1 para SlowDownThreshold ya que siempre parece ser la diferencia del último evento onScrollChanged.
Para que esto se comporte correctamente al arrastrar lentamente, tuve que hacer esto:
@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; }
fuente
Mi enfoque es determinar el estado de desplazamiento mediante una marca de tiempo que cambia cada vez que se llama a onScrollChanged (). Es muy fácil determinar cuándo comienza y cuándo termina el desplazamiento. También puede cambiar el umbral (yo uso 100 ms) para corregir la sensibilidad.
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 } }
fuente
Aquí hay otra solución, bastante simple y limpia en mi opinión, naturalmente inspirada en las respuestas anteriores. Básicamente, una vez que el usuario finalizó el gesto, verifique si getScrollY () todavía está cambiando, después de un breve retraso (aquí 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; } }
fuente
Mi enfoque para esta pregunta es usar un temporizador para verificar los siguientes 2 "eventos".
1) onScrollChanged () dejó de ser llamado
2) El dedo del usuario se levanta de la vista de desplazamiento
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); } }
fuente
Mi solución es una variación de la gran solución de Lin Yu Cheng y también detecta cuándo se inició y se detuvo el desplazamiento.
Paso 1. Defina un HorizontalScrollView y 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);
Paso 2. Agregue la clase 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(); } } }
fuente
Intente echar un vistazo a esta pregunta aquí en StackOverflow; no es exactamente lo mismo que su pregunta, pero le da una idea de cómo puede administrar el evento de desplazamiento de un archivo
ScrollView
.Básicamente, necesita crear el suyo propio
CustomScrollView
extendiendoScrollView
y anulandoonScrollChanged(int x, int y, int oldx, int oldy)
. Luego, debe hacer referencia a esto en su archivo de diseño en lugar del estándarScrollView
comocom.mypackage.CustomScrollView
.fuente
Para un caso simple como el que describió, probablemente pueda salirse con la suya reemplazando el método de lanzamiento en su vista de desplazamiento personalizada. Se llama al método Fling para realizar una "desaceleración" cada vez que el usuario levanta el dedo de la pantalla.
Entonces, lo que debes hacer es algo como esto:
Subclase 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 } } }; //... } }
Método de lanzamiento de subclase y NO llame a la implementación de superclase.
@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. }
Fling no se activará si el usuario deja de desplazarse antes de levantar el dedo (velocityY == 0). En caso de que desee interceptar este tipo de eventos también, anule 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; }
NOTA Aunque esto funciona, anular el método de lanzamiento puede ser una mala idea. Es público, pero apenas está diseñado para subclases. En este momento hace 3 cosas: inicia el lanzamiento para mScroller privado, maneja posibles cambios de enfoque y muestra barras de desplazamiento. Esto podría cambiar en futuras versiones de Android. Por ejemplo, la instancia privada de mScroller cambió su clase de Scroller a OvershootScroller entre 2.3 y 3.0. Tienes que tener en cuenta todas estas pequeñas diferencias. En cualquier caso, prepárate para consecuencias imprevistas en el futuro.
fuente
Aquí hay algunas respuestas excelentes, pero mi código puede detectar cuándo se detiene el desplazamiento sin tener que extender la clase ScrollView. cada instancia de vista puede llamar a getViewTreeObserver (). al mantener esta instancia de ViewTreeObserver, puede agregar un OnScrollChangedListener usando la función addOnScrollChangedListener ().
declare lo siguiente:
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 } } };
y en tu onCreate (u otro lugar) agrega:
scrollListener = (ScrollView) findViewById(R.id.scroll); scrollListener.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { milesec = System.currentTimeMillis(); scrollStopDetector.postDelayed(scrollcalled, 200); } });
es posible que desee tomar más o menos tiempo entre estas comprobaciones, pero cuando se desplaza, este listner se llama muy rápido, por lo que funcionará muy rápido.
fuente
Aquí está mi solución que incluye seguimiento de desplazamiento y finalización de desplazamiento:
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; } }
fuente
Creo que esto ha surgido en el pasado. AFAIK, no puedes detectar eso fácilmente . Mi sugerencia es que eches un vistazo
ScrollView.java
(así es como hacemos las cosas en la tierra de Android :)) y descubras cómo puedes extender la clase para proporcionar la funcionalidad que estás buscando. Esto es lo que probaría primero:@Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { if (mScroller.isFinished()) { // do something, for example call a listener } }
fuente
onScrollChanged
,scrollTo
o cualquier otro método de desplazamiento relacionada. Alternativamente, puede copiar todo elScrollView
código y crear ese campoprotected
. Sin embargo, no lo recomendaría.este es un hilo antiguo, pero me gustaría agregar una solución más corta que se me ocurrió:
buttonsScrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> handler.removeCallbacksAndMessages(null) handler.postDelayed({ //YOUR CODE TO BE EXECUTED HERE },1000) }
Naturalmente, hay un retraso de 1000 milisegundos. Ajústelo si es necesario.
fuente
Hice algunas mejoras en la respuesta de ZeroG. Principalmente la cancelación del exceso de llamadas de tareas e implementación de todo como un OnTouchListener privado, por lo que todo el código de detección de desplazamiento estaría en un solo lugar.
Pegue el siguiente código en su propia implementación de 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; } }
Y luego en su implementación de ScrollView:
setOnTouchListener( new ScrollFinishHandler() );
fuente
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... }
Espero que el fragmento ayude :)
fuente