ScrollView 中的 ViewPager 无法正确滚动
Posted
技术标签:
【中文标题】ScrollView 中的 ViewPager 无法正确滚动【英文标题】:ViewPager inside a ScrollView does not scroll correclty 【发布时间】: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_
android:layout_ >
<LinearLayout
android:layout_
android:layout_
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_
android:layout_ />
</LinearLayout>
</ScrollView>
【问题讨论】:
在这里查看答案:***.com/questions/9034030/viewpager-in-scrollview/… 请在此链接中显示我的答案:***.com/questions/7381360/… 【参考方案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 中不完全水平滑动的问题,所以我对其进行了一些修改以使其工作:***.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】:因此,通过这种方法,我让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。我现在就投赞成票!【参考方案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;
【讨论】:
请在这里总结或引用最重要的几点。发生链接失效。以上是关于ScrollView 中的 ViewPager 无法正确滚动的主要内容,如果未能解决你的问题,请参考以下文章
ScrollView 中的 ViewPager 无法正确滚动
ViewPager中的视图在ScrollView中不能垂直滚动
(转)ViewPager,ScrollView 嵌套ViewPager滑动冲突解决
当ViewPager嵌套在ScrollView/ListView里时,手势冲突如何处理?
安卓ScrollView中放ViewPager+ViewPager,ViewPager中放的是2个Fragment,Fragment中放Listview.