ViewPager dentro de un ScrollView no se desplaza correctamente

88

Tengo una 'página' que tiene varios componentes y cuyo contenido es más largo que la altura del dispositivo. Bien, solo coloque todo el diseño (la página completa) dentro de ScrollView, no hay problema.

Uno de los componentes es un ViewPager. Esto se representa correctamente, pero la respuesta a un deslizamiento / lanzamiento no funciona correctamente, es inquietante y no siempre funciona. Parece que se 'confunde' con el ScrollView, por lo que solo funciona al 100% cuando arroja una línea horizontal exacta.

Si ScrollViewelimino el , ViewPager responde perfectamente.

He realizado una búsqueda y no he encontrado esto como un defecto conocido. Alguien más ha experimentado esto?

  • Versión de la plataforma: 1.6
  • Biblioteca de compatibilidad v4.
  • Dispositivo: HTC Incredible S

A continuación se muestra un código de ejemplo para que lo pruebe, comente ScrollViewpara ver que funciona correctamente.

Actividad:

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

Diseño:

<?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>
eléctricoSunny
fuente
Verifique la respuesta aquí: stackoverflow.com/questions/9034030/viewpager-in-scrollview/…
Andrew Dmytrenko
Muestre mi respuesta en este enlace: stackoverflow.com/questions/7381360/…
Geral Alexander Torres

Respuestas:

60

Yo tuve el mismo problema. Mi solución fue llamar requestDisallowInterceptTouchEventcuando comenzó el desplazamiento de ViewPager.

Aquí está mi código:

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);
    }
});
El mejor artista
fuente
1
esto es algo que funciona. Me gustaría que la vista de desplazamiento fuera vertical incluso si el tacto comienza en el buscapersonas de la vista si el movimiento es "mayormente" vertical
Nemanja Kovacevic
3
¿Es necesario tener requestDisallowInterceptTouchEvent tanto en onTouch como en onPageScrolled o es una exageración? ¿Podríamos obtener los mismos resultados moviendo requestDisallowInterceptTouchEvent solo a onPageScrollStateChanged?
craigrs84
1
Tengo el mismo problema y encontré la mejor solución, espero que esto funcione para usted. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
¿Cómo puedo anular el evento onClick? entra en conflicto con on Touch?
Kiddo
4
¿Qué es el buscapersonas y el mpager aquí necesito dar la entrada?
Sujithrao
30

La lectura adicional ha revelado que existen problemas con los componentes de desplazamiento dentro de los componentes de desplazamiento.

Una solución es 'deshabilitar' el desplazamiento vertical de ScrollView en el área del componente desplazable contenido, en mi caso un ViewPager.

El código para esta solución se detalla aquí (¡y es simplemente brillante!)

eléctricoSunny
fuente
¿A qué solución de esa página debo hacer referencia?
Harsha MV
1
requestDisallowInterceptTouchEvent es el que busca.
yahya
2
Tengo el mismo problema y encontré la mejor solución, espero que esto funcione para usted. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
gracias por el enlace, la solución que encontré allí todavía me dio problemas con deslizamientos no perfectamente horizontales en el visor, así que lo modifiqué un poco para que funcione: stackoverflow.com/a/33696740/2093236
Dmide
15

He aquí una solución:

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

Básicamente, estableciendo los valores X, Y cuando presiona hacia abajo y calculando la distancia mientras arrastra para determinar en qué dirección nos gustaría ir. Juega con el umbral de arrastre para optimizarlo para tu caso.


fuente
Tuve el mismo problema Esta es la única solución que funcionó. Buen trabajo ... :)
rawcoder064
Esta es la mejor solución para mí. No molestará al ScrollView deslizándose hacia arriba y hacia abajo.
Lei Guo
Esta es la mejor solución para mí. También tenemos la opción de configurar el valor umbral en esta solución.
Vinayak
no funciona para mí, porque la vista de desplazamiento intercepta el toque antes de que se alcance esta declaración de cancelación
AdrianoCelentano
Gran trabajo, y si quieres usar esto con Wrapcontentviewpager, también está funcionando muy bien
Silvans Solanki
4

Con ViewPager, puede capturar eventos de cambio de página y evitar que ScrollView intercepte el evento táctil que causó el cambio de página.

Esto es muy simple, usar ViewGroup.requestDisallowInterceptTouchEvent(boolean). También permite al usuario arrastrar el ScrollView incluso si inicia un arrastre en el ViewPager, pero el arrastre horizontal en el paginador seguirá funcionando sin que ScrollView interfiera.

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

    }

Esto funciona para mí con la biblioteca de soporte de Android v4 en Android 2.3.4 y 4.2.1.

mrb
fuente
1
¿Se supone que está onPageScrollStateChanged en lugar de onPageSelected?
craigrs84
2

Adapté la solución de @Michael Herbig. La ventaja de esto es que funciona en cualquier vista que lo permita setOnTouchListener, no solo en un ViewPager (por ejemplo, ViewPagerIndicator) y es una clase autónoma.

Uso de ejemplo:

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

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

Y la clase:

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
fuente
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, evento 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;

}

Use arriba de dos y funcionará muy bien. También lo he usado en mi código.

Silvans Solanki
fuente
1

Entonces, con este enfoque, dejo el ViewPagerdesplazamiento en la Xdirección sin tener que preocuparme de que el ScrollView(padre) robe el evento y cancele el desplazamiento actual. Más importante aún, esto también deja el área donde ViewPagerse encuentra desplazable en la Ydirección.

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
fuente
Es un poco desafortunado tener una subclase de ViewPager aquí. Imagino que esto podría adaptarse a un View.OnTouchListener. ¡Votaré a favor por ahora!
Jon Willis
0

Ninguna de las soluciones aquí funcionó bien para mí porque es la modificación de ViewPagerla lógica táctil o la lógica de ScrollView'. Tuve que implementar ambos, ahora funciona como un encanto.

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
fuente
-1

Puede anular el método instantiateItem en su clase PagerAdapter agregando un scrollView a cada página. Aquí hay un código:

@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.
fuente
Resuma o cite los puntos más importantes aquí. La pudrición del enlace ocurre.
ЯegDwight