ViewPager внутри ScrollView - вертикальная прокрутка не работает
Итак, у меня есть этот ScrollView
с одним дочерним - a LinearLayout
, у которого два ребенка: a TextView
и a ViewPager
. ViewPager
содержит макет со многими элементами, поэтому мне нужна возможность прокрутки по вертикали. Только страницы в ViewPager
можно прокручивать по горизонтали (то есть: я хотел бы прокручивать по горизонтали только в пределах ViewPager
). Этот TextView
не должен прокручиваться по горизонтали, но должен прокручиваться вместе с моим ViewPager
.
Простой? Нет.
Я видел очень похожие проблемы при появлении StackOverflow (здесь, здесь и здесь и здесь).
Ни одно из предлагаемых решений не работает для меня: (
То, что я вижу, this < - my sweet UI:), но я не могу прокручивать по вертикали: (
Встраивание ScrollViews внутри ViewPager не является опцией - настройка UI запрещает это.
Может быть, это что-то с программным заполнением каждой страницы в пейджере? Хммм...
Любые предложения будут оценены.
Мой код:
activity_main.xml:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:color/white"
android:fillViewport="true" >
<LinearLayout
android:id="@+id/linearLayoutGeneral"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tvText"
android:layout_width="fill_parent"
android:layout_height="200dp"
android:text="Test text" />
<android.support.v4.view.ViewPager
android:id="@+android:id/viewpager"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</android.support.v4.view.ViewPager>
</LinearLayout>
</ScrollView>
каждая страница в ViewPager имеет этот макет:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/layoutData"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
расположение одного элемента на такой странице:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="40dp"
android:gravity="center"
android:orientation="vertical"
android:background="@android:color/white" >
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Large Text"
android:background="@android:color/black"
android:textColor="@android:color/white"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
Один фрагмент страницы также очень прост:
public class DayFragment extends Fragment {
private static final String TAG = DayFragment.class.getSimpleName();
public String tag;
LinearLayout data;
View mView;
final int ROWS_NUM = 60;
public DayFragment() {
}
/**
* (non-Javadoc)
*
* @see android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
* android.view.ViewGroup, android.os.Bundle)
*/
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) {
// We have different layouts, and in one of them this
// fragment containing frame doesn't exist. The fragment
// may still be created from its saved state, but there is
// no reason to try to create its view hierarchy because it
// won't be displayed. Note this is not needed -- we could
// just run the code below, where we would create and return
// the view hierarchy; it would just never be used.
return null;
}
mView = (LinearLayout) inflater.inflate(R.layout.day, container, false);
setUpControls();
generateData();
String text = getArguments().getString("text");
Log.d(TAG, "creating view with text: " + text);
return mView;
}
private void setUpControls() {
data = (LinearLayout) mView.findViewById(R.id.layoutData);
}
private void generateData() {
for (int i = 0; i < ROWS_NUM; i++) {
View v = createRow(i);
data.addView(v);
}
}
private View createRow(int num) {
LayoutInflater inflater = (LayoutInflater) getActivity()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.row, null);
TextView tv = (TextView) v.findViewById(R.id.textView);
tv.setText("Data nr: " + num);
return v;
}
public static DayFragment newInstance(String text) {
Log.d(TAG, "newInstance with text: " + text);
DayFragment f = new DayFragment();
f.tag = text;
// Supply text input as an argument.
Bundle args = new Bundle();
args.putString("text", text);
f.setArguments(args);
return f;
}
}
Ответы
Ответ 1
У меня была проблема, что ViewPager
вел себя странно, и я выяснил причину этого, потому что иногда ScrollView
восстанавливает фокус, а ViewPager
теряет фокус. Если у вас есть ViewPager
в ScrollView
, и вы хотите, чтобы он всегда находился в фокусе, когда вы касаетесь его, а ScrollView
никогда не фокусируется, установка requestDisallowInterceptTouchEvent
делает это. Помогло ли это?
mViewPager= (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(adapter);
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
Ответ 2
Продолжая отвечать на Marius, чтобы разрешить вертикальную прокрутку на родительском объекте:
Я заметил, что когда вы вызываете метод requestDisallowInterceptTouchEvent на начальном прокрутке, он будет блокировать прокрутку по вертикали на родительском представлении при прокрутке по вертикали сверху плеера представления.
Мое решение состояло в том, чтобы вызвать этот метод только после того, как пользователь начал прокручивать по горизонтали на заданное расстояние (переменная "margin" ).
mViewPager.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent e) {
// How far the user has to scroll before it locks the parent vertical scrolling.
final int margin = 10;
final int fragmentOffset = v.getScrollX() % v.getWidth();
if (fragmentOffset > margin && fragmentOffset < v.getWidth() - margin) {
mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
}
return false;
}
});
Ответ 3
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;
}
});
Ответ 4
У меня была такая же проблема
попробуйте мой код, вы должны отключить вертикальную прокрутку и отправить событие в Scrollview:
private int dragThreshold = 10, int downX = 0, int downY = 0;
vPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP){
downX = (int) event.getRawX();
downY = (int) event.getRawY();
return false;
}else if(event.getAction() == MotionEvent.ACTION_MOVE){
int distanceX = Math.abs((int) event.getRawX() - downX);
int distanceY = Math.abs((int) event.getRawY() - downY);
ExceptionHelpers.dLog("OnTouchListener", "distance X : "+distanceX+" , distance Y : "+distanceY);
if(distanceY > distanceX && distanceY > dragThreshold){
v.getParent().requestDisallowInterceptTouchEvent(false);
return true;
}
}
v.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
Также вы можете установить Min Horizontal Scroll By Add:
else {
distanceX > distanceY && distanceX > dragThreshold)
}
И помните, строители → Попробуйте и попробуйте
разработчики (мы) делаем → Подумайте и подумайте и попробуйте
Хорошая блокировка
Ответ 5
Попробуйте это
public static class XScrollDetector extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceY) < Math.abs(distanceX);
}
}
а для viewpager
final GestureDetector mGestureDetector= new GestureDetector(this,new XScrollDetector());
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mScrollView.requestDisallowInterceptTouchEvent(mGestureDetector.onTouchEvent(event));
return false;
}
});
Ответ 6
Горизонтальная и вертикальная прокрутка в одной операции обычно является проблемой. ViewPager обрабатывает это очень хорошо. Я не знаю, есть ли хорошее решение для вашей проблемы. Но я бы предложил избавиться от ScrollView и TextView в вашем основном макете.
Используйте ViewPager в качестве отправной точки для вашего макета и поместите вертикальный ScrollView внутри фрагментов ViewPager.
Ответ 7
Запретить ScrollView для перехвата событий касания. Для этого требуется, чтобы пользователь коснулся внешнего вида ViewPager для прокрутки вверх или вниз.
mScrollView = (ScrollView) findViewById(R.id.scroll_view);
mViewPager= (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(adapter);
mViewPager.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mScrollView.requestDisallowInterceptTouchEvent(true);
return false;
}
});