ViewPager all'interno di un ScrollView non scorre correttamente


88

Ho una "pagina" che ha una serie di componenti e il cui contenuto è più lungo dell'altezza del dispositivo. Bene, metti tutto il layout (l'intera pagina) all'interno di un ScrollView, nessun problema.

Uno dei componenti è un file ViewPager. Viene visualizzato correttamente, ma la risposta a uno swipe / fling non funziona correttamente, è nervosa e non sempre funziona. Sembra che si stia "confondendo" con ScrollView, quindi funziona solo al 100% quando ti lanci in una linea orizzontale esatta.

Se rimuovo il ScrollView, ViewPager risponde perfettamente.

Ho fatto una ricerca in giro e non ho trovato questo come un difetto noto. Qualcun altro ha sperimentato questo?

  • Versione piattaforma: 1.6.0
  • Libreria di compatibilità v4.
  • Dispositivo: HTC Incredible S.

Di seguito è riportato un codice di esempio con cui testare, commentare ScrollViewper vederlo funzionare correttamente.

Attività:

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

Disposizione:

<?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>


Si prega di mostrare la mia risposta a questo link: stackoverflow.com/questions/7381360/…
Geral Alexander Torres

Risposte:


60

Ho avuto lo stesso problema. La mia soluzione era chiamare requestDisallowInterceptTouchEventquando è iniziato lo scorrimento di ViewPager.

Ecco il mio codice:

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

1
questo è tipo di lavoro. Vorrei che la visualizzazione a scorrimento andasse verticalmente anche se il tocco inizia sul cercapersone di visualizzazione se lo spostamento è "prevalentemente" verticale
Nemanja Kovacevic

3
è necessario avere requestDisallowInterceptTouchEvent sia in onTouch che in onPageScrolled o è solo eccessivo? È possibile ottenere gli stessi risultati spostando requestDisallowInterceptTouchEvent solo su onPageScrollStateChanged?
craigrs84

1
Ho lo stesso problema e ho trovato questa soluzione migliore, spero che funzioni per te. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath

come posso ancora sovrascrivere l'evento onClick? è in conflitto con Touch?
Kiddo

4
qual è il cercapersone e mpager qui ho bisogno di dare l'input?
Sujithrao

30

Ulteriori letture hanno rivelato che ci sono problemi con i componenti di scorrimento all'interno dei componenti di scorrimento.

Una soluzione è 'disabilitare' lo scorrimento verticale di ScrollView sull'area del componente scorrevole contenuto, nel mio caso un ViewPager.

Il codice per questa soluzione è dettagliato qui (ed è semplicemente geniale!)


a quale soluzione in quella pagina devo fare riferimento?
Harsha MV

1
requestDisallowInterceptTouchEvent è quello che cerchi.
yahya

2
Ho lo stesso problema e ho trovato questa soluzione migliore, spero che funzioni per te. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath

grazie per il link, la soluzione che ho trovato lì ancora mi ha dato problemi con non fendenti perfettamente orizzontali in viewpager, quindi ho modificato un po 'per farlo funzionare: stackoverflow.com/a/33696740/2093236
Dmide

15

Ecco una soluzione:

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

Fondamentalmente impostando i valori X, Y quando si preme verso il basso e calcolando la distanza durante il trascinamento per determinare in quale direzione vorremmo andare. Gioca con la soglia di trascinamento per ottimizzare per il tuo caso.


Ho avuto lo stesso problema Questa è l'unica soluzione che ha funzionato. Ottimo lavoro ... :)
rawcoder064

Questa è la soluzione migliore per me. Non disturberà lo ScrollView che scorre su e giù.
Lei Guo

Questa è la soluzione migliore per me. Inoltre abbiamo la possibilità di configurare il valore di soglia in questa soluzione.
Vinayak

non funziona per me, perché lo scrollview intercetta il tocco prima che questa dichiarazione di annullamento sia raggiunta
AdrianoCelentano

Ottimo lavoro, e se vuoi usarlo con Wrapcontentviewpager, funziona anche molto bene
Silvans Solanki

4

Con un ViewPager, puoi acquisire eventi di cambio pagina e impedire a ScrollView di intercettare l'evento tocco che ha causato il cambio di pagina.

Questo è molto semplice, usare ViewGroup.requestDisallowInterceptTouchEvent(boolean). Consente inoltre all'utente di trascinare lo ScrollView anche se avvia un trascinamento sul ViewPager, ma il trascinamento orizzontale sul cercapersone continuerà a funzionare senza che ScrollView interferisca.

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

    }

Questo funziona per me con la libreria Android Support v4 su Android 2.3.4 e 4.2.1.


1
dovrebbe essere onPageScrollStateChanged invece di onPageSelected?
craigrs84

2

Ho adattato la soluzione di @Michael Herbig Il vantaggio di questo è che funziona su qualsiasi vista che consente setOnTouchListener, non solo un ViewPager (ad esempio ViewPagerIndicator) ed è una classe autonoma.

Utilizzo di esempio:

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

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

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

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

    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;

}

Usa più di due e funzionerà molto bene. Ho usato anche nel mio codice.


1

Quindi, con questo approccio, lascio ViewPagerscorrere nella Xdirezione senza doversi preoccupare che il ScrollView(genitore) rubi l'evento e annulli lo scorrimento corrente. Ancora più importante, questo lascia anche l'area in cui ViewPagersi trova scorrevole nella Ydirezione.

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

Un po 'sfortunato sottoclasse ViewPager qui. Immagino che questo potrebbe essere adattato a View.OnTouchListener. Upvoto per ora!
Jon Willis

0

Nessuna delle soluzioni qui ha funzionato bene per me perché è la modifica della ViewPagerlogica tattile di o ScrollViewdella logica di. Ho dovuto implementarli entrambi, ora funziona a meraviglia.

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

-1

È possibile sovrascrivere il metodo instantiateItem nella classe PagerAdapter aggiungendo uno scrollView a ciascuna pagina. Ecco un codice:

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

Si prega di riassumere o citare i punti più importanti proprio qui. Il marciume del collegamento accade.
ЯegDwight
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.