Domanda

Ho uno ScrollView che circonda tutto il mio layout in modo che l'intero schermo è scorrevole. Il primo elemento che ho in questo ScrollView è un blocco HorizontalScrollView che ha caratteristiche che si possono scorrere attraverso orizzontalmente. Ho aggiunto un ontouchlistener al horizontalscrollview a eventi di tocco maniglia e forzare la fine di "SNAP" per l'immagine più vicina sull'evento ACTION_UP.

Così l'effetto Vado a fare è come lo stock homescreen Android dove è possibile passare da una all'altra e non si blocca a uno schermo quando si solleva il dito.

Il tutto funziona alla grande tranne per un problema: ho bisogno di strisciare sinistra a destra quasi perfettamente orizzontale per un ACTION_UP a mai registrati. Se io Striscia verticalmente in meno (che penso che molte persone tendono a fare sul proprio cellulare quando scorrendo lato a lato), riceverò un ACTION_CANCEL invece di un ACTION_UP. La mia teoria è che questo è perché il horizontalscrollview è all'interno di uno ScrollView, e lo ScrollView è dirottamento il tocco verticale per consentire lo scorrimento verticale.

Come è possibile disattivare gli eventi di tocco per lo ScrollView da solo nel mio ScrollView orizzontale, ma ancora permettere il normale scorrimento verticale in altre parti del ScrollView?

Ecco un esempio del mio codice:

   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;
        }
    }
}
È stato utile?

Soluzione

Aggiornamento: Ho capito questo. Sul mio ScrollView, avevo bisogno di sovrascrivere il metodo onInterceptTouchEvent solo intercettare l'evento contatto, se il movimento Y è> il movimento X. Sembra che il comportamento predefinito di uno ScrollView è quello di intercettare l'evento di tocco ogni volta che c'è qualsiasi movimento Y. Quindi, con la correzione, l'ScrollView sarà solo intercettare l'evento se l'utente è volutamente scorre nella direzione Y e in tal caso il passaggio al largo ACTION_CANCEL ai bambini.

Ecco il codice per la mia classe di scorrimento Visualizza che contiene il 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);
        }
    }
}

Altri suggerimenti

Grazie Joel per avermi dato un indizio su come risolvere questo problema.

Ho semplificato il codice (senza bisogno di un GestureDetector ) per ottenere lo stesso effetto:

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);
    }
}

Credo di avere trovato una soluzione più semplice, solo che questa utilizza una sottoclasse di ViewPager invece del (suo padre) ScrollView.

UPDATE 2013/7/16 : ho aggiunto un override per onTouchEvent pure. Si potrebbe eventualmente aiutare con i problemi menzionati nei commenti, anche se YMMV.

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;
    }
}

Questo è simile alla la tecnica utilizzata in di android.widget.Gallery onscroll () . Si è ulteriormente spiegata con il Google I / O 2013 presentazione scrittura Visualizzazioni personalizzate per Android .

Aggiornamento 2013/12/10 : Un approccio simile è descritta anche nella un post da Kirill Grouchnikov circa la (allora) mercato Android .

ho scoperto che a volte si riprende, ScrollView fuoco e l'altro non è più attivo. È possibile impedire che, da solo concedendo uno dei focus 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;
        }
    });

Non funzionava bene per me. L'ho cambiato ed ora funziona senza intoppi. Se chiunque sia interessato.

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;
        }
    }
}

Grazie alla sua risposta Neevek lavorato per me ma non si blocca lo scorrimento verticale quando l'utente ha iniziato a scorrere la visualizzazione orizzontale (ViewPager) in direzione orizzontale e quindi senza sollevare il rotolo dito verticalmente inizia a scorrere la vista contenitore sottostante (ScrollView). Ho riparato facendo un leggero cambiamento nel codice di 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);

}

Questo è diventato finalmente una parte della libreria di supporto v4, NestedScrollView . Quindi, non più locale hack è necessario per la maggior parte dei casi, direi.

La soluzione di Neevek funziona meglio di Joel su dispositivi che eseguono 3.2 e superiori. C'è un bug in Android che causerà java.lang.IllegalArgumentException: pointerIndex fuori portata, se un rivelatore gesto viene utilizzato all'interno di uno scollview. Per duplicare il problema, implementare uno scollview personalizzato come suggerito Joel e mettere una vista interna cercapersone. Se si trascina (non ti alzare figura) in una direzione (sinistra / destra) e poi al contrario, si vedrà l'incidente. Anche in soluzione di Joel, se si trascina il pager vista muovendo il dito in diagonale, una volta il dito lascia contenuti area di visualizzazione del cercapersone vista, il cercapersone balzerà nella sua posizione precedente. Tutti questi problemi sono più a che fare con il design interno di Android o la mancanza di esso che l'implementazione di Joel, che è essa stessa un pezzo di codice intelligente e conciso.

http://code.google.com/p/android/ problemi / dettaglio? id = 18990

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top