ViewPager在ScrollView中 - 垂直滚动不起作用

16

所以,我有一个ScrollView,�有一个�元素——一个带有两个�元素(TextView和ViewPager)的LinearLayout。因为ViewPager包�许多元素,所以我需��直滚动的能力。�有ViewPager中的页��以水平滚动(也就是说:我�想在ViewPager内部水平滑动)。那个TextView�应该水平滚动,但应该��我的ViewPager一起滚动。

简��?�。

我在StackOverflow上看到了�常相似的问题(here,here,here和here)。没有建议的解决方案适�我😢

我是一名有帮助的助手,可以为您翻译文本。
我看到的是 this <- 我可爱的UI :), 不过我无法垂直滚动 :(
在ViewPager内嵌套ScrollViews不是一个选项 - UI的设计禁止这样做。
也许是因为我以编程方式填充了每个视图页面?嗯...
任何建议将不胜感激。
我的代码:

activity_main.xml:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"

                android:id="@+id/scrollView"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:background="@android:color/white"
                android:fillViewport="true" >

                <LinearLayout
                    android:id="@+id/linearLayoutGeneral"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    <TextView
                    android:id="@+id/tvText"
                    android:layout_width="fill_parent"
                    android:layout_height="200dp"
                    android:text="Test text" />

                <android.support.v4.view.ViewPager
                            android:id="@+android:id/viewpager"
                            android:layout_width="fill_parent"
                            android:layout_height="fill_parent" >
                </android.support.v4.view.ViewPager>


                </LinearLayout>
</ScrollView>

每个ViewPager页面都具有以下布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <LinearLayout
        android:id="@+id/layoutData"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    </LinearLayout>

</LinearLayout>

在这样的页面中,单个元素的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="40dp"
    android:gravity="center"
    android:orientation="vertical"
    android:background="@android:color/white" >

    <TextView
        android:id="@+id/textView"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Large Text"
        android:background="@android:color/black"
        android:textColor="@android:color/white"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</LinearLayout>

一个单页面片段也非常简单:

public class DayFragment extends Fragment {

    private static final String TAG = DayFragment.class.getSimpleName();

    public String tag;

    LinearLayout data;

    View mView;

    final int ROWS_NUM = 60;

    public DayFragment() {

    }

    /**
     * (non-Javadoc)
     * 
     * @see android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
     *      android.view.ViewGroup, android.os.Bundle)
     */
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist. The fragment
            // may still be created from its saved state, but there is
            // no reason to try to create its view hierarchy because it
            // won't be displayed. Note this is not needed -- we could
            // just run the code below, where we would create and return
            // the view hierarchy; it would just never be used.
            return null;
        }

        mView = (LinearLayout) inflater.inflate(R.layout.day, container, false);

        setUpControls();

        generateData();

        String text = getArguments().getString("text");
        Log.d(TAG, "creating view with text: " + text);

        return mView;
    }

    private void setUpControls() {
        data = (LinearLayout) mView.findViewById(R.id.layoutData);
    }

    private void generateData() {
        for (int i = 0; i < ROWS_NUM; i++) {
            View v = createRow(i);
            data.addView(v);
        }
    }

    private View createRow(int num) {
        LayoutInflater inflater = (LayoutInflater) getActivity()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflater.inflate(R.layout.row, null);

        TextView tv = (TextView) v.findViewById(R.id.textView);
        tv.setText("Data nr: " + num);

        return v;
    }

    public static DayFragment newInstance(String text) {
        Log.d(TAG, "newInstance with text: " + text);
        DayFragment f = new DayFragment();

        f.tag = text;

        // Supply text input as an argument.
        Bundle args = new Bundle();
        args.putString("text", text);
        f.setArguments(args);

        return f;
    }

}
8个回答

29

我遇到了一个问题,就是ViewPager的行为很奇怪,我找出了原因,是因为有时候ScrollView重新获得焦点,而ViewPager则失去了焦点。如果你在ScrollView中放置了一个ViewPager,并且希望当你触摸它时它总是保持焦点,并且ScrollView永远不会获得焦点,那么设置requestDisallowInterceptTouchEvent就可以做到这一点。这对你有帮助吗?

    mViewPager= (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(adapter);
    mViewPager.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

通常情况下,ViewPager可能不是ScrollView的直接子项。我发现在某些情况下,最好在ScrollView上调用requestDisallowInterceptTouchEvent而不是在父级上调用。 - Nick Snyder
2
我在Listview的头部插入了Viewpager,垂直滚动正常工作。问题是Listview的水平滑动不起作用。使用这段代码解决了这个问题。谢谢! - Signcodeindie
在这里,我尝试了很多其他解决方案都不起作用之后,终于找到了一个适合我的解决方案:https://dev59.com/XHE85IYBdhLWcg3wtV71和https://dev59.com/Ims05IYBdhLWcg3wNPM3。 - Joseph
我有一个父布局,它是RelativeLayout->ScrollView->LinearLayout->LinearLayout(包含TextView和ImageView)-></Linearlayout> -----> ProgressBar->View->LinearLayout(包含选项卡+页面)</LinearLayout>----></LinearLayout>-></ScrollView>-></RelativeLayout> - Erum

11

扩展 Marius 的回答,允许在父级视图上进行垂直滚动:

我注意到,当您在初始滚动中调用“requestDisallowInterceptTouchEvent”方法时,在视图翻页器顶部垂直滚动时,它将阻止在父视图上进行垂直滚动。

我的解决方案是,仅在用户水平滚动一定距离(“margin”变量)后触发该方法。

mViewPager.setOnTouchListener(new View.OnTouchListener() {
    public boolean onTouch(View v, MotionEvent e) {
        // How far the user has to scroll before it locks the parent vertical scrolling.
        final int margin = 10;
        final int fragmentOffset = v.getScrollX() % v.getWidth();

        if (fragmentOffset > margin && fragmentOffset < v.getWidth() - margin) {
            mViewPager.getParent().requestDisallowInterceptTouchEvent(true);
        }
        return false;
    }
});

这样做更好,因为它意味着您仍然可以从在viewpager上开始的触摸事件垂直滚动页面。 - Dean Wild
还要注意,您可能希望以dp为单位声明margin变量,就像示例中的像素一样。https://dev59.com/MW445IYBdhLWcg3w_O8m - elimirks
只有当我设置了自定义的ViewPager高度时,它才能正常工作。对于wrap和match parent则不行,但是这会给你带来+1的贡献。 - Robi Kumar Tomar
有趣... 当你记录“v.getScrollX()”和“v.getWidth()”时,输出是什么? - elimirks

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

1

试试这个

    public  static class XScrollDetector extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return Math.abs(distanceY) < Math.abs(distanceX);
       }
   } 

并且对于ViewPager

    final GestureDetector mGestureDetector= new GestureDetector(this,new XScrollDetector());
    mViewPager.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            mScrollView.requestDisallowInterceptTouchEvent(mGestureDetector.onTouchEvent(event));
            return false;

        }
    });

1

我曾经有同样的问题,你可以尝试我的代码,我们应该禁用垂直滚动并向Scrollview发送事件

private int dragThreshold = 10, int downX = 0, int downY = 0;

vPager.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if(event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_UP){
                downX = (int) event.getRawX();
                downY = (int) event.getRawY();
                return false;
            }else if(event.getAction() == MotionEvent.ACTION_MOVE){
                int distanceX = Math.abs((int) event.getRawX() - downX);
                int distanceY = Math.abs((int) event.getRawY() - downY);

                ExceptionHelpers.dLog("OnTouchListener", "distance X : "+distanceX+" , distance Y : "+distanceY);

                if(distanceY > distanceX && distanceY > dragThreshold){
                    v.getParent().requestDisallowInterceptTouchEvent(false);
                    return true;
                }
            }
            v.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

此外,您可以通过添加以下内容来设置最小水平滚动:

else {
      distanceX > distanceY && distanceX > dragThreshold)
}

记住,建筑工人做事情时会尝试、尝试、再尝试。

而开发者(我们)则会思考、思考、再尝试。

祝好运。


如何使用此方法禁用水平滚动并启用垂直滚动? - Harneet Kaur
@Harneet 你需要使用水平滚动视图。 - Hossein Kurd
这种方法不可行吗?在我的项目中,我必须使用它,但想要禁用视图翻页器的水平滚动,并启用父级的水平滚动。同时,只想启用 VP 的垂直滚动。 - Harneet Kaur

0

我尝试了很多东西,但有些对我无效,有些则过于复杂且代码不干净。

我用以下方式解决了ScrollView中的ViewPager问题。你可以在ViewPager.onMeasure()ViewPager.OnPageChangeListener()中更新viewPager大小。

onMeasure()方法会在每次片段中的ui更改时调用。但是我的ui变化很多(例如TabButton),我仅在用户滚动页面时更新View Pager大小。

    mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            // will work when ViewPager shows first fragment.
            if (position != currentPosition) {
                currentPosition = position;
                updateViewPagerSize(position);
            }
        }

        @Override
        public void onPageSelected(int position) {

        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });

根据情况,你可能需要给一个焦点来使ScrollView正常工作。
public void updateViewPagerSize(int position) {
    View view = mPager.getChildAt(position);
    view.measure(ViewPager.LayoutParams.WRAP_CONTENT, ViewPager.LayoutParams.WRAP_CONTENT);
    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, view.getMeasuredHeight());
    mPager.setLayoutParams(params);
    edtText.requestFocus(); //you might have to give a focus something to use scroll.
}

-1

禁止ScrollView拦截触摸事件。这需要用户在ViewPager外部触摸才能上下滚动。

mScrollView = (ScrollView) findViewById(R.id.scroll_view); 
mViewPager= (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(adapter);
mViewPager.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mScrollView.requestDisallowInterceptTouchEvent(true);
        return false;
    }
});

-1

在一个Activity中实现水平和垂直滚动通常是个问题。ViewPager处理这个问题非常好。我不知道你的问题有没有好的解决方案。但是我建议你摆脱主布局中的ScrollView和TextView。

使用ViewPager作为你布局的起点,然后将垂直ScrollView放入ViewPager Fragments中。


感谢您的输入。我知道在ViewPager内更容易嵌入ScrollView,但这正是我想避免的情况。 - Genkaku

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接