vertical studio propiedades linearlayout funciona elementos desplazamiento configurar como barra amontonados android scrollview android-viewpager

android - studio - ViewPager dentro de ScrollView no se desplaza correctamente



scroll layout android studio (9)

Tengo una ''página'' que tiene una cantidad de componentes y cuyo contenido es más largo que la altura del dispositivo. Bien, simplemente 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 deslizar / arrojar no está funcionando correctamente, está inquieto y no siempre funciona. Parece que se está "confundiendo" con ScrollView , por lo que solo funciona al 100% cuando arrojas una línea horizontal exacta.

Si elimino ScrollView , ViewPager responde perfectamente.

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

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

A continuación se muestra un código de ejemplo para probar con, comentar ScrollView para ver que funcione 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>


Aquí hay 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 establece los valores X, Y cuando presiona hacia abajo, y calcula la distancia mientras arrastra para determinar hacia dónde nos gustaría ir. Juega con el dragthreshold para optimizar tu caso.


Con un 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, usando ViewGroup.requestDisallowInterceptTouchEvent(boolean) . También permite al usuario arrastrar el ScrollView, incluso si comienzan a arrastrar en el ViewPager, pero el arrastre horizontal en el buscapersonas funcionará 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 Android Support v4 en Android 2.3.4 y 4.2.1.


Entonces, con este enfoque, dejo que ViewPager desplace en la dirección X sin tener que preocuparme por el ScrollView (principal) que roba el evento y cancela el desplazamiento actual. Más importante aún, esto también deja el área donde se encuentra el ViewPager desplazable en la dirección Y

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


He adaptado la solución de @Michael Herbig. La ventaja de esto es que funciona en cualquier vista que permita setOnTouchListener , no solo ViewPager (por ejemplo, ViewPagerIndicator) y que sea una clase independiente.

Ejemplo de uso:

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


Lectura adicional ha revelado que hay problemas con los componentes de desplazamiento dentro de los componentes de desplazamiento.

Una solución es "desactivar" 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 here (¡y es simplemente genial!)


Ninguna de las soluciones aquí funcionó bien para mí porque es la modificación de la lógica táctil de ViewPager 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); } }


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


Yo tuve el mismo problema. Mi solución fue llamar a requestDisallowInterceptTouchEvent cuando se inició 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); } });


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

Usa los dos anteriores y funcionará muy bien. Lo he usado en mi código también.