Frage

Ich habe eine Scrollview, der mein gesamtes Layout umgibt, so dass der gesamte Bildschirm geblättert werden kann. Das erste Element I in dieser Scroll haben, ist ein Block, der HorizontalScrollView Merkmale aufweist, die durch horizontal gescrollt werden kann. Ich habe eine ontouchlistener zum horizontalscrollview zu Griff Touch-Ereignisse hinzugefügt und zwingen die Ansicht auf „Snap“ zum nächsten Bild auf dem ACTION_UP Ereignis.

So ist der Effekt, den ich für gehe ist wie das Lager Android Homescreen, wo Sie von einem zum anderen bewegen kann und es schnappt zu einem Bildschirm, wenn Sie den Finger heben.

Das funktioniert alles toll, bis auf ein Problem: ich Swipe müssen fast links nach rechts perfekt horizontal für einen ACTION_UP jemals zu registrieren. Wenn ich vertikal in allerwenigsten streichen (was ich denke, dass viele Menschen neigen dazu, auf ihren Handys zu tun, wenn einer Seite zur anderen klauen), werde ich einen ACTION_CANCEL anstelle eines ACTION_UP erhalten. Meine Theorie ist, dass dies, weil die horizontalscrollview innerhalb eines Scrollview ist, und die Scrollview ist Hijacking die vertikale Touch für vertikales Scrollen zu ermöglichen.

Wie kann ich die Touch-Ereignisse für die Scroll von nur innerhalb meiner horizontalen Scrollview zu deaktivieren, aber immer noch für die normale vertikales Scrollen an anderer Stelle im Scroll erlauben?

Hier ist eine Probe von meinem Code:

   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;
        }
    }
}
War es hilfreich?

Lösung

Update: Ich dachte, diese aus. Auf meiner Scrollview, ich brauchte die onInterceptTouchEvent Methode nur abfangen das Berührungsereignis, wenn die Y-Bewegung ist außer Kraft zu setzen> die X-Bewegung. Es scheint, wie das Standardverhalten eines Scroll abzufangen ist das Berührungsereignis, wenn sie die Y-Bewegung ist. Also mit dem Update, das Scrollview wird nur abfangen den Fall, wenn der Benutzer absichtlich in der Y-Richtung scrollen und in diesem Fall den Ball aus dem ACTION_CANCEL an die Kinder.

Hier ist der Code für meine Scroll View-Klasse, die die HorizontalScrollView enthält:

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

Andere Tipps

Danke Joel dass sie mir einen Anhaltspunkt, wie dieses Problem zu lösen.

Ich habe den Code (ohne Notwendigkeit für eine GestureDetector ) vereinfacht den gleichen Effekt zu erzielen:

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

Ich glaube, ich eine einfachere Lösung gefunden, nur diese eine Unterklasse von ViewPager verwendet anstelle von (Mutterunternehmen) Scroll.

UPDATE 2013.07.16 : Ich eine Überschreibung für onTouchEvent ebenfalls hinzugefügt. Es könnte möglicherweise Hilfe bei den genannten Themen in den Kommentaren, obwohl 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;
    }
}

Dies ist vergleichbar mit die in android.widget.Gallery der OnScroll () verwendete Technik. Es wird weiter erläutert durch die Google I / O 2013 Präsentation Writing benutzerdefinierte Ansichten für Android .

Update 2013.12.10 : Ein ähnlicher Ansatz wird auch in ein Beitrag von Kirill Grouchnikov über die (dann) Android Market App .

Ich habe herausgefunden, dass manchmal ein Scrollgewinnen konzentrieren und die andere verliert Fokus. Sie können dies verhindern, indem nur die Gewährung einer der Scroll Fokus:

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

Es war nicht gut für mich arbeiten. Habe ich es und jetzt funktioniert es reibungslos. Wenn jemand interessiert.

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

Dank Neevek seine Antwort für mich gearbeitet, aber es sperrt nicht die vertikalen Scrollen, wenn Benutzer gestartet hat die horizontale Ansicht Scrollen (ViewPager) in horizontaler Richtung und dann, ohne den Finger scrollen zu Heben vertikal startet er die darunter liegenden Behälter Ansicht blättern (Scroll). Ich reparierte sie durch eine leichte Veränderung in Neevak des Code machen:

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

}

Das wurde schließlich ein Teil der Unterstützung v4 Bibliothek, NestedScrollView . Also, nicht mehr lokal Hacks für die meisten Fälle benötigt wird, ich denke, würde.

Neevek-Lösung funktioniert besser als Joels auf Geräten 3.2 und höher. Es gibt einen Bug in Android, die java.lang.IllegalArgumentException verursachen: pointerIndex außerhalb des Bereichs, wenn eine Geste Detektor in einem scollview verwendet wird. Um das Problem zu duplizieren, implementieren eine benutzerdefinierte scollview als Joel vorgeschlagen und einen Blick Pager nach innen setzen. Wenn Sie ziehen (tun Sie Figur nicht anheben) in eine Richtung (links / rechts) und dann in die entgegengesetzte, werden Sie den Absturz sehen. Auch in Joels Lösung, wenn Sie die Ansicht Pager ziehen, indem Sie Ihren Finger diagonal bewegen, sobald Sie mit dem Finger nach Ansicht Pagers Seitenbereich verlassen, wird der Pager springt in seine vorherige Position zurück. Alle diese Fragen sind, mehr zu tun mit Android internen Design oder Mangel an ihm als Joels Implementierung, die sich ein Stück von intelligenten und präzisen Code ist.

http://code.google.com/p/android/ Fragen / detail? id = 18990

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top