views:

2251

answers:

4

Hi,

I have a ScrollView that surrounds my entire layout so that the entire screen is scrollable. The first element I have in this ScrollView is a HorizontalScrollView block that has features that can be scrolled through horizontally. I've added an ontouchlistener to the horizontalscrollview to handle touch events and force the view to "snap" to the closest image on the ACTION_UP event.

So the effect I'm going for is like the stock android homescreen where you can scroll from one to the other and it snaps to one screen when you lift your finger.

This all works great except for one problem: I need to swipe left to right almost perfectly horizontally for an ACTION_UP to ever register. If I swipe vertically in the very least (which I think many people tend to do on their phones when swiping side to side), I will receive an ACTION_CANCEL instead of an ACTION_UP. My theory is that this is because the horizontalscrollview is within a scrollview, and the scrollview is hijacking the vertical touch to allow for vertical scrolling.

How can I disable the touch events for the scrollview from just within my horizontal scrollview, but still allow for normal vertical scrolling elsewhere in the scrollview?

Here's a sample of my code:

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

}

+3  A: 

Update: I figured this out. On my ScrollView, I needed to override the onInterceptTouchEvent method to only intercept the touch event if the Y motion is > the X motion. It seems like the default behavior of a ScrollView is to intercept the touch event whenever there is ANY Y motion. So with the fix, the ScrollView will only intercept the event if the user is deliberately scrolling in the Y direction and in that case pass off the ACTION_CANCEL to the children.

Here is the code for my Scroll View class that contains the HorizontalScrollView:

public class CustomScrollView extends ScrollView {
private GestureDetector gestureDetector;
View.OnTouchListener gestureListener;

public CustomScrollView(Context context, AttributeSet attrs) {
    super(context, attrs);
    gestureDetector = new GestureDetector(new YScrollDetector());
    setFadingEdgeLength(0);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    return super.onTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    //Call super first because it does some hidden motion event handling
    boolean result = super.onInterceptTouchEvent(ev);
    //Now see if we are scrolling vertically with the custom gesture detector
    if (gestureDetector.onTouchEvent(ev)) {
        return result;
    } 
    //If not scrolling vertically (more y than x), don't hijack the event.
    else {
        return false;
    }
}

// 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) {
        try {
            if (Math.abs(distanceY) > Math.abs(distanceX)) {
                return true;
            } else {
                return false;
            }
        } catch (Exception e) {
            // nothing
        }
        return false;
    }
}

}

Joel
A: 

Is it possible if you can put the solution here? Much appreciated.

Abid
A: 

Hi Joel,

Can you share your solution with us? Can you post your code?

Thanks in advance

Elias
Elias, I posted my code above.
Joel