ViewPager in einer ScrollView scrollt nicht korrekt

88

Ich habe eine 'Seite', die eine Reihe von Komponenten enthält und deren Inhalt länger als die Höhe des Geräts ist. Gut, setzen Sie einfach das gesamte Layout (die gesamte Seite) in ein ScrollView, kein Problem.

Eine der Komponenten ist a ViewPager. Dies wird korrekt gerendert, aber die Reaktion auf ein Wischen / Schleudern funktioniert nicht richtig, es ist nervös und funktioniert nicht immer. Es scheint mit dem 'verwechselt' zu werden ScrollView, funktioniert also nur zu 100%, wenn Sie in einer exakten horizontalen Linie fliegen.

Wenn ich das entferne ScrollView, reagiert der ViewPager perfekt.

Ich habe eine Suche durchgeführt und dies nicht als bekannten Defekt gefunden. Hat das noch jemand erlebt?

  • Plattformversion: 1.6
  • Kompatibilitätsbibliothek v4.
  • Gerät: HTC Incredible S.

Im Folgenden finden Sie einen Beispielcode, mit dem Sie testen können. Kommentieren Sie ihn aus ScrollView, damit er ordnungsgemäß funktioniert.

Aktivität:

package com.ss.activities;

import com.ss.R;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.widget.TextView;

public class PagerInsideScollViewActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ViewPager vp = (ViewPager) findViewById(R.id.viewpager);
        vp.setAdapter(new MyPagerAdapter(this));
    }
}

class MyPagerAdapter extends PagerAdapter {

    private Context ctx;

    public MyPagerAdapter(Context context) {
        ctx = context;
    }

    @Override
    public int getCount() {
        return 2;
    }

    @Override
    public Object instantiateItem(View collection, int position) {

        TextView tv =  new TextView(ctx);
        tv.setTextSize(50);
        tv.setTextColor(Color.WHITE);
        tv.setText("SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, " +
                "SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, " +
                "SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, SMILE DUDE, " +
                "SMILE DUDE, SMILE DUDE, SMILE DUDE");

        ((ViewPager) collection).addView(tv);

        return tv;

    }

    @Override
    public void destroyItem(View collection, int position, Object view) {
         ((ViewPager) collection).removeView((View) view);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
    }

    @Override
    public void startUpdate(View arg0) {
    }

    @Override
    public void finishUpdate(View arg0) {
    }
}

Layout:

<?xml version="1.0" encoding="utf-8"?>
    <ScrollView
         xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical" >

            <android.support.v4.view.ViewPager
                android:id="@+id/viewpager"
                android:layout_width="fill_parent"
                android:layout_height="300dp" />

        </LinearLayout>

    </ScrollView>
electricSunny
quelle
Überprüfen Sie die Antwort hier: stackoverflow.com/questions/9034030/viewpager-in-scrollview/…
Andrew Dmytrenko
Bitte zeigen Sie meine Antwort in diesem Link: stackoverflow.com/questions/7381360/…
Geral Alexander Torres

Antworten:

60

Ich hatte das gleiche Problem. Meine Lösung bestand darin, aufzurufen, requestDisallowInterceptTouchEventals der ViewPager-Bildlauf gestartet wurde.

Hier ist mein Code:

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

pager.setOnPageChangeListener(new SimpleOnPageChangeListener() {
    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        pager.getParent().requestDisallowInterceptTouchEvent(true);
    }
});
Der beste Künstler
quelle
1
Das ist eine Art Arbeit. Ich möchte, dass die Bildlaufansicht vertikal verläuft, auch wenn die Berührung des Ansichtspagers beginnt, wenn die Bewegung "größtenteils" vertikal erfolgt
Nemanja Kovacevic
3
Ist es notwendig, requestDisallowInterceptTouchEvent sowohl in onTouch als auch in onPageScrolled zu haben, oder ist das nur übertrieben? Könnten wir die gleichen Ergebnisse erzielen, indem wir requestDisallowInterceptTouchEvent nur auf onPageScrollStateChanged verschieben?
craigrs84
1
Ich habe das gleiche Problem und habe diese beste Lösung gefunden. Ich hoffe, diese Arbeit funktioniert für Sie. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
Wie kann ich das onClick-Ereignis trotzdem überschreiben? es widerspricht on Touch?
Kiddo
4
Was ist der Pager und MPager hier, den ich brauche, um die Eingabe zu geben?
Sujithrao
30

Weitere Informationen haben ergeben, dass es Probleme mit Bildlaufkomponenten innerhalb von Bildlaufkomponenten gibt.

Eine Lösung besteht darin, das vertikale Scrollen der ScrollView im Bereich der enthaltenen scrollbaren Komponente, in meinem Fall eines ViewPagers, zu deaktivieren.

Der Code für diese Lösung ist hier detailliert (und einfach genial!)

electricSunny
quelle
Auf welche Lösung auf dieser Seite sollte ich verweisen?
Harsha MV
1
requestDisallowInterceptTouchEvent ist das, was Sie suchen.
Yahoo
1
Ich habe das gleiche Problem und habe diese beste Lösung gefunden. Ich hoffe, diese Arbeit funktioniert für Sie. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
Vielen Dank für den Link. Die Lösung, die ich dort gefunden habe, hat mir immer noch Probleme mit nicht perfekt horizontalen Wischbewegungen im Viewpager bereitet. Deshalb habe ich sie ein wenig geändert, damit sie funktioniert: stackoverflow.com/a/33696740/2093236
Dmide
15

Hier ist eine Lösung:

    mPager.setOnTouchListener(new View.OnTouchListener() {

        int dragthreshold = 30;
        int downX;
        int downY;

        @Override
        public boolean onTouch(View v, MotionEvent event) {

            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int distanceX = Math.abs((int) event.getRawX() - downX);
                int distanceY = Math.abs((int) event.getRawY() - downY);

                if (distanceY > distanceX && distanceY > dragthreshold) {
                    mPager.getParent().requestDisallowInterceptTouchEvent(false);
                    mScrollView.getParent().requestDisallowInterceptTouchEvent(true);
                } else if (distanceX > distanceY && distanceX > dragthreshold) {
                    mPager.getParent().requestDisallowInterceptTouchEvent(true);
                    mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                mPager.getParent().requestDisallowInterceptTouchEvent(false);
                break;
            }
            return false;
        }
    });

Grundsätzlich stellen Sie die X- und Y-Werte ein, wenn Sie nach unten drücken, und berechnen die Entfernung beim Ziehen, um zu bestimmen, in welche Richtung wir gehen möchten. Spielen Sie mit der Dragthreshold, um sie für Ihren Fall zu optimieren.


quelle
Ich hatte das gleiche Problem. Dies ist die einzige Lösung, die funktioniert hat. Gute Arbeit ... :)
rawcoder064
Dies ist die beste Lösung für mich. Es wird das ScrollView nicht stören, wenn es auf und ab rutscht.
Lei Guo
Dies ist die beste Lösung für mich. Wir haben auch die Möglichkeit, den Schwellenwert in dieser Lösung zu konfigurieren.
Vinayak
funktioniert bei mir nicht, weil die Bildlaufansicht die Berührung abfängt, bevor diese
Abbruchanweisung
Tolle Arbeit, und wenn Sie dies mit Wrapcontentviewpager verwenden möchten, dann funktioniert es auch sehr gut
Silvans Solanki
4

Mit einem ViewPager können Sie Seitenänderungsereignisse erfassen und verhindern, dass ScrollView das Berührungsereignis abfängt, das die Seitenänderung verursacht hat.

Dies ist sehr einfach zu bedienen ViewGroup.requestDisallowInterceptTouchEvent(boolean). Der Benutzer kann die ScrollView auch dann ziehen, wenn er den ViewPager zieht. Das horizontale Ziehen des Pagers funktioniert jedoch weiterhin, ohne dass die ScrollView stört.

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Add android:id for your ScrollView in your layout
        final ScrollView sv = (ScrollView) findViewById(R.id.scrollview);
        final ViewPager vp = (ViewPager) findViewById(R.id.viewpager);
        vp.setAdapter(new MyPagerAdapter(this));

        // Use a page-change listener to respond to begin-drag events:
        vp.setOnPageChangeListener(new OnPageChangeListener() {
            @Override
            public void onPageSelected(int newState) {
                if (newState == ViewPager.SCROLL_STATE_DRAGGING) {
                    // Prevent the ScrollView from intercepting this event now that the page is changing.
                    // When this drag ends, the ScrollView will start accepting touch events again.
                    sv.requestDisallowInterceptTouchEvent(true);
                }
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }

            @Override
            public void onPageScrollStateChanged(int arg0) {
            }
        });

    }

Dies funktioniert bei mir mit der Android Support v4-Bibliothek unter Android 2.3.4 und 4.2.1.

mrb
quelle
1
soll es onPageScrollStateChanged statt onPageSelected sein?
craigrs84
2

Ich habe die Lösung von @Michael Herbig angepasst. Der Vorteil dabei ist, dass sie in jeder Ansicht funktioniert, die dies zulässt setOnTouchListener, nicht nur in einem ViewPager (z. B. ViewPagerIndicator), sondern in einer eigenständigen Klasse.

Anwendungsbeispiel:

// runStatsPager is a android.support.v4.view.ViewPager;
runStatsPager.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPager));

// runStatsPagerIndicator is a com.viewpagerindicator.TitlePageIndicator
runStatsPagerIndicator.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPagerIndicator));

Und die Klasse:

class ViewInScrollViewTouchHelper implements View.OnTouchListener {

    private final ScrollView scrollView;
    private final View viewInScrollView;
    int dragthreshold = 30;
    int downX;
    int downY;

    public ViewInScrollViewTouchHelper(View viewInScrollView) {

        if (viewInScrollView == null) {
            throw new IllegalArgumentException("viewInScrollView cannot be null.");
        }

        ViewParent parent = viewInScrollView.getParent();
        ScrollView scrollView = null;
        do {
            if (parent instanceof ScrollView) {
                scrollView = (ScrollView) parent;
                break;
            }
        } while(parent != null && (parent = parent.getParent()) != null);

        if (scrollView == null) {
            throw new IllegalArgumentException("View does not have a ScrollView in its parent hierarchy.");
        }

        this.scrollView = scrollView;
        this.viewInScrollView = viewInScrollView;
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                break;
            case MotionEvent.ACTION_MOVE:
                int distanceX = Math.abs((int) event.getRawX() - downX);
                int distanceY = Math.abs((int) event.getRawY() - downY);

                if (distanceY > distanceX && distanceY > dragthreshold) {
                    viewInScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                    scrollView.getParent().requestDisallowInterceptTouchEvent(true);
                } else if (distanceX > distanceY && distanceX > dragthreshold) {
                    viewInScrollView.getParent().requestDisallowInterceptTouchEvent(true);
                    scrollView.getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                scrollView.getParent().requestDisallowInterceptTouchEvent(false);
                viewInScrollView.getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return false;
    }
}
Jon Willis
quelle
1
public class WrapContentHeightViewPager extends ViewPager {

public WrapContentHeightViewPager(Context context) {
    super(context);
}

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

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int height = 0;
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));

        int h = child.getMeasuredHeight();
        if (h > height) height = h;
    }

    heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);

    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

}}

@Override public boolean onTouch (View v, MotionEvent-Ereignis) {

    int dragthreshold = 30;
    int downX = 0;
    int downY = 0;

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = (int) event.getRawX();
            downY = (int) event.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            int distanceX = Math.abs((int) event.getRawX() - downX);
            int distanceY = Math.abs((int) event.getRawY() - downY);

            if (distanceY > distanceX && distanceY > dragthreshold) {
                mViewPager.getParent().requestDisallowInterceptTouchEvent(false);
                mScrollView.getParent().requestDisallowInterceptTouchEvent(true);
            } else if (distanceX > distanceY && distanceX > dragthreshold) {
                mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
                mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            mScrollView.getParent().requestDisallowInterceptTouchEvent(false);
            mViewPager.getParent().requestDisallowInterceptTouchEvent(false);
            break;
    }
    return false;

}

Verwenden Sie über zwei und es wird sehr gut funktionieren. Ich habe auch in meinem Code verwendet.

Silvans Solanki
quelle
1

Bei diesem Ansatz lasse ich den ViewPagerBildlauf in die XRichtung, ohne dass ich mir Sorgen machen muss, dass der ScrollView(übergeordnete) Benutzer das Ereignis stiehlt und den aktuellen Bildlauf abbricht. Noch wichtiger ist, dass dadurch auch der Bereich, in dem ViewPagersich der befindet, in der YRichtung scrollbar bleibt .

public class CustomViewPager extends ViewPager {

private GestureDetector     mGestureDetector;
View.OnTouchListener        mGestureListener;

public CustomViewPager(Context context, AttributeSet attrs) {
    super(context, attrs);
    mGestureDetector = new GestureDetector(context, new Detector());
}

@Override
public boolean onTouchEvent(MotionEvent motionEvent) {

    mGestureDetector.onTouchEvent(motionEvent);
    return super.onTouchEvent(motionEvent);
}

class Detector extends SimpleOnGestureListener {

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {

        requestDisallowInterceptTouchEvent(true);

        return false;
    }
}
}
Slickelito
quelle
Es ist etwas unglücklich, ViewPager hier zu unterordnen. Ich stelle mir vor, dies könnte an einen View.OnTouchListener angepasst werden. Ich werde vorerst upvoten!
Jon Willis
0

Keine der Lösungen hier hat für mich gut funktioniert, da es sich entweder um die Modifikation der ViewPagerBerührungslogik oder ScrollViewder Logik handelt. Ich musste beides implementieren, jetzt funktioniert es wie ein Zauber.

public class TouchGreedyViewPager extends ViewPager {
    private float xDistance, yDistance, lastX, lastY;

    public TouchGreedyViewPager(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) / 3; // favor X events
                lastX = curX;
                lastY = curY;
                if (xDistance > yDistance) {
                    return true;
                }
        }

        return super.onInterceptTouchEvent(ev);
    }
}

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

    public TouchHumbleScrollView(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) / 3; // favor X events
                lastX = curX;
                lastY = curY;
                if (xDistance > yDistance) {
                    return false;
                }
        }

        return super.onInterceptTouchEvent(ev);
    }
}
Dmide
quelle
-1

Sie können die InstantiateItem-Methode in Ihrer PagerAdapter-Klasse überschreiben, indem Sie jeder Seite eine scrollView hinzufügen. Hier ist ein Code:

@Override
public Object instantiateItem(View collection, int position) {
    ScrollView sc = new ScrollView(cxt);
    sc.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    sc.setFillViewport(true);
    TextView tv = new TextView(cxt);
    tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    tv.setText(pages[position]);
    tv.setPadding(5,5,5,5);         
    sc.addView(tv);
    ((ViewPager) collection).addView(sc);

    return sc;
    }
Marcin S.
quelle
Bitte fassen Sie die wichtigsten Punkte hier zusammen oder zitieren Sie sie. Link Rot passiert.
DegDwight