我有一个ScrollView环绕我的整个布局,以便整个屏幕可滚动 . 我在这个ScrollView中的第一个元素是一个HorizontalScrollView块,它具有可以水平滚动的功能 . 我在horizontalscrollview中添加了一个ontouchlistener来处理触摸事件并强制视图“捕捉”到ACTION_UP事件上最近的图像 .
所以我想要的效果就像股票android主屏幕,你可以从一个滚动到另一个,当你举起手指时,它会捕捉到一个屏幕 .
这一切都很有效,除了一个问题:我需要从左到右几乎完全水平滑动,以便ACTION_UP进行注册 . 如果我至少垂直滑动(我认为许多人往往会在他们的手机上左右滑动),我会收到ACTION_CANCEL而不是ACTION_UP . 我的理论是,这是因为横向视图滚动视图位于滚动视图内,并且滚动视图正在劫持垂直触摸以允许垂直滚动 .
如何在水平滚动视图中禁用滚动视图的触摸事件,但仍允许在滚动视图中的其他位置正常垂直滚动?
这是我的代码示例:
public class HomeFeatureLayout extends HorizontalScrollView {
private ArrayList<ListItem> items = null;
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 300;
private int activeFeature = 0;
public HomeFeatureLayout(Context context, ArrayList<ListItem> items){
super(context);
setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
setFadingEdgeLength(0);
this.setHorizontalScrollBarEnabled(false);
this.setVerticalScrollBarEnabled(false);
LinearLayout internalWrapper = new LinearLayout(context);
internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
addView(internalWrapper);
this.items = items;
for(int i = 0; i< items.size();i++){
LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
TextView header = (TextView) featureLayout.findViewById(R.id.featureheader);
ImageView image = (ImageView) featureLayout.findViewById(R.id.featureimage);
TextView title = (TextView) featureLayout.findViewById(R.id.featuretitle);
title.setTag(items.get(i).GetLinkURL());
TextView date = (TextView) featureLayout.findViewById(R.id.featuredate);
header.setText("FEATURED");
Image cachedImage = new Image(this.getContext(), items.get(i).GetImageURL());
image.setImageDrawable(cachedImage.getImage());
title.setText(items.get(i).GetTitle());
date.setText(items.get(i).GetDate());
internalWrapper.addView(featureLayout);
}
gestureDetector = new GestureDetector(new MyGestureDetector());
setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
int scrollX = getScrollX();
int featureWidth = getMeasuredWidth();
activeFeature = ((scrollX + (featureWidth/2))/featureWidth);
int scrollTo = activeFeature*featureWidth;
smoothScrollTo(scrollTo, 0);
return true;
}
else{
return false;
}
}
});
}
class MyGestureDetector extends SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//right to left
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature < (items.size() - 1))? activeFeature + 1:items.size() -1;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
//left to right
else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
activeFeature = (activeFeature > 0)? activeFeature - 1:0;
smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
return true;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
}
8 回答
更新:我想通了 . 在我的ScrollView上,我需要覆盖onInterceptTouchEvent方法,以便仅在Y运动> X运动时拦截触摸事件 . 看起来ScrollView的默认行为是在任何Y运动时拦截触摸事件 . 因此,使用此修复程序,ScrollView将仅在用户故意在Y方向上滚动时拦截事件,并且在这种情况下将ACTION_CANCEL传递给子项 .
以下是包含HorizontalScrollView的Scroll View类的代码:
谢谢Joel给我一个如何解决这个问题的线索 .
我已经简化了代码(不需要 GestureDetector )来实现相同的效果:
我想我找到了一个更简单的解决方案,只有这个使用ViewPager的子类而不是(它的父)ScrollView .
UPDATE 2013-07-16 :我也为
onTouchEvent
添加了一个覆盖 . 尽管YMMV,它可能有助于评论中提到的问题 .这类似于the technique used in android.widget.Gallery's onScroll() . Google I / O 2013演示文稿Writing Custom Views for Android进一步解释了这一点 .
Update 2013-12-10 :a post from Kirill Grouchnikov about the (then) Android Market app中也描述了类似的方法 .
我发现有些时候ScrollView会重新获得焦点而另一个会失去焦点 . 您可以通过仅授予其中一个scrollView焦点来防止这种情况:
它对我来说效果不佳 . 我改变了它,现在它运作顺利 . 如果有人有兴趣 .
感谢Neevek,他的回答对我有用,但是当用户开始在水平方向上滚动水平视图(ViewPager)然后没有垂直抬起手指滚动它开始滚动底层容器视图(ScrollView)时,它不会锁定垂直滚动 . 我通过对Neevak代码稍作修改来修复它:
这最终成为支持v4库的一部分,NestedScrollView . 因此,对于我猜的大多数情况,不再需要本地黑客攻击 .
Neevek的解决方案比运行3.2及更高版本的设备上的Joel更好 . Android中存在一个错误,如果在scollview中使用手势检测器,则会导致java.lang.IllegalArgumentException:pointerIndex超出范围 . 要复制该问题,请按照Joel的建议实现自定义scollview,并在其中放置一个视图寻呼机 . 如果你(不要抬起你的身影)向一个方向(左/右)然后向相反方向拖动,你会看到崩溃 . 同样在Joel的解决方案中,如果您通过对角移动手指来拖动视图寻呼机,一旦您的手指离开视图寻呼机的内容视图区域,寻呼机将弹回其先前的位置 . 所有这些问题都与Android的内部设计有关,或者与Joel的实现相比缺乏,这本身就是一段智能和简洁的代码 .
http://code.google.com/p/android/issues/detail?id=18990