Tengo un ScrollView que rodea todo mi diseño para que toda la pantalla se pueda desplazar. El primer elemento que tengo en este ScrollView es un bloque HorizontalScrollView que tiene características que se pueden desplazar horizontalmente. Agregué un ontouchlistener a la vista de desplazamiento horizontal para manejar eventos táctiles y forzar la vista a "ajustar" a la imagen más cercana en el evento ACTION_UP.
Entonces, el efecto que busco es como la pantalla de inicio de Android en la que puedes desplazarte de una a otra y se ajusta a una pantalla cuando levantas el dedo.
Todo esto funciona muy bien, excepto por un problema: necesito deslizar de izquierda a derecha casi perfectamente horizontalmente para que un ACTION_UP se registre. Si al menos deslizo verticalmente (lo que creo que muchas personas tienden a hacer en sus teléfonos cuando deslizan de lado a lado), recibiré un ACTION_CANCEL en lugar de un ACTION_UP. Mi teoría es que esto se debe a que la vista de desplazamiento horizontal está dentro de una vista de desplazamiento, y la vista de desplazamiento está secuestrando el toque vertical para permitir el desplazamiento vertical.
¿Cómo puedo deshabilitar los eventos táctiles para la vista de desplazamiento desde mi vista de desplazamiento horizontal, pero aún permitir el desplazamiento vertical normal en otra parte de la vista de desplazamiento?
Aquí hay una muestra de mi código:
public class HomeFeatureLayout extends HorizontalScrollView {
private ArrayList<ListItem> items = null;
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 300;
private int activeFeature = 0;
public HomeFeatureLayout(Context context, ArrayList<ListItem> items){
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setFadingEdgeLength(0);
this.setHorizontalScrollBarEnabled(false);
this.setVerticalScrollBarEnabled(false);
LinearLayout internalWrapper = new LinearLayout(context);
internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
addView(internalWrapper);
this.items = items;
for(int i = 0; i< items.size();i++){
LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
TextView header = (TextView) featureLayout.findViewById(R.id.featureheader);
ImageView image = (ImageView) featureLayout.findViewById(R.id.featureimage);
TextView title = (TextView) featureLayout.findViewById(R.id.featuretitle);
title.setTag(items.get(i).GetLinkURL());
TextView date = (TextView) featureLayout.findViewById(R.id.featuredate);
header.setText("FEATURED");
Image cachedImage = new Image(this.getContext(), items.get(i).GetImageURL());
image.setImageDrawable(cachedImage.getImage());
title.setText(items.get(i).GetTitle());
date.setText(items.get(i).GetDate());
internalWrapper.addView(featureLayout);
}
gestureDetector = new GestureDetector(new MyGestureDetector());
setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
int scrollX = getScrollX();
int featureWidth = getMeasuredWidth();
activeFeature = ((scrollX + (featureWidth/2))/featureWidth);
int scrollTo = activeFeature*featureWidth;
smoothScrollTo(scrollTo, 0);
return true;
}
else{
return false;
}
}
});
}
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature < (items.size() - 1))? activeFeature + 1:items.size() -1;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature > 0)? activeFeature - 1:0;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
}
MeetMe's HorizontalListView
bibliotecaHomeFeatureLayout extends HorizontalScrollView
) aquí velir.com/blog/index.php/2010/11/17/… Hay algunos comentarios adicionales sobre lo que está sucediendo a medida que se compone la clase de desplazamiento personalizada.Respuestas:
Actualización: descubrí esto. En mi ScrollView, necesitaba anular el método onInterceptTouchEvent para interceptar solo el evento táctil si el movimiento Y es> el movimiento X. Parece que el comportamiento predeterminado de un ScrollView es interceptar el evento táctil siempre que haya CUALQUIER movimiento Y. Entonces, con la corrección, ScrollView solo interceptará el evento si el usuario se desplaza deliberadamente en la dirección Y y, en ese caso, pasa el ACTION_CANCEL a los niños.
Aquí está el código para mi clase de Vista de desplazamiento que contiene HorizontalScrollView:
fuente
mGestureDetector.onTouchEvent(ev)
se llame. Tal como está ahora, no se llamará sisuper.onInterceptTouchEvent(ev)
es falso. Me encontré con un caso en el que los niños que pueden hacer clic en la vista de desplazamiento pueden tomar los eventos táctiles y onScroll no será llamado en absoluto. De lo contrario, gracias, gran respuesta!Gracias Joel por darme una pista sobre cómo resolver este problema.
He simplificado el código (sin necesidad de un GestureDetector ) para lograr el mismo efecto:
fuente
Creo que encontré una solución más simple, solo que esto usa una subclase de ViewPager en lugar de (su padre) ScrollView.
ACTUALIZACIÓN 2013-07-16 : También agregué una anulación para
onTouchEvent
. Posiblemente podría ayudar con los problemas mencionados en los comentarios, aunque YMMV.Esto es similar a la técnica utilizada en onScroll () de android.widget.Gallery . Se explica con más detalle en la presentación de Google I / O 2013 Escritura de vistas personalizadas para Android .
Actualización 2013-12-10 : También se describe un enfoque similar en una publicación de Kirill Grouchnikov sobre la (entonces) aplicación Android Market .
fuente
ScrollView
con unLinearLayout
en el queUninterceptableViewPager
se coloca el. De hecho,ret
siempre es falso ... ¿Alguna idea de cómo solucionar esto?TableRow
que está dentro de unTableLayout
que está dentro de unScrollView
(sí, lo sé ...), y está funcionando según lo previsto. Tal vez podría intentar anular enonScroll
lugar deonInterceptTouchEvent
, como lo hace Google (línea 1010)Descubrí que a veces un ScrollView recupera el foco y el otro pierde el foco. Puede evitar eso, otorgando solo uno de los enfoques scrollView:
fuente
No me estaba funcionando bien. Lo cambié y ahora funciona sin problemas. Si alguien esta interesado.
fuente
Gracias a Neevek, su respuesta funcionó para mí, pero no bloquea el desplazamiento vertical cuando el usuario ha comenzado a desplazar la vista horizontal (ViewPager) en dirección horizontal y luego, sin levantar el desplazamiento vertical del dedo, comienza a desplazar la vista del contenedor subyacente (ScrollView) . Lo arreglé haciendo un ligero cambio en el código de Neevak:
fuente
Esto finalmente se convirtió en parte de la biblioteca de soporte v4, NestedScrollView . Entonces, ya no se necesitan hacks locales para la mayoría de los casos, supongo.
fuente
La solución de Neevek funciona mejor que la de Joel en dispositivos con 3.2 y superior. Hay un error en Android que causará java.lang.IllegalArgumentException: pointerIndex fuera de rango si se usa un detector de gestos dentro de una vista de desplazamiento. Para duplicar el problema, implemente una vista de exploración personalizada como sugirió Joel y coloque un buscapersonas dentro. Si arrastra (no levante su figura) en una dirección (izquierda / derecha) y luego a la opuesta, verá el choque. También en la solución de Joel, si arrastra el localizador de vista moviendo el dedo en diagonal, una vez que su dedo abandone el área de visualización del contenido del localizador de visualización, el localizador volverá a su posición anterior. Todos estos problemas tienen más que ver con el diseño interno de Android o la falta de él que con la implementación de Joel, que en sí misma es un código inteligente y conciso.
http://code.google.com/p/android/issues/detail?id=18990
fuente