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.