【问题标题】:ViewPager inside a ScrollView does not scroll correcltyScrollView 中的 ViewPager 无法正确滚动
【发布时间】:2012-01-12 23:31:03
【问题描述】:

我有一个“页面”,上面有许多组件,并且谁的内容比设备的高度长。好吧,只要把所有的布局(整个页面)都放在一个ScrollView里面,没问题。

其中一个组件是ViewPager。这可以正确呈现,但是对滑动/甩动的响应没有正确执行,它很紧张并且并不总是有效。它似乎与ScrollView '混淆'了,所以只有在你在一条精确的水平线上投掷时才能 100% 工作。

如果我删除 ScrollView,ViewPager 会完美响应。

我已经四处搜索,并没有发现这是一个已知的缺陷。 有其他人经历过吗?

  • 平台版本: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>

【问题讨论】:

标签: android scrollview android-viewpager


【解决方案1】:

我遇到了同样的问题。我的解决方案是在 ViewPager 滚动开始时调用 requestDisallowInterceptTouchEvent

这是我的代码:

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

【讨论】:

  • 这是一种工作。如果移动“大部分”是垂直的,即使在视图寻呼机上开始触摸,我也希望滚动视图垂直移动
  • 是否有必要在 onTouch 和 onPageScrolled 中都有 requestDisallowInterceptTouchEvent 或者这只是矫枉过正?我们可以通过将 requestDisallowInterceptTouchEvent 移动到仅 onPageScrollStateChanged 来获得相同的结果吗?
  • 我有同样的问题,找到了这个最好的解决方案,希望这对你有用。 bitbucket.org/NxAlex/swipes-navigation-demo/src/…
  • 我怎样才能覆盖 onClick 事件?它与 on Touch 冲突?
  • 这里的寻呼机和mpager是什么我需要输入?
【解决方案2】:

进一步阅读发现滚动组件内部存在滚动组件问题。

一种解决方案是在包含的可滚动组件(在我的例子中是 ViewPager)区域上“禁用”ScrollView 的垂直滚动。

这个解决方案的代码很详细here(而且它简直太棒了!)

【讨论】:

  • 我应该参考该页面中的哪个解决方案?
  • requestDisallowInterceptTouchEvent 是您要寻找的。​​span>
  • 我有同样的问题,找到了这个最好的解决方案,希望这对你有用。 bitbucket.org/NxAlex/swipes-navigation-demo/src/…
  • 感谢链接,我在那里找到的解决方案仍然给我在 viewpager 中不完全水平滑动的问题,所以我对其进行了一些修改以使其工作:stackoverflow.com/a/33696740/2093236
【解决方案3】:

这里有一个解决方案:

    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 值,并在拖动时计算距离以确定我们想要走的路。玩转拖动阈值以针对您的情况进行优化。

【讨论】:

  • 我遇到了同样的问题。这是唯一有效的解决方案。干得好...:)
  • 这对我来说是最好的解决方案。不会干扰 ScrollView 上下滑动。
  • 这对我来说是最好的解决方案。我们还可以选择在此解决方案中配置阈值。
  • 对我不起作用,因为滚动视图在达到此取消语句之前拦截了触摸
  • 干得好,如果你想将它与 Wrapcontentviewpager 一起使用,那么它也工作得很好
【解决方案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 Support v4 库。

【讨论】:

  • 它应该是 onPageScrollStateChanged 而不是 onPageSelected?
【解决方案5】:

我改编了@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;
    }
}

【讨论】:

    【解决方案6】:
    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);
    }
    

    }

    @覆盖 public boolean onTouch(View v, 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;
    
    }
    

    使用以上两个,它会很好地工作。我也在我的代码中使用过。

    【讨论】:

      【解决方案7】:

      因此,通过这种方法,我让ViewPagerX 方向滚动,而不必担心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。我现在就投赞成票!
      【解决方案8】:

      这里的解决方案对我来说都不是很好,因为它要么是修改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);
          }
      }
      

      【讨论】:

        【解决方案9】:

        您可以通过向每个页面添加滚动视图来覆盖 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;
            }
        

        【讨论】:

        • 请在这里总结或引用最重要的几点。发生链接失效。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-20
        • 2019-11-02
        • 1970-01-01
        • 1970-01-01
        • 2023-04-08
        • 2021-01-01
        • 1970-01-01
        相关资源
        最近更新 更多