ViewPager di dalam ScrollView tidak menggulir benar

88

Saya memiliki 'halaman' yang berisi sejumlah komponen, dan konten siapa yang lebih panjang dari tinggi perangkat. Baik, cukup letakkan semua tata letak (seluruh halaman) di dalam ScrollView, tidak masalah.

Salah satu komponennya adalah a ViewPager. Ini dirender dengan benar, tetapi respons terhadap gesekan / lempar tidak bekerja dengan benar, gelisah dan tidak selalu berfungsi. Tampaknya menjadi 'bingung' dengan ScrollView, jadi hanya berfungsi 100% saat Anda melemparkan dalam garis horizontal yang tepat.

Jika saya menghapus ScrollView, ViewPager merespons dengan sempurna.

Saya telah mencari-cari dan belum menemukan ini sebagai cacat yang diketahui. Apakah ada orang lain yang mengalami ini?

  • Versi Platform: 1.6.0
  • Library Kompatibilitas v4.
  • Perangkat: HTC Incredible S

Di bawah ini adalah beberapa contoh kode untuk Anda uji, beri komentar ScrollView untuk melihatnya berfungsi dengan benar.

Aktivitas:

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) {
    }
}

Tata Letak:

<?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>
electricSunny
sumber
Periksa jawabannya di sini: stackoverflow.com/questions/9034030/viewpager-in-scrollview/…
Andrew Dmytrenko
Tolong tunjukkan jawaban saya di tautan ini: stackoverflow.com/questions/7381360/…
Geral Alexander Torres

Jawaban:

60

Saya memiliki masalah yang sama. Solusi saya adalah meneleponrequestDisallowInterceptTouchEvent saat scroll ViewPager dimulai.

Ini kode saya:

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);
    }
});
Artis Terbaik
sumber
1
ini jenis pekerjaan. Saya ingin tampilan gulir bergerak secara vertikal meskipun sentuhan dimulai pada halaman tampilan jika gerakannya "sebagian besar" vertikal
Nemanja Kovacevic
3
apakah perlu requestDisallowInterceptTouchEvent di onTouch dan onPageScrolled atau itu hanya berlebihan? Bisakah kita mendapatkan hasil yang sama dengan memindahkan requestDisallowInterceptTouchEvent ke hanya onPageScrollStateChanged?
craigrs84
1
Saya memiliki masalah yang sama, dan menemukan solusi terbaik ini, semoga ini berhasil untuk Anda. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
bagaimana saya masih bisa mengganti acara onClick? itu bertentangan dengan di Touch?
Kiddo
4
apa pager dan mpager disini yang perlu saya beri masukan?
Sujithrao
30

Bacaan lebih lanjut telah mengungkapkan bahwa ada masalah dengan komponen scrolling di dalam komponen scrolling.

Salah satu solusinya adalah 'menonaktifkan' pengguliran vertikal ScrollView di area komponen yang dapat digulir, dalam kasus saya adalah ViewPager.

Kode untuk solusi ini dirinci di sini (dan ini sangat brilian!)

electricSunny
sumber
solusi mana di halaman itu yang harus saya rujuk?
Harsha MV
1
requestDisallowInterceptTouchEvent adalah yang Anda cari.
yahya
2
Saya memiliki masalah yang sama, dan menemukan solusi terbaik ini, semoga ini berhasil untuk Anda. bitbucket.org/NxAlex/swipes-navigation-demo/src/…
Jitendra Nath
terima kasih untuk tautannya, solusi yang saya temukan di sana masih memberi saya masalah dengan gesekan horizontal yang tidak sempurna di viewpager, jadi saya memodifikasinya sedikit agar berfungsi: stackoverflow.com/a/33696740/2093236
Dmide
15

Inilah solusinya:

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

Pada dasarnya mengatur nilai X, Y saat Anda menekan ke bawah, dan menghitung jarak sambil menyeret untuk menentukan ke mana kita ingin pergi. Bermain-main dengan dragthreshold untuk mengoptimalkan kasus Anda.


sumber
Saya memiliki masalah yang sama, ini adalah satu-satunya solusi yang berhasil. Kerja bagus ... :)
rawcoder064
Ini adalah solusi terbaik untuk saya. Itu tidak akan mengganggu ScrollView yang meluncur ke atas dan ke bawah.
Lei Guo
Ini adalah solusi terbaik untuk saya. Kami juga memiliki opsi untuk mengonfigurasi nilai ambang batas dalam solusi ini.
Vinayak
tidak bekerja untuk saya, karena scrollview memotong sentuhan sebelum pernyataan pembatalan ini tercapai
AdrianoCelentano
Kerja bagus, dan jika Anda ingin menggunakan ini dengan Wrapcontentviewpager maka itu juga bekerja dengan sangat baik
Silvans Solanki
4

Dengan ViewPager, Anda dapat merekam peristiwa perubahan halaman dan mencegah ScrollView mencegat peristiwa sentuh yang menyebabkan halaman berubah.

Ini sangat sederhana, menggunakan ViewGroup.requestDisallowInterceptTouchEvent(boolean). Ini juga memungkinkan pengguna menyeret ScrollView bahkan jika mereka memulai tarik pada ViewPager, tetapi menyeret horizontal pada pager akan tetap berfungsi tanpa gangguan 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) {
            }
        });

    }

Ini berfungsi untuk saya dengan pustaka Dukungan Android v4 di Android 2.3.4 dan 4.2.1.

mrb
sumber
1
apakah seharusnya onPageScrollStateChanged bukan onPageSelected?
craigrs84
2

Saya mengadaptasi solusi dari @Michael Herbig. Keuntungannya adalah ia berfungsi pada tampilan apa pun yang memungkinkan setOnTouchListener , bukan hanya ViewPager (misalnya ViewPagerIndicator) dan ini adalah kelas mandiri.

Contoh penggunaan:

// runStatsPager is a android.support.v4.view.ViewPager;
runStatsPager.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPager));

// runStatsPagerIndicator is a com.viewpagerindicator.TitlePageIndicator
runStatsPagerIndicator.setOnTouchListener(new ViewInScrollViewTouchHelper(runStatsPagerIndicator));

Dan kelasnya:

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;
    }
}
Jon Willis
sumber
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 (Tampilan v, acara 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;

}

Gunakan di atas dua dan itu akan bekerja dengan sangat baik. Saya telah menggunakan kode saya juga.

Silvans Solanki
sumber
1

Jadi dengan pendekatan ini saya membiarkan ViewPagerscroll ke Xarah tanpa harus khawatir tentang ScrollView(orang tua) yang mencuri acara tersebut dan membatalkan scroll saat ini. Lebih penting lagi, ini juga meninggalkan area di mana ViewPagerterletak dapat digulir ke Yarah.

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
sumber
Agak disayangkan untuk subclass ViewPager di sini. Saya membayangkan ini bisa diadaptasi ke View.OnTouchListener. Saya akan memberi suara positif untuk saat ini!
Jon Willis
0

Tak satu pun dari solusi di sini yang bekerja dengan baik untuk saya karena itu adalah modifikasi ViewPagerlogika sentuh atau ScrollViewlogika. Saya harus menerapkan keduanya, sekarang berfungsi seperti pesona.

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);
    }
}
Dmide
sumber
-1

Anda bisa mengganti metode instantiateItem di kelas PagerAdapter Anda dengan menambahkan scrollView ke setiap halaman. Ini kodenya:

@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;
    }
Marcin S.
sumber
Harap simpulkan atau kutip poin terpenting di sini. Pembusukan tautan terjadi.
ЯegDwight