ArrayIndexOutOfBoundsException with PhotoView library and DrawerLayout

This bug caused me a lot of time to track down and fix. Especially the dead line of the project is just week ahead.

Since more and more developer using drawerlayout in the app with material design and new SDK, and PhotoView library is actually a good image viewer replacement, I think it worth to write it down.

Issue

When you are using PhotoView to display a image, and let it to be able to zoom in and out. If at the same time you are using Drawer Layout for the navigation drawer in your app, and the PhotoView component is on one of the fragment in the activity using the drawer, you may encounter the ArrayIndexOutOfBoundsException when you trying to zoom the image, either in or out.

Causes

The bug has two possible cause. One is the limitation with the View Pager, which the writer of PhotoView already provided a HackyViewPager class with the library and it saves a lot of trouble.

What the current PhotoView didn’t taking care of, is the situation that it using with Drawer Layout. Long story short, drawer layout also takes the touch event when you are trying to zoom in or out. And it doesn’t handle it well with PhotoView.

This is a problem when comes to multi fingers touch on the screen and on a inner view (which, is PhotoView in this case) of the DrawerLayout.

First, the PhotoView will do requestDisallowInterceptTouchEvent(true), so the function onInterceptTouchEvent of the DrawerLayout will be disabled.

Now, user touches the screen, with one finger, the requestDisallowInterceptTouchEvent(true) is called, now when zoom out or in, user puts another finger on. Since Drawer layout is disabled its onInterceptTouchEvent, the info of the second screen will not be handled. Each finger should have Id. That means Drawer Layout DOES NOT have the second finger ID!

When user down zooming, and lift finger, The first finger released well, but when user lifting the second finger, even it only does a very very small movement, the Drawer Layout will call ACTION_MOVE event. But it doesn’t has the ID for it! The Array structure only has size 1, and index 0. And clearly the second finger will be index 1! That’s why it throw ArrayIndexOutOfBounds.

Solution

The Solution is extends the drawer layout to a custom class, and using that to replace current default drawer layout.

package com.arthur.modules.core;

import android.content.Context;
import android.support.v4.widget.DrawerLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;

/**
 * Created by Arthur on 15-04-24.
 */
public class ArthurDrawerLayout extends DrawerLayout{
    private boolean IntercepterDisallowed = false;

//Required default constructors
    public ArthurDrawerLayout(Context context){super(context);}
    public ArthurDrawerLayout(Context context, AttributeSet attrs) {super(context, attrs);}

    public ArthurDrawerLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}


    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        /*
         * We will store the decision of the inner view, whether the intercepter is disallowed or not.
         */
        IntercepterDisallowed = disallowIntercept;
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        /*
         * When multi touch happens, handle it.
         */
        if (ev.getPointerCount() >1 && IntercepterDisallowed) {
            requestDisallowInterceptTouchEvent(false);
            boolean handled =super.dispatchTouchEvent(ev);
            requestDisallowInterceptTouchEvent(true);
            return handled;
        } else {
            return super.dispatchTouchEvent(ev);
        }
    }
}

Using this drawer layout to replace your current one. Then just don’t forget to update your layout xml file also using your new DrawerLayout.

 

Leave a Reply

Your email address will not be published. Required fields are marked *