所以我想要的效果就像股票一样android homescreen,您可以从一个屏幕滚动到另一个屏幕,并且在松开手指时可以捕捉到一个屏幕。
除了一个问题之外,所有其他功能都很有效:我需要几乎完美地水平左右滑动以便进行ACTION_UP注册。如果我至少在垂直方向上滑动(我认为很多人在左右滑动时倾向于在手机上滑动),那么我会收到ACTION_CANCEL而不是ACTION_UP。我的理论是,这是因为horizontalscrollview在scrollview内,并且scrollview劫持了垂直触摸以允许垂直滚动。
如何在水平滚动视图中禁用滚动视图的触摸事件,但仍允许在滚动视图中的其他位置进行正常的垂直滚动?
这是我的示例代码:
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;
}
}
}
#1 楼
更新:我想通了。在我的ScrollView上,我需要重写onInterceptTouchEvent方法以仅在Y动作大于X动作时拦截触摸事件。似乎ScrollView的默认行为是在有任何Y运动时都拦截触摸事件。因此,有了此修复程序,如果用户有意在Y方向上滚动并在这种情况下将ACTION_CANCEL传递给子级,则ScrollView将仅拦截该事件。这是我的Scroll View的代码包含HorizontalScrollView的类:public class CustomScrollView extends ScrollView {
private GestureDetector mGestureDetector;
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
mGestureDetector = new GestureDetector(context, new YScrollDetector());
setFadingEdgeLength(0);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return Math.abs(distanceY) > Math.abs(distanceX);
}
}
}
评论
我正在使用相同的东西,但是在x方向滚动时,在公共布尔值onInterceptTouchEvent(MotionEvent ev)上出现了异常NULL POINTER EXCEPTION异常{return super.onInterceptTouchEvent(ev)&& mGestureDetector.onTouchEvent(ev);我在scrollview中使用了horizontall滚动
– Vipin Sahu
2012年8月20日在11:59
@全部感谢它的工作,我忘了在scrollview构造函数中初始化手势检测器
– Vipin Sahu
2012年8月20日在12:27
我应该如何使用此代码在ScrollView中实现ViewPager
– Harsha M V
2012年12月11日在19:40
当我有一个始终处于扩展状态的网格视图时,此代码存在一些问题,有时它不会滚动。我必须重写YScrollDetector中的onDown(...)方法,以始终按照整个文档中的建议返回true(例如,此处developer.android.com/training/custom-views/…),这解决了我的问题。
– Nemanja Kovacevic
13年2月6日在13:28
我只是遇到了一个小错误,值得一提。我相信onInterceptTouchEvent中的代码应拆分两个布尔调用,以确保将调用mGestureDetector.onTouchEvent(ev)。到目前为止,如果super.onInterceptTouchEvent(ev)为false,则不会调用它。我只是遇到一种情况,滚动视图中的可单击子级可以捕获触摸事件,而onScroll根本不会被调用。否则,谢谢,很好的答案!
– GLee
13年5月5日,0:46
#2 楼
感谢Joel为我提供了有关如何解决此问题的线索。我简化了代码(无需GestureDetector)以实现相同的效果:
public class VerticalScrollView extends ScrollView {
private float xDistance, yDistance, lastX, lastY;
public VerticalScrollView(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);
lastX = curX;
lastY = curY;
if(xDistance > yDistance)
return false;
}
return super.onInterceptTouchEvent(ev);
}
}
评论
非常好,感谢您的重构。滚动到listView的底部时,上述方法出现问题,因为无论Y / X移动率如何,子元素上的任何触摸行为都不会开始被拦截。奇怪的!
–多里
2012年1月25日20:06
谢谢!还可以在带有自定义ListView的ListView中使用ViewPager。
– Shaief Shaik
2012-2-16在6:55
刚刚用这个替换了可接受的答案,现在对我来说效果更好。谢谢!
–大卫·斯科特(David Scott)
2012年7月10日15:41
@VipinSahu,要告知触摸移动的方向,可以获取当前X坐标和lastX的增量,如果大于0,则触摸从左向右移动,否则从右向左移动。然后将当前X保存为lastX以便进行下一次计算。
–neevek
2012年8月23日在1:25
水平滚动视图呢?
– Zin Win Htet
2014年12月3日,9:10
#3 楼
我想我找到了一个更简单的解决方案,仅此方法使用ViewPager的子类而不是(其父级)ScrollView。UPDATE 2013-07-16:我也为
onTouchEvent
添加了覆盖。尽管可能是YMMV,但它可能可以解决注释中提到的问题。public class UninterceptableViewPager extends ViewPager {
public UninterceptableViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean ret = super.onInterceptTouchEvent(ev);
if (ret)
getParent().requestDisallowInterceptTouchEvent(true);
return ret;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean ret = super.onTouchEvent(ev);
if (ret)
getParent().requestDisallowInterceptTouchEvent(true);
return ret;
}
}
这类似于android.widget.Gallery的onScroll()中使用的技术。
Google I / O 2013演示文稿“编写Android的自定义视图”对此作了进一步的解释。
2013-12-10更新:Kirill Grouchnikov的帖子中也介绍了类似的方法(然后)Android Market应用程序。
评论
布尔ret = super.onInterceptTouchEvent(ev);只为我返回假。在Scrollview中的UninterceptableViewPager上使用
–scottyab
2012年11月13日在16:47
这对我不起作用,尽管我喜欢它的简单性。我将ScrollView与LinearLayout一起使用,其中放置了UninterceptableViewPager。确实,ret始终是错误的。
– Peterdk
13年5月27日在23:54
@scottyab&@Peterdk:我的是在TableRow中,该行位于ScrollView内的TableLayout中(是的,我知道...),并且按预期工作。也许您可以尝试覆盖onScroll而不是onInterceptTouchEvent,就像Google那样(第1010行)
– Giorgos Kylafas
13年5月31日在7:11
我只是将ret硬编码为true,并且工作正常。之前它返回的是false,但我认为这是因为scrollview具有线性布局,该布局包含所有子级
–阿曼尼
2013年6月7日9:58
#4 楼
我发现有时候一个ScrollView重新获得焦点,而另一个失去焦点。您可以通过只授予scrollView焦点之一来防止这种情况: scrollView1= (ScrollView) findViewById(R.id.scrollscroll);
scrollView1.setAdapter(adapter);
scrollView1.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
scrollView1.getParent().requestDisallowInterceptTouchEvent(true);
return false;
}
});
评论
您要传递什么适配器?
– Harsha M V
2012-12-11 19:43
我再次检查,发现它实际上不是我正在使用的ScrollView,而是一个ViewPager,并且我为其传递了FragmentStatePagerAdapter,其中包括画廊的所有图像。
– Marius Hilarious
2012年12月12日0:01在
#5 楼
这对我来说不是很好。我进行了更改,现在可以正常使用了。如果有兴趣的人。public class ScrollViewForNesting extends ScrollView {
private final int DIRECTION_VERTICAL = 0;
private final int DIRECTION_HORIZONTAL = 1;
private final int DIRECTION_NO_VALUE = -1;
private final int mTouchSlop;
private int mGestureDirection;
private float mDistanceX;
private float mDistanceY;
private float mLastX;
private float mLastY;
public ScrollViewForNesting(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
}
public ScrollViewForNesting(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public ScrollViewForNesting(Context context) {
this(context,null);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDistanceY = mDistanceX = 0f;
mLastX = ev.getX();
mLastY = ev.getY();
mGestureDirection = DIRECTION_NO_VALUE;
break;
case MotionEvent.ACTION_MOVE:
final float curX = ev.getX();
final float curY = ev.getY();
mDistanceX += Math.abs(curX - mLastX);
mDistanceY += Math.abs(curY - mLastY);
mLastX = curX;
mLastY = curY;
break;
}
return super.onInterceptTouchEvent(ev) && shouldIntercept();
}
private boolean shouldIntercept(){
if((mDistanceY > mTouchSlop || mDistanceX > mTouchSlop) && mGestureDirection == DIRECTION_NO_VALUE){
if(Math.abs(mDistanceY) > Math.abs(mDistanceX)){
mGestureDirection = DIRECTION_VERTICAL;
}
else{
mGestureDirection = DIRECTION_HORIZONTAL;
}
}
if(mGestureDirection == DIRECTION_VERTICAL){
return true;
}
else{
return false;
}
}
}
评论
这是我的项目的答案。我有一个视图寻呼机,它可以作为图库在滚动视图中单击。我使用上面提供的解决方案,它可以水平滚动,但是单击启动新活动的传呼机图像并返回后,传呼机将无法滚动。很好,tks!
–龙凯
2014年8月29日在3:04
非常适合我。我在滚动视图中有一个自定义的“滑动解锁”视图,这给我带来了同样的麻烦。此解决方案解决了该问题。
–混合动力
18-10-3在3:14
#6 楼
感谢Neevek,他的回答对我有用,但是当用户开始在水平方向上滚动水平视图(ViewPager)时,它并没有锁定垂直滚动,然后在不垂直抬起手指滚动的情况下,它开始滚动了基础容器视图(ScrollView) 。我通过对Neevak的代码稍作更改来修复它:private float xDistance, yDistance, lastX, lastY;
int lastEvent=-1;
boolean isLastEventIntercepted=false;
@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);
lastX = curX;
lastY = curY;
if(isLastEventIntercepted && lastEvent== MotionEvent.ACTION_MOVE){
return false;
}
if(xDistance > yDistance )
{
isLastEventIntercepted=true;
lastEvent = MotionEvent.ACTION_MOVE;
return false;
}
}
lastEvent=ev.getAction();
isLastEventIntercepted=false;
return super.onInterceptTouchEvent(ev);
}
#7 楼
这最终成为支持v4库NestedScrollView的一部分。因此,在我猜想的大多数情况下,都不再需要本地黑客了。#8 楼
Neevek的解决方案在运行3.2及更高版本的设备上比Joel更好。如果在scollview中使用手势检测器,则Android中存在一个错误,它将导致java.lang.IllegalArgumentException:pointerIndex超出范围。要重现该问题,请按照Joel的建议实施自定义scollview,然后在其中放入视图分页器。如果向一个方向(向左/向右)拖动(不要抬高图形)然后向相反方向拖动,则会看到崩溃。同样在Joel的解决方案中,如果您通过对角移动手指来拖动视图寻呼机,则一旦手指离开视图寻呼机的内容查看区域,该寻呼机就会弹回到其先前位置。所有这些问题更多是与Android的内部设计有关,而不是与Joel的实现有关,而Joel的实现本身就是一段巧妙而简洁的代码。http://code.google.com/p/android/issues/detail?id=18990
评论
我已经尝试了这篇文章中的所有方法,但是它们都不适合我。我正在使用MeetMe的HorizontalListView库。velir.com/blog/index.php/2010/11/17/…上有一篇文章,其中包含类似的代码(HomeFeatureLayout扩展了HorizontalScrollView)。关于自定义滚动类的组成,还有一些其他注释。