Android ScrollView触摸处理中的水平ScrollView
我有一个环绕整个布局的滚动视图,这样整个屏幕都可以滚动。这个ScrollView中的第一个元素是HorizontalScrollView块,它具有可以水平滚动的特性。我在horizontalscrollview中添加了一个ontouchlistener来处理触摸事件,并强制视图“捕捉”到ACTION\u UP事件上最近的图像 所以我想要的效果就像股票android主屏幕一样,你可以从一个屏幕滚动到另一个屏幕,当你抬起手指时,它会捕捉到一个屏幕 这一切都很好,除了一个问题:我需要从左向右几乎完美地水平滑动一个动作直到注册为止。如果我至少垂直滑动(我想很多人在手机上左右滑动时都会这么做),我会收到一个ACTION\u CANCEL而不是ACTION\u UP。我的理论是,这是因为horizontalscrollview在scrollview中,而scrollview正在劫持垂直触摸以允许垂直滚动 如何在水平滚动视图中禁用滚动视图的触摸事件,但仍允许在滚动视图的其他位置进行正常的垂直滚动 以下是我的代码示例:Android ScrollView触摸处理中的水平ScrollView,android,ontouchlistener,android-scrollview,horizontalscrollview,Android,Ontouchlistener,Android Scrollview,Horizontalscrollview,我有一个环绕整个布局的滚动视图,这样整个屏幕都可以滚动。这个ScrollView中的第一个元素是HorizontalScrollView块,它具有可以水平滚动的特性。我在horizontalscrollview中添加了一个ontouchlistener来处理触摸事件,并强制视图“捕捉”到ACTION\u UP事件上最近的图像 所以我想要的效果就像股票android主屏幕一样,你可以从一个屏幕滚动到另一个屏幕,当你抬起手指时,它会捕捉到一个屏幕 这一切都很好,除了一个问题:我需要从左向右几乎完美地
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;
}
}
}
公共类HomeFeatureLayout扩展了HorizontalScrollView{
private ArrayList items=null;
私人手势检测器;
View.OnTouchListener手势Listener;
专用静态最终整数滑动最小距离=5;
专用静态最终整数滑动\u阈值\u速度=300;
私有int-activeFeature=0;
公共HomeFeatureLayout(上下文、ArrayList项){
超级(上下文);
setLayoutParams(新的LayoutParams(LayoutParams.FILL\u父级,LayoutParams.WRAP\u内容));
设置FadingEdgeLength(0);
此.setHorizontalScrollBarEnabled(false);
此.setVerticalScrollBarEnabled(false);
LinearLayout internalWrapper=新的LinearLayout(上下文);
internalWrapper.setLayoutParams(新的LayoutParams(LayoutParams.FILL_父级,LayoutParams.FILL_父级));
设置方向(LinearLayout.HORIZONTAL);
addView(内部包装器);
这个项目=项目;
对于(int i=0;i滑动最小距离和&Math.abs(速度x)>滑动阈值速度){
activeFeature=(activeFeature<(items.size()-1))?activeFeature+1:items.size()-1;
smoothScrollTo(activeFeature*getMeasuredWidth(),0);
返回true;
}
//从左到右
else if(e2.getX()-e1.getX()>swip\u MIN\u DISTANCE&&Math.abs(velocityX)>swip\u THRESHOLD\u VELOCITY){
activeFeature=(activeFeature>0)?activeFeature-1:0;
smoothScrollTo(activeFeature*getMeasuredWidth(),0);
返回true;
}
}捕获(例外e){
//没什么
}
返回false;
}
}
}
更新:我发现了这一点。在我的ScrollView上,我需要重写onInterceptTouchEvent方法,以便仅在Y运动>X运动时拦截触摸事件。ScrollView的默认行为似乎是在有任何Y运动时拦截触摸事件。因此,通过修复,ScrollView将仅在用户有意沿Y方向滚动时拦截事件,在这种情况下,将取消操作传递给子级
以下是包含HorizontalScrollView的my Scroll View类的代码:
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);
}
}
}
谢谢Joel给了我解决这个问题的线索 我简化了代码(无需使用手势检测器)以实现相同的效果:
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);
}
}
我想我找到了一个更简单的解决方案,只是它使用了ViewPager的一个子类
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;
}
}
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;
}
});
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);
}
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;
}
}
}
private class HorizontallyScrollingView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : ViewThatYouWannaScrollHorizontally(context, attrs){
override fun onTouchEvent(event: MotionEvent?): Boolean {
// When the user's finger touches the webview and starts moving
if(event?.action == MotionEvent.ACTION_MOVE){
// get the velocity tracker object
val mVelocityTracker = VelocityTracker.obtain();
// connect the velocity tracker object with the event that we are emitting while we are touching the webview
mVelocityTracker.addMovement(event)
// compute the velocity in terms of pixels per 1000 millisecond(i.e 1 second)
mVelocityTracker.computeCurrentVelocity(1000);
// compute the Absolute Velocity in X axis
val xVelocityABS = abs(mVelocityTracker.getXVelocity(event?.getPointerId((event?.actionIndex))));
// compute the Absolute Velocity in Y axis
val yVelocityABS = abs(mVelocityTracker.getYVelocity(event?.getPointerId((event?.actionIndex))));
// If the velocity of x axis is greater than y axis then we'll consider that it's a horizontal scroll and tell the parent layout
// "Hey parent bro! im scrolling horizontally, this has nothing to do with ur scrollview so stop capturing my event and stay the f*** where u are "
if(xVelocityABS > yVelocityABS){
// So, we'll disallow the parent to listen to any touch events until i have moved my fingers off the screen
parent.requestDisallowInterceptTouchEvent(true)
}
} else if (event?.action == MotionEvent.ACTION_CANCEL || event?.action == MotionEvent.ACTION_UP){
// If the touch event has been cancelled or the finger is off the screen then reset it (i.e let the parent capture the touch events on webview as well)
parent.requestDisallowInterceptTouchEvent(false)
}
return super.onTouchEvent(event)
}
}