tutorial studio example android android-canvas smooth-scrolling

studio - drawing android



Android: cómo detectar cuándo ha terminado un pergamino (12)

Estoy usando el método onScroll de GestureDetector.SimpleOnGestureListener para desplazar un mapa de bits grande en un lienzo. Cuando el pergamino haya terminado, quiero volver a dibujar el mapa de bits en caso de que el usuario quiera desplazarse más ... fuera del borde del mapa de bits, pero no puedo ver cómo detectar cuándo el pergamino ha finalizado (el usuario ha levantado el dedo de la pantalla).

e2.getAction () siempre parece devolver el valor 2, por lo que no es de ayuda. e2.getPressure parece devolver valores bastante constantes (alrededor de 0.25) hasta la llamada final al Despliegue cuando la presión parece caer a aproximadamente 0.13. Supongo que podría detectar esta reducción en la presión, pero esto estará lejos de ser infalible.

Debe haber una mejor manera: ¿alguien puede ayudar, por favor?


Así es como resolví el problema. Espero que esto ayude.

// declare class member variables private GestureDetector mGestureDetector; private OnTouchListener mGestureListener; private boolean mIsScrolling = false; public void initGestureDetection() { // Gesture detection mGestureDetector = new GestureDetector(new SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { handleDoubleTap(e); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { handleSingleTap(e); return true; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // i''m only scrolling along the X axis mIsScrolling = true; handleScroll(Math.round((e2.getX() - e1.getX()))); return true; } @Override /** * Don''t know why but we need to intercept this guy and return true so that the other gestures are handled. * https://code.google.com/p/android/issues/detail?id=8233 */ public boolean onDown(MotionEvent e) { Log.d("GestureDetector --> onDown"); return true; } }); mGestureListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { if (mGestureDetector.onTouchEvent(event)) { return true; } if(event.getAction() == MotionEvent.ACTION_UP) { if(mIsScrolling ) { Log.d("OnTouchListener --> onTouch ACTION_UP"); mIsScrolling = false; handleScrollFinished(); }; } return false; } }; // attach the OnTouchListener to the image view mImageView.setOnTouchListener(mGestureListener); }


Creo que esto funcionará como lo necesites

protected class SnappingGestureDetectorListener extends SimpleOnGestureListener{ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY){ boolean result = super.onScroll(e1, e2, distanceX, distanceY); if(!result){ //Do what you need to do when the scrolling stop here } return result; } }


Deberías echarle un vistazo a http://developer.android.com/reference/android/widget/Scroller.html . Especialmente, esto podría ser útil (ordenado por relevancia):

isFinished(); computeScrollOffset(); getFinalY(); getFinalX(); and getCurrY() getCurrX() getDuration()

Esto implica que tienes que crear un Scroller.

Si desea usar el toque, también puede usar GestureDetector y definir su propio desplazamiento de lienzo. El siguiente ejemplo crea un ScrollableImageView y para usarlo debe definir las medidas de su imagen. Puedes definir tu propio rango de desplazamiento y luego de finalizar el desplazamiento la imagen se vuelve a dibujar.

http://www.anddev.org/viewtopic.php?p=31487#31487

Dependiendo de tu código, debes considerar la invalidación (int l, int t, int r, int b); para la invalidación


Estaba investigando este mismo problema. Vi a Akos Cz responder a tu pregunta. Creé algo similar, pero con mi versión noté que solo funcionaba para un rollo normal, es decir, uno que no genera una aventura. Pero si se generó una aventura, independientemente de si procesé una aventura o no, entonces NO detectó "ACTION_UP" en "onTouchEvent". Ahora bien, tal vez esto fue solo algo con mi implementación, pero si fuera así, no podría entender por qué.

Después de más investigaciones, noté que durante una aventura, el "ACTION_UP" pasaba a "onFling" en "e2" cada vez. Así que pensé que debe ser por eso que no se manejaba en "onTouchEvent" en esos casos.

Para hacerlo funcionar, solo tuve que llamar a un método para manejar "ACTION_UP" en "onFling" y luego funcionó para ambos tipos de desplazamiento. A continuación se detallan los pasos exactos que realicé para implementar en mi aplicación:

-inicializó un booleano "gestureScrolling" a "falso" en un constructor.

-Lo configuré como "verdadero" en "onScroll"

-creó un método para manejar el evento "ACTION_UP". Dentro de ese evento, reinicié "gestureSCrolling" en falso y luego hice el resto del proceso que necesitaba hacer.

-in "onTouchEvent", si se detectó "ACTION_UP" y "gestureScrolling" = true, llamé a mi método para manejar "ACTION_UP"

-Y la parte que hice que era diferente fue: también llamé a mi método para manejar "ACTION_UP" dentro de "onFling".


Esto es lo que funcionó para mí.

He enriquecido el GestureDetector.OnGestureListener existente con el método onFingerUp () . Este oyente hace todo como el GestureDetector integrado y también puede escuchar el evento de arriba hacia arriba (no está enFling () ya que se llama solo cuando el dedo se levanta junto con una acción de deslizamiento rápido).

import android.content.Context; import android.os.Handler; import android.view.GestureDetector; import android.view.MotionEvent; public class FingerUpGestureDetector extends GestureDetector { FingerUpGestureDetector.OnGestureListener fListener; public FingerUpGestureDetector(Context context, OnGestureListener listener) { super(context, listener); fListener = listener; } public FingerUpGestureDetector(Context context, GestureDetector.OnGestureListener listener, OnGestureListener fListener) { super(context, listener); this.fListener = fListener; } public FingerUpGestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler, OnGestureListener fListener) { super(context, listener, handler); this.fListener = fListener; } public FingerUpGestureDetector(Context context, GestureDetector.OnGestureListener listener, Handler handler, boolean unused, OnGestureListener fListener) { super(context, listener, handler, unused); this.fListener = fListener; } public interface OnGestureListener extends GestureDetector.OnGestureListener { boolean onFingerUp(MotionEvent e); } public static class SimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener implements FingerUpGestureDetector.OnGestureListener { @Override public boolean onFingerUp(MotionEvent e) { return false; } } @Override public boolean onTouchEvent(MotionEvent ev) { if (super.onTouchEvent(ev)) return true; if (ev.getAction() == MotionEvent.ACTION_UP) { return fListener.onFingerUp(ev); } return false; } }


Estoy seguro de que es demasiado tarde para ti, sin embargo, parece que he encontrado la solución correcta para tu pregunta original y no es necesaria la intención.

Si está utilizando el Scroller / OverScroller Object para desplazarse, debe verificar el valor de retorno de la siguiente función.

public boolean computeScrollOffset()

disfrutar

harvinder


Extracto del evento onScroll de GestureListener API: enlace de texto

public abstract boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) Desde: API Level 1

Devuelve * verdadero si el evento se consume, de lo contrario es falso

Tal vez una vez que se haya consumido el evento, la acción haya finalizado y el usuario haya quitado el dedo de la pantalla o al menos haya terminado esta acción de Despliegue.

Puede usar esto en una declaración IF para buscar == verdadero y luego comenzar con la siguiente acción.


No he hecho esto por mí mismo, pero mirando a Toque () siempre se obtiene una secuencia 0 <2> 1, por lo que el final tiene que ser un 1 para levantar los dedos.


No he probado / usado esto, sino una idea para un enfoque:

detener / interrumpir el rediseño del lienzo en CADA evento de desplazamiento esperar 1s y luego comenzar a dibujar de nuevo lienzo en CADA desplazamiento.

esto llevará a realizar el redibujado solo al final del scroll, ya que solo el último scroll no se interrumpirá para que se complete el redraw.

Espero que esta idea te ayude :)


No sé Android, pero mirando la documentación, parece que Rob tiene razón: Android ACTION_UP constante ¿ Intenta buscar ACTION_UP desde getAction ()?

Editar: ¿Qué muestra e1.getAction ()? ¿Alguna vez devuelve ACTION_UP? La documentación dice que contiene el evento de inactividad inicial, por lo que tal vez también notifique cuando el puntero esté activo

Editar: Solo dos cosas más que puedo pensar ¿Estás regresando falso en algún punto? Eso puede prevenir ACTION_UP

La única otra cosa que probaría es tener un evento separado, tal vez en Baja, y establecer una bandera dentro de Desplazamiento como isScrolling. Cuando ACTION_UP se da a onDown y se establece isScrolling, puede hacer lo que quiera y restablecer isScrolling en false. Es decir, suponiendo que se llama a Onown junto con onScroll, y getAction devolverá ACTION_UP durante onDown


Volviendo a esto después de unos meses, ahora he seguido una táctica diferente: usar un controlador (como en la muestra de Android Snake) para enviar un mensaje a la aplicación cada 125 milisegundos, lo que lo impulsa a verificar si se ha iniciado un desplazamiento y si ha transcurrido más de 100 milisegundos desde el último evento de desplazamiento.

Esto parece funcionar bastante bien, pero si alguien puede ver cualquier inconveniente o posible mejora, le agradeceré que me lo cuente.

El código relevante está en la clase MyView:

public class MyView extends android.view.View { ... private long timeCheckInterval = 125; // milliseconds private long scrollEndInterval = 100; public long latestScrollEventTime; public boolean scrollInProgress = false; public MyView(Context context) { super(context); } private timeCheckHandler mTimeCheckHandler = new timeCheckHandler(); class timeCheckHandler extends Handler{ @Override public void handleMessage(Message msg) { long now = System.currentTimeMillis(); if (scrollInProgress && (now>latestScrollEventTime+scrollEndInterval)) { scrollInProgress = false;

// El desplazamiento ha finalizado, así que inserte el código aquí

// que llama al método doDrawing ()

// para volver a dibujar el mapa de bits recentrado donde terminó el desplazamiento

[ layout or view ].invalidate(); } this.sleep(timeCheckInterval); } public void sleep(long delayMillis) { this.removeMessages(0); sendMessageDelayed(obtainMessage(0), delayMillis); } } } @Override protected void onDraw(Canvas canvas){ super.onDraw(canvas);

// código para dibujar un mapa de bits de gran tamaño en el lienzo de la vista // posicionado para tener en cuenta cualquier desplazamiento que esté en curso

} public void doDrawing() {

// código para hacer un dibujo detallado (y que consume mucho tiempo) // en un mapa de bits de gran tamaño

// la siguiente instrucción restablece el reloj de Time Check // el reloj se inicia por primera vez cuando // la actividad principal llama a este método cuando se inicia la aplicación

mTimeCheckHandler.sleep(timeCheckInterval); }

// resto de la clase MyView

}

y en la clase MyGestureDetector

public class MyGestureDetector extends SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { [MyView].scrollInProgress = true; long now = System.currentTimeMillis(); [MyView].latestScrollEventTime =now; [MyView].scrollX += (int) distanceX; [MyView].scrollY += (int) distanceY;

// la siguiente instrucción hace que se llame al método onDraw de la Vista // que traza el mapa de bits del búfer en la pantalla // desplazado para tener en cuenta el desplazamiento

[MyView].invalidate(); }

// resto de la clase MyGestureDetector

}


SimpleOnGestureListener.onFling()

Parece tener lugar cuando termina un pergamino (es decir, el usuario suelta el dedo), eso es lo que estoy usando y funciona muy bien para mí.