ScrollView 내의 ViewPager가 올바르게 스크롤되지 않습니다.


88

많은 구성 요소가있는 '페이지'가 있으며 콘텐츠가 장치 높이보다 더 깁니다. ScrollView좋습니다. 모든 레이아웃 (전체 페이지)을 , 문제 없습니다.

구성 요소 중 하나는 ViewPager. 이것은 올바르게 렌더링되지만 스 와이프 / 플링에 대한 응답이 올바르게 수행되지 않고 불안정하고 항상 작동하지는 않습니다. 와 '혼란스러워'하는 것 같아서 ScrollView정확한 수평선으로 날아갈 때만 100 % 작동합니다.

를 제거하면 ScrollViewViewPager가 완벽하게 응답합니다.

주변을 검색했지만 알려진 결함으로 발견되지 않았습니다. 다른 사람이 이것을 경험 한 적이 있습니까?

  • 플랫폼 버전 : 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/7381360/...
GERAL 알렉산더 토레스

답변:


60

나는 같은 문제가 있었다. 내 해결책은 requestDisallowInterceptTouchEventViewPager 스크롤이 시작될 때 호출 하는 것입니다.

내 코드는 다음과 같습니다.

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
이것은 일종의 효과입니다. 이동은 "대부분"수직이면 나는 터치 뷰 페이저에서 시작되는 경우에도 수직으로 이동 스크롤 뷰를 싶습니다
네마냐 Kovacevic

3
onTouch와 onPageScrolled 모두에서 requestDisallowInterceptTouchEvent가 필요합니까? 아니면 과잉입니까? requestDisallowInterceptTouchEvent를 onPageScrollStateChanged로만 이동하여 동일한 결과를 얻을 수 있습니까?
craigrs84

1
나는 같은 문제가 있으며 최선의 해결책을 찾았습니다. bitbucket.org/NxAlex/swipes-navigation-demo/src/...
Jitendra 나스

onClick 이벤트를 어떻게 재정의 할 수 있습니까? 터치와 충돌합니까?
얘야

4
여기에 입력을 제공해야하는 호출기와 mpager는 무엇입니까?
Sujithrao

30

추가 읽기를 통해 스크롤 구성 요소 내부의 스크롤 구성 요소에 문제가 있음이 밝혀졌습니다.

한 가지 해결책은 포함 된 스크롤 가능 구성 요소 (제 경우에는 ViewPager) 영역에서 ScrollView의 세로 스크롤을 '비활성화'하는 것입니다.

이 솔루션에 대한 코드는 여기 에 자세히 설명되어 있습니다 (단순히 훌륭합니다!).


해당 페이지의 어떤 솔루션을 참조해야합니까?
Harsha MV

1
requestDisallowInterceptTouchEvent는 당신이 찾는 것입니다.
YAHYA

2
나는 같은 문제가 있으며 최선의 해결책을 찾았습니다. bitbucket.org/NxAlex/swipes-navigation-demo/src/...
Jitendra 나스

링크 덕분에 내가 찾은 솔루션은
뷰 페이저

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 값을 설정하고 드래그하는 동안 거리를 계산하여 가고 싶은 방향을 결정합니다. 귀하의 경우에 최적화하기 위해 dragthreshold를 가지고 놀아보십시오.


나는 같은 문제가 있었는데 이것이 유일한 해결책이었습니다. 잘 했어 ... :)
rawcoder064

이것이 저에게 가장 적합한 솔루션입니다. ScrollView가 위아래로 슬라이딩하는 것을 방해하지 않습니다.
Lei Guo

이것이 저에게 가장 적합한 솔루션입니다. 또한이 솔루션에서 임계 값을 구성하는 옵션이 있습니다.
Vinayak

있는 ScrollView의 차단이 취소 문 앞에 터치에 도달하기 때문에, 나를 위해 작동하지 않습니다
AdrianoCelentano

멋진 작업, 당신은 Wrapcontentviewpager 다음도 아주 잘 작동에 이것을 사용하려는 경우
Silvans Solanki

4

ViewPager를 사용하면 페이지 변경 이벤트를 캡처하고 ScrollView가 페이지 변경을 일으킨 터치 이벤트를 가로채는 것을 방지 할 수 있습니다.

이것은 ViewGroup.requestDisallowInterceptTouchEvent(boolean). 또한 사용자가 ViewPager에서 드래그를 시작하더라도 ScrollView를 드래그 할 수 있지만 페이저에서 수평 드래그는 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 2.3.4 및 4.2.1의 Android 지원 v4 라이브러리에서 저에게 적합합니다.


1
onPageSelected 대신 onPageScrollStateChanged이어야합니까?
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 event) {

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

여기 ViewPager를 하위 클래스로 만드는 것은 불행한 일입니다. 나는 이것이 View.OnTouchListener에 적용될 수 있다고 생각합니다. 지금은 찬성하겠습니다!
Jon Willis

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

각 페이지에 scrollView를 추가하여 PagerAdapter 클래스의 instantiateItem 메서드를 재정의 할 수 있습니다. 다음은 코드입니다.

@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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.