ViewPager внутри ScrollView не прокручивает корректность

88

У меня есть «страница», на которой есть несколько компонентов, и чье содержимое длиннее, чем высота устройства. Хорошо, просто поместите весь макет (всю страницу) внутрь 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>
электрический
источник
Проверьте ответ здесь: stackoverflow.com/questions/9034030/viewpager-in-scrollview/…
Андрей Дмитренко
Пожалуйста, покажите мой ответ по этой ссылке: stackoverflow.com/questions/7381360/…
Герал Александр Торрес

Ответы:

60

У меня такая же проблема. Мое решение заключалось в вызове 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);
    }
});
Лучший художник
источник
1
это вроде работает. Я бы хотел, чтобы вид прокрутки двигался вертикально, даже если на пейджере начинается прикосновение, если движение «в основном» вертикальное
Неманья Ковачевич
3
необходимо ли иметь requestDisallowInterceptTouchEvent как в onTouch, так и в onPageScrolled, или это просто перебор? Можем ли мы получить те же результаты, переместив requestDisallowInterceptTouchEvent только в onPageScrollStateChanged?
craigrs84
1
У меня такая же проблема, и я нашел это лучшее решение, надеюсь, это сработает для вас. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Джитендра Натх
как я могу переопределить событие onClick? он конфликтует с on Touch?
Kiddo
4
какой здесь пейджер и mpager мне нужно ввести?
Суджитрао
30

Дальнейшее чтение показало, что есть проблемы с прокруткой компонентов внутри прокручиваемых компонентов.

Одно из решений - «отключить» вертикальную прокрутку ScrollView в области содержащегося прокручиваемого компонента, в моем случае ViewPager.

Код этого решения подробно описан здесь (и он просто великолепен!)

электрический
источник
к какому решению на этой странице мне следует обратиться?
Harsha MV
1
requestDisallowInterceptTouchEvent - это то, что вы ищете.
yahya
2
У меня такая же проблема, и я нашел это лучшее решение, надеюсь, это сработает для вас. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Джитендра Натх
спасибо за ссылку, решение, которое я нашел там, по-прежнему доставляло мне проблемы с не идеально горизонтальными свайпами в окне просмотра, поэтому я немного изменил его, чтобы заставить его работать: stackoverflow.com/a/33696740/2093236
Dmide
15

Вот решение:

    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 при нажатии и вычисление расстояния при перетаскивании, чтобы определить, какой путь мы хотим пройти. Поиграйте с порогом перетаскивания, чтобы оптимизировать его для вашего случая.


источник
У меня была такая же проблема. Это единственное сработало. Хорошая работа ... :)
rawcoder064 06
Для меня это лучшее решение. Это не помешает скольжению ScrollView вверх и вниз.
Лей Гуо
Для меня это лучшее решение. Также у нас есть возможность настроить пороговое значение в этом решении.
Vinayak
не работает для меня, потому что scrollview перехватывает касание до того, как будет достигнуто это заявление об отмене
AdrianoCelentano
Отличная работа, и если вы хотите использовать это с Wrapcontentviewpager, тогда он также работает очень хорошо,
Сильванс Соланки
4

С помощью 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.

mrb
источник
1
должно быть onPageScrollStateChanged вместо onPageSelected?
craigrs84
2

Я адаптировал решение от @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;
    }
}
Джон Уиллис
источник
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) {

    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;

}

Используйте выше двух, и он будет работать очень хорошо. Я тоже использовал в своем коде.

Сильванс Соланки
источник
1

Таким образом, с этим подходом я позволяю 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;
    }
}
}
Slickelito
источник
Отчасти неудачно создавать здесь подкласс ViewPager. Я предполагаю, что это можно адаптировать к View.OnTouchListener. Я сейчас проголосую за!
Джон Уиллис,
0

Ни одно из решений здесь не сработало для меня, потому что это либо модификация 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);
    }
}
Дмиде
источник
-1

Вы можете переопределить метод 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;
    }
Марцин С.
источник
Пожалуйста, подведите итоги или процитируйте самые важные моменты прямо здесь. Ссылка гниет бывает.
ЯegDwight