ViewPager à l'intérieur d'un ScrollView ne défile pas correctement

88

J'ai une «page» qui contient un certain nombre de composants et dont le contenu est plus long que la hauteur de l'appareil. Très bien, il suffit de mettre toute la mise en page (toute la page) dans un ScrollView, pas de problème.

L'un des composants est un ViewPager. Le rendu est correct, mais la réponse à un balayage / lancer ne fonctionne pas correctement, elle est instable et ne fonctionne pas toujours. Cela semble être `` confondu '' avec le ScrollView, donc ne fonctionne à 100% que lorsque vous lancez une ligne horizontale exacte.

Si je supprime le ScrollView, le ViewPager répond parfaitement.

J'ai fait une recherche et je n'ai pas trouvé cela comme un défaut connu. Quelqu'un d'autre a-t-il vécu cela?

  • Version de la plate-forme: 1.6
  • Bibliothèque de compatibilité v4.
  • Appareil: HTC Incredible S

Vous trouverez ci-dessous un exemple de code avec lequel tester, commenter ScrollViewpour le voir fonctionner correctement.

Activité:

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

Disposition:

<?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>
électriqueSunny
la source
Vérifiez la réponse ici: stackoverflow.com/questions/9034030/viewpager-in-scrollview/…
Andrew Dmytrenko
Veuillez montrer ma réponse dans ce lien: stackoverflow.com/questions/7381360/...
Geral Alexander Torres

Réponses:

60

J'ai eu le même problème. Ma solution a été d'appeler requestDisallowInterceptTouchEventlorsque le défilement ViewPager a commencé.

Voici mon 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);
    }
});
Le meilleur artiste
la source
1
c'est une sorte de travail. Je voudrais que la vue de défilement aille verticalement même si le toucher commence sur le téléavertisseur si le mouvement est "principalement" vertical
Nemanja Kovacevic
3
est-il nécessaire d'avoir requestDisallowInterceptTouchEvent à la fois dans onTouch et onPageScrolled ou est-ce juste exagéré? Pourrions-nous obtenir les mêmes résultats en déplaçant requestDisallowInterceptTouchEvent vers uniquement onPageScrollStateChanged?
craigrs84
1
J'ai le même problème, et j'ai trouvé cette meilleure solution, j'espère que ce travail pour vous. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
comment puis-je toujours ignorer l'événement onClick? il entre en conflit avec sur Touch?
Kiddo
4
quel est le téléavertisseur et le mpager ici dont j'ai besoin pour donner l'entrée?
Sujithrao
30

Une lecture plus approfondie a révélé qu'il existe des problèmes avec les composants de défilement à l'intérieur des composants de défilement.

Une solution est de «désactiver» le défilement vertical du ScrollView sur la zone du composant scrollable contenu, dans mon cas un ViewPager.

Le code de cette solution est détaillé ici (et c'est tout simplement génial!)

électriqueSunny
la source
à quelle solution de cette page dois-je me référer?
Harsha MV du
1
requestDisallowInterceptTouchEvent est celui que vous recherchez.
yahya
1
J'ai le même problème, et j'ai trouvé cette meilleure solution, j'espère que ce travail pour vous. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
merci pour le lien, la solution que j'ai trouvée là-bas m'a toujours posé des problèmes avec des balayages pas parfaitement horizontaux dans le viewpager, donc je l'ai un peu modifié pour le faire fonctionner: stackoverflow.com/a/33696740/2093236
Dmide
15

Voici une solution:

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

Fondamentalement, définir les valeurs X, Y lorsque vous appuyez vers le bas et calculer la distance tout en faisant glisser pour déterminer dans quelle direction nous voulons aller. Jouez avec le seuil de glissement pour optimiser votre cas.


la source
J'ai eu le même problème, c'est la seule solution qui fonctionne. Bon travail ... :)
rawcoder064
C'est la meilleure solution pour moi. Cela ne dérangera pas le ScrollView glissant de haut en bas.
Lei Guo
C'est la meilleure solution pour moi. Nous avons également la possibilité de configurer la valeur de seuil dans cette solution.
Vinayak
ne fonctionne pas pour moi, car la vue de défilement intercepte le toucher avant que cette déclaration d'annulation ne soit atteinte
AdrianoCelentano
Excellent travail, et si vous voulez l'utiliser avec Wrapcontentviewpager, cela fonctionne aussi très bien
Silvans Solanki
4

Avec un ViewPager, vous pouvez capturer les événements de changement de page et empêcher le ScrollView d'intercepter l'événement tactile qui a provoqué le changement de page.

C'est très simple, en utilisant ViewGroup.requestDisallowInterceptTouchEvent(boolean). Il permet également à l'utilisateur de faire glisser le ScrollView même s'il commence un glissement sur le ViewPager, mais le glissement horizontal sur le pager fonctionnera toujours sans que le ScrollView n'interfère.

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

    }

Cela fonctionne pour moi avec la bibliothèque Android Support v4 sur Android 2.3.4 et 4.2.1.

mrb
la source
1
est-il censé être onPageScrollStateChanged au lieu de onPageSelected?
craigrs84 le
2

J'ai adapté la solution de @Michael Herbig L'avantage de cela est que cela fonctionne sur n'importe quelle vue qui le permet setOnTouchListener, pas seulement un ViewPager (par exemple ViewPagerIndicator) et c'est une classe autonome.

Exemple d'utilisation:

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

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

Et la classe:

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
la source
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, événement MotionEvent) {

    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;

}

Utilisez plus de deux et cela fonctionnera très bien. J'ai également utilisé dans mon code.

Silvans Solanki
la source
1

Donc, avec cette approche, je laisse le ViewPagerdéfilement dans la Xdirection sans avoir à m'inquiéter du fait que le ScrollView(parent) vole l'événement et annule le défilement actuel. Plus important encore, cela laisse également la zone où ViewPagerse trouve le défilement dans la Ydirection.

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
la source
Un peu malheureux de sous-classer ViewPager ici. J'imagine que cela pourrait être adapté à un View.OnTouchListener. Je vais voter pour le moment!
Jon Willis
0

Aucune des solutions ici n'a bien fonctionné pour moi parce que c'est soit la modification de ViewPagerla logique tactile de 's ou la logique de ScrollView' s. J'ai dû mettre en œuvre les deux, maintenant cela fonctionne comme un charme.

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
la source
-1

Vous pouvez remplacer la méthode instantiateItem dans votre classe PagerAdapter en ajoutant un scrollView à chaque page. Voici un 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.
la source
Veuillez résumer ou citer les points les plus importants ici. La pourriture des liens se produit.
ЯegDwight