ViewPager ภายใน ScrollView ไม่เลื่อน Correclty


88

ฉันมี 'เพจ' ที่มีส่วนประกอบจำนวนหนึ่งและเนื้อหาของใครยาวเกินความสูงของอุปกรณ์ ดีเพียงวางเค้าโครงทั้งหมด (ทั้งหน้า) ไว้ใน a ScrollViewก็ไม่มีปัญหา

หนึ่งในส่วนประกอบคือไฟล์ViewPager. สิ่งนี้แสดงผลได้อย่างถูกต้อง แต่การตอบสนองต่อการปัด / ปัดทำงานไม่ถูกต้องมันกระวนกระวายใจและไม่ได้ผลเสมอไป ดูเหมือนว่าจะ 'สับสน' กับสิ่งScrollViewนี้ดังนั้นจึงใช้ได้ 100% เมื่อคุณพุ่งไปในเส้นแนวนอนเท่านั้น

หากฉันลบออกScrollViewViewPager จะตอบสนองอย่างสมบูรณ์

ฉันได้ค้นหารอบ ๆ และไม่พบว่านี่เป็นข้อบกพร่องที่ทราบ มีใครมีประสบการณ์นี้อีกบ้าง?

  • เวอร์ชันแพลตฟอร์ม: 1.6.2
  • ไลบรารีที่เข้ากันได้ v4.2
  • อุปกรณ์: 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 Alexander Torres

คำตอบ:


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
นี่คือการทำงาน ฉันต้องการให้มุมมองแบบเลื่อนไปในแนวตั้งแม้ว่าการสัมผัสจะเริ่มต้นในการดูเพจเจอร์หากการเคลื่อนไหวเป็นแนวตั้ง "ส่วนใหญ่"
Nemanja Kovacevic

3
จำเป็นหรือไม่ที่จะต้องมี requestDisallowInterceptTouchEvent ทั้งใน onTouch และ onPageScrolled หรือว่า overkill? เราจะได้ผลลัพธ์เดียวกันหรือไม่โดยการย้าย requestDisallowInterceptTouchEvent ไปที่ onPageScrollStateChanged เท่านั้น
craigrs84

1
ฉันมีปัญหาเดียวกันและพบวิธีแก้ปัญหาที่ดีที่สุดหวังว่าจะได้ผลสำหรับคุณ bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath

ฉันจะยังคงลบล้างเหตุการณ์ onClick ได้อย่างไร มันขัดแย้งกับบน Touch?
Kiddo

4
เพจเจอร์และ mpager คืออะไรที่นี่ฉันต้องให้อินพุต?
Sujithrao

30

การอ่านเพิ่มเติมพบว่ามีปัญหาเกี่ยวกับส่วนประกอบการเลื่อนภายในส่วนประกอบการเลื่อน

ทางออกหนึ่งคือการ 'ปิดใช้งาน' การเลื่อนแนวตั้งของ ScrollView บนพื้นที่ของส่วนประกอบที่เลื่อนได้ในกรณีของฉันคือ ViewPager

รหัสสำหรับโซลูชันนี้มีรายละเอียดอยู่ที่นี่ (และยอดเยี่ยมมาก!)


ฉันควรอ้างถึงโซลูชันใดในหน้านั้น
Harsha MV

1
requestDisallowInterceptTouchEvent คือสิ่งที่คุณต้องการ
yahya

2
ฉันมีปัญหาเดียวกันและพบวิธีแก้ปัญหาที่ดีที่สุดหวังว่าจะได้ผลสำหรับคุณ bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath

ขอบคุณสำหรับลิงค์วิธีแก้ปัญหาที่ฉันพบยังคงทำให้ฉันมีปัญหากับการปัดนิ้วแนวนอนใน viewpager ไม่สมบูรณ์ดังนั้นฉันจึงแก้ไขเล็กน้อยเพื่อให้มันใช้งานได้: 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 เมื่อคุณกดลงและคำนวณระยะทางขณะลากเพื่อกำหนดว่าเราต้องการไปทางใด เล่นกับ dragthreshold เพื่อปรับให้เหมาะกับกรณีของคุณ


ฉันมีปัญหาเดียวกันนี่เป็นทางออกเดียวที่ใช้ได้ Good work ... :)
rawcoder064

นี่เป็นทางออกที่ดีที่สุดสำหรับฉัน จะไม่รบกวน ScrollView ที่เลื่อนขึ้นและลง
Lei Guo

นี่เป็นทางออกที่ดีที่สุดสำหรับฉัน นอกจากนี้เรายังมีตัวเลือกในการกำหนดค่า threshold ในโซลูชันนี้
Vinayak

ไม่ได้ผลสำหรับฉันเพราะ scrollview ขัดขวางการสัมผัสก่อนที่จะถึงคำสั่งยกเลิกนี้
AdrianoCelentano

เยี่ยมมากและถ้าคุณต้องการใช้กับ Wrapcontentviewpager มันก็ใช้งานได้ดีเช่นกัน
Silvans Solanki

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


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 บูลีนสาธารณะ 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

คุณสามารถแทนที่วิธีการ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.