Pregunta

Tengo un ScrollView que rodea toda mi disposición para que toda la pantalla es desplazable. El primer elemento que tengo en este ScrollView es un bloque HorizontalScrollView que tiene características que es posible desplazarse a través de horizontalmente. He añadido un ontouchlistener a la horizontalscrollview a eventos de toque de mango y forzar el fin de "chasquido" a la imagen más cercana en el caso ACTION_UP.

Así que el efecto que voy a es igual que el de la pantalla de inicio de Android, donde puede desplazarse de una a la otra y que se ajuste a una pantalla cuando levanta el dedo.

Todo esto funciona muy bien excepto por un problema: Necesito golpe de izquierda a derecha casi perfectamente horizontal para una ACTION_UP a registrarse cada vez. Si swipe verticalmente en el mínimo (que creo que muchas personas tienden a hacer en sus teléfonos cuando deslizando de lado a lado), voy a recibir una ACTION_CANCEL en lugar de un ACTION_UP. Mi teoría es que esto se debe a la horizontalscrollview está dentro de un ScrollView y el ScrollView está secuestrando el toque vertical para permitir el desplazamiento vertical.

¿Cómo puedo desactivar los eventos de toque para la ScrollView desde justo dentro de mi ScrollView horizontal, pero aún así permitir el desplazamiento vertical normal en el ScrollView otra parte?

He aquí 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;
        }
    }
}
¿Fue útil?

Solución

Actualización: Me cuenta de esto. En mi ScrollView, necesitaba reemplazar el método de intercepción onInterceptTouchEvent sólo toque caso si el movimiento Y es> el movimiento X. Parece que el comportamiento predeterminado de un ScrollView es interceptar el evento táctil cuando se produzca algún movimiento Y. Así, con la corrección, el ScrollView sólo se interceptar el caso si el usuario se desplaza deliberadamente en la dirección Y y en que pase caso de la ACTION_CANCEL a los niños.

Este es el código para mi clase de desplazamiento vista que contiene la HorizontalScrollView:

public class CustomScrollView extends ScrollView {
    private GestureDetector mGestureDetector;

    public CustomScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mGestureDetector = new GestureDetector(context, new YScrollDetector());
        setFadingEdgeLength(0);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }

    // Return false if we're scrolling in the x direction  
    class YScrollDetector extends SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {             
            return Math.abs(distanceY) > Math.abs(distanceX);
        }
    }
}

Otros consejos

Gracias Joel por darme una idea de cómo resolver este problema.

He simplificado el código (sin necesidad de un GestureDetector ) para lograr el mismo efecto:

public class VerticalScrollView extends ScrollView {
    private float xDistance, yDistance, lastX, lastY;

    public VerticalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0f;
                lastX = ev.getX();
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();
                xDistance += Math.abs(curX - lastX);
                yDistance += Math.abs(curY - lastY);
                lastX = curX;
                lastY = curY;
                if(xDistance > yDistance)
                    return false;
        }

        return super.onInterceptTouchEvent(ev);
    }
}

Creo que he encontrado una solución más simple, sólo que esta utiliza una subclase de ViewPager en lugar de (su padre) ScrollView.

ACTUALIZACIÓN 07/16/2013 : He añadido una anulación para onTouchEvent también. Podría posiblemente ayuda con las cuestiones mencionadas en los comentarios, aunque tu caso es distinto.

public class UninterceptableViewPager extends ViewPager {

    public UninterceptableViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean ret = super.onInterceptTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean ret = super.onTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }
}

Esto es similar a la técnica utilizada en onScroll () de android.widget.Gallery. Se explica además por el Google I / O 2013 escritura Vistas personalizadas para Android .

Actualizar 10/12/2013 : Un enfoque similar se describe también en un puesto de Kirill Grouchnikov acerca de la (entonces) Android Market aplicación .

Me he dado cuenta de que a veces se recupera la ScrollView centran y el otro pierde el foco. Puede evitar que, por una sola concesión del enfoque ScrollView:

    scrollView1= (ScrollView) findViewById(R.id.scrollscroll);
    scrollView1.setAdapter(adapter);
    scrollView1.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            scrollView1.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

No estaba funcionando bien para mí. He cambiado y ahora funciona sin problemas. Si cualquier persona interesada.

public class ScrollViewForNesting extends ScrollView {
    private final int DIRECTION_VERTICAL = 0;
    private final int DIRECTION_HORIZONTAL = 1;
    private final int DIRECTION_NO_VALUE = -1;

    private final int mTouchSlop;
    private int mGestureDirection;

    private float mDistanceX;
    private float mDistanceY;
    private float mLastX;
    private float mLastY;

    public ScrollViewForNesting(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);

        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
    }

    public ScrollViewForNesting(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ScrollViewForNesting(Context context) {
        this(context,null);
    }    


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {      
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDistanceY = mDistanceX = 0f;
                mLastX = ev.getX();
                mLastY = ev.getY();
                mGestureDirection = DIRECTION_NO_VALUE;
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();
                mDistanceX += Math.abs(curX - mLastX);
                mDistanceY += Math.abs(curY - mLastY);
                mLastX = curX;
                mLastY = curY;
                break;
        }

        return super.onInterceptTouchEvent(ev) && shouldIntercept();
    }


    private boolean shouldIntercept(){
        if((mDistanceY > mTouchSlop || mDistanceX > mTouchSlop) && mGestureDirection == DIRECTION_NO_VALUE){
            if(Math.abs(mDistanceY) > Math.abs(mDistanceX)){
                mGestureDirection = DIRECTION_VERTICAL;
            }
            else{
                mGestureDirection = DIRECTION_HORIZONTAL;
            }
        }

        if(mGestureDirection == DIRECTION_VERTICAL){
            return true;
        }
        else{
            return false;
        }
    }
}

Gracias a su respuesta Neevek trabajó para mí, pero no bloquea el desplazamiento vertical cuando el usuario ha iniciado el desplazamiento de la vista horizontal (ViewPager) en dirección horizontal y luego sin levantar el dedo de desplazamiento vertical que comienza a desplazar la vista contenedor subyacente (ScrollView). Me fijo que al hacer un pequeño cambio en el código de Neevak:

private float xDistance, yDistance, lastX, lastY;

int lastEvent=-1;

boolean isLastEventIntercepted=false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            xDistance = yDistance = 0f;
            lastX = ev.getX();
            lastY = ev.getY();


            break;

        case MotionEvent.ACTION_MOVE:
            final float curX = ev.getX();
            final float curY = ev.getY();
            xDistance += Math.abs(curX - lastX);
            yDistance += Math.abs(curY - lastY);
            lastX = curX;
            lastY = curY;

            if(isLastEventIntercepted && lastEvent== MotionEvent.ACTION_MOVE){
                return false;
            }

            if(xDistance > yDistance )
                {

                isLastEventIntercepted=true;
                lastEvent = MotionEvent.ACTION_MOVE;
                return false;
                }


    }

    lastEvent=ev.getAction();

    isLastEventIntercepted=false;
    return super.onInterceptTouchEvent(ev);

}

Ésta pasa a ser parte de la biblioteca de soporte v4, NestedScrollView . Por lo tanto, se necesita más tiempo sin cortes locales para la mayoría de los casos supongo.

La solución de Neevek funciona mejor que Joel en dispositivos que ejecutan 3.2 y superiores. Hay un error en Android que hará que java.lang.IllegalArgumentException: pointerIndex fuera de rango si un detector gesto se utiliza dentro de un scollview. Para duplicar el problema, implementar una scollview personalizado como sugirió Joel y poner una vista interior buscapersonas. Si arrastra (no se levante la figura) a una dirección (izquierda / derecha) y luego al contrario, verá el choque. También en la solución de Joel, si arrastra el punto de vista del localizador moviendo el dedo en diagonal, una vez que el dedo dejar contenido de área de visualización de la vista de buscapersonas, el buscapersonas se retorna a su posición anterior. Todas estas cuestiones son más que ver con el diseño interno de Android o la falta de ella que en la implementación de Joel, que en sí es una pieza de código inteligente y concisa.

http://code.google.com/p/android/ temas / detalle? id = 18990

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top