0

I am using the TouchImageView by Mike Ortiz.

Within it, canScrollHorizontally is overwritten:

@Override
public boolean canScrollHorizontally(int direction) {
    matrix.getValues(m);
    float x = m[Matrix.MTRANS_X];
    float y = m[Matrix.MTRANS_Y];

    if (getImageWidth() < viewWidth) {
        return false;

    } else if (x >= -1 && direction < 0) {
        return false;

    } else if (Math.abs(x) + viewWidth + 1 >= getImageWidth() && direction > 0) {
        return false;
    }

    return true;
}

This works as expected. I can put the TouchImageView in a ViewPager, and scroll around the zoomed image, but also swipe right/left through the ViewPager when I reach the edges of the image.

But what I intend to have is a vertical ViewPager nested within the regular ViewPager. The vertical ViewPager is modelled after this SO post:

public class VerticalViewPager extends ViewPager {

    public VerticalViewPager(Context context) {
        super(context);
        init();
    }

    public VerticalViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        // The majority of the magic happens here
        setPageTransformer(true, new VerticalPageTransformer());
        // The easiest way to get rid of the overscroll drawing that happens on the left and right
        setOverScrollMode(OVER_SCROLL_NEVER);
    }

    private class VerticalPageTransformer implements ViewPager.PageTransformer {

        @Override
        public void transformPage(View view, float position) {

            if (position < -1) { // [-Infinity,-1)
                // This page is way off-screen to the left.
                view.setAlpha(0);

            } else if (position <= 1) { // [-1,1]
                view.setAlpha(1);

                // Counteract the default slide transition
                view.setTranslationX(view.getWidth() * -position);

                //set Y position to swipe in from top
                float yPosition = position * view.getHeight();
                view.setTranslationY(yPosition);

            } else { // (1,+Infinity]
                // This page is way off-screen to the right.
                view.setAlpha(0);
            }
        }
    }

    /**
     * Swaps the X and Y coordinates of your touch event.
     */
    private MotionEvent swapXY(MotionEvent ev) {
        float width = getWidth();
        float height = getHeight();

        float newX = (ev.getY() / height) * width;
        float newY = (ev.getX() / width) * height;

        ev.setLocation(newX, newY);

        return ev;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev){
        boolean intercepted = super.onInterceptTouchEvent(swapXY(ev));
        swapXY(ev); // return touch coordinates to original reference frame for any child views
        return intercepted;
    }

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

}

With regular ImageViews, it works great. I can swipe left/right through the horizontal ViewPager, while also scrolling vertically through the nested ViewPager as needed. One modification to the vertical ViewPager (to allow horizontal scrolling from anywhere within the vertical ViewPager), is:

@Override
public boolean canScrollHorizontally(int direction) {
    return false;
}

Small change, works great.

The trouble comes when the contents of the vertical ViewPager are the custom TouchImageViews. The right/left swiping still works great, but the vertical swiping only works when the right edge of the image is lined up with the right side of the device screen. This is due to the canScrollHorizontally override within the TouchImageView class.

The int direction argument does not take x or y into account. A positive y swipe is indistinguishable from a positive x swipe.

What I'd like to do is override the canScrollVertically method, to detect when a vertical swipe is occurring, but the method is never called. Even just a quick test like:

@Override
public boolean canScrollVertically(int direction) {
    matrix.getValues(m);

    Toast.makeText(context, "vertical", Toast.LENGTH_SHORT).show();


    return true;
}

is never called. Putting the same Toast message within the canScrollHorizontally method shows the message on every swipe (horizontal or vertical).

Why is canScrollVertically never called?

I'd like to detect when BOTH the image is bottomed-out in the view, and when the user is swiping up. When these two conditions are met, I can return false from the canScrollHorizontally and allow the vertical ViewPager to scroll properly.

Thanks in advance for any help. Anyone know Mike Ortiz!?

Community
  • 1
  • 1
Birrel
  • 4,464
  • 5
  • 27
  • 62

2 Answers2

0

So a solution I've come up with is the following.

Within the TouchImageView, Mike has a custom class

private class PrivateOnTouchListener implements OnTouchListener

which has the following block:

    case MotionEvent.ACTION_MOVE:
        if (state == State.DRAG) {
            float deltaX = curr.x - last.x;
            float deltaY = curr.y - last.y;

            ... etc ... 
        }
        break;

What I've done is set a global boolean verticalDrag;. Using the above block, I check to make sure the deltaY is sufficiently larger than the deltaX (i.e. a vertical drag/swipe)

if(Math.abs(deltaY) > (Math.abs(deltaX) * sensitivity)){
    verticalDrag = true;
}else{
    verticalDrag = false;
}

Where sensitivity is up to the coder's discretion to assign. A true vertical swipe will usually have a few pixels left/right deviation - it is difficult to swipe exclusively in one direction or the other. Because of this, the sensitivity can be pretty high (i.e. 5 to 10, or greater depending on testing), and it will still fire.

Back to within the canScrollHorizontally method, I just add:

float y = m[Matrix.MTRANS_Y];

else if(Math.abs(y) + viewHeight + 1 >= getImageHeight() && verticalDrag){
    return false;
}

which allows the vertical ViewPager to swipe, as desired.

Birrel
  • 4,464
  • 5
  • 27
  • 62
0

In my case, I was checking to see if my scrollView(which contained text) was scrollable vertically when the activity was initialized. On phones, it would scroll but on tablets it couldn't. canScrollVertically was returning me incorrect value because it couldn't be determined yet. I fixed this issue by calling it in the OnGlobalLayoutListener.

(Kotlin)

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

        // Must use onGlobalLayout or else canScrollVertically will not return the correct value because the layout hasn't been made yet
        scrollView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {
                // If the scrollView can scroll, disable the accept menu item button
                if ( scrollView.canScrollVertically(1) || scrollView.canScrollVertically(-1) )
                    acceptMenuItem?.isEnabled = false

                // Remove itself after onGlobalLayout is first called or else it would be called about a million times per second
                scrollView.viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        })
  }

My use case was displaying terms of use. I didn't want the accept button to be enabled until the user scrolled to the bottom. I know this is late but i hope this resolves some confusion about canScrollVertically

James
  • 2,316
  • 19
  • 26