У меня есть «страница», на которой есть несколько компонентов, и чье содержимое длиннее, чем высота устройства. Хорошо, просто поместите весь макет (всю страницу) внутрь ScrollView
, без проблем.
Один из компонентов - это ViewPager
. Это отображается правильно, но ответ на смахивание / бросок не выполняется правильно, он нервный и не всегда работает. Кажется, что он «путается» с ScrollView
, поэтому работает на 100% только тогда, когда вы бросаете точную горизонтальную линию.
Если я удалю ScrollView
, ViewPager отреагирует отлично.
Я поискал и не нашел это как известный дефект. Кто-нибудь еще испытал это?
- Версия платформы: 1.6
- Библиотека совместимости v4.
- Устройство: HTC Incredible S
Ниже приведен пример кода для тестирования, закомментируйте, ScrollView
чтобы убедиться, что он работает правильно.
Деятельность:
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) {
}
}
Макет:
<?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>
android
scrollview
android-viewpager
электрический
источник
источник
Ответы:
У меня такая же проблема. Мое решение заключалось в вызове
requestDisallowInterceptTouchEvent
при запуске прокрутки ViewPager.Вот мой код:
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); } });
источник
Дальнейшее чтение показало, что есть проблемы с прокруткой компонентов внутри прокручиваемых компонентов.
Одно из решений - «отключить» вертикальную прокрутку ScrollView в области содержащегося прокручиваемого компонента, в моем случае ViewPager.
Код этого решения подробно описан здесь (и он просто великолепен!)
источник
Вот решение:
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; } });
В основном установка значений X, Y при нажатии и вычисление расстояния при перетаскивании, чтобы определить, какой путь мы хотим пройти. Поиграйте с порогом перетаскивания, чтобы оптимизировать его для вашего случая.
источник
С помощью ViewPager вы можете фиксировать события изменения страницы и не допускать перехвата ScrollView события касания, вызвавшего изменение страницы.
Это очень просто, используя
ViewGroup.requestDisallowInterceptTouchEvent(boolean)
. Он также позволяет пользователю перетаскивать ScrollView, даже если он начинает перетаскивание на ViewPager, но горизонтальное перетаскивание на пейджере будет работать без вмешательства ScrollView.@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) { } }); }
У меня это работает с библиотекой Android Support v4 на Android 2.3.4 и 4.2.1.
источник
Я адаптировал решение от @Michael Herbig. Преимущество этого заключается в том, что оно работает с любым представлением, которое позволяет
setOnTouchListener
, а не только с ViewPager (например, ViewPagerIndicator), и это автономный класс.Пример использования:
// runStatsPager is a android.support.v4.view.ViewPager; runStatsPager.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPager)); // runStatsPagerIndicator is a com.viewpagerindicator.TitlePageIndicator runStatsPagerIndicator.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPagerIndicator));
И класс:
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; } }
источник
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) {
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; }
Используйте выше двух, и он будет работать очень хорошо. Я тоже использовал в своем коде.
источник
Таким образом, с этим подходом я позволяю
ViewPager
прокрутке вX
направлении, не беспокоясь о том, чтоScrollView
(родитель) украдет событие и отменит текущую прокрутку. Что еще более важно, это также оставляет область, вViewPager
которой находится, прокручиваемой в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; } } }
источник
Ни одно из решений здесь не сработало для меня, потому что это либо модификация
ViewPager
логики касания, либоScrollView
логики пользователя. Пришлось реализовать оба, теперь это работает как шарм.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); } }
источник
Вы можете переопределить метод instantiateItem в своем классе PagerAdapter, добавив scrollView на каждую страницу. Вот код:
@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; }
источник