Android – Sliding overlay

Standard

While, I was working on one of the Android App, I needed a layout which offers overlay layer and provides a semi-transparent effect to view controls beneath it. In addition to the look & feel, I was also looking for a smooth transition effect to bring overlay in the view. It should also offer responsive touch to slide it out. The requirement seems to be very simple and my hunt began to implement such control. While I could able to locate many references for transition effect, none of them were perfect and had some flaws in it. The major flaw, I noticed with the implementation was that – Once an overlay view is displayed, sliding it to the right, will sometimes cause controls beneath it receiving events and yielding weird results. The rationale behind it was simple, the usage of setOnTouchListener doesn’t provide enough control over the touch lifecycle of a control and hence the touch event was getting leaked to the controls, visible one layer below.

Frustrated after seeing too many examples, were not falling close to what I expected, I thought to create my own Layout and going through Android documentation and some nicely written blogs, I could able to implement my control (github).

Android - Sliding overlay

The secret of handling touch event, is buried inside onTouchInterceptEvent. As per the Android documentation

If onInterceptTouchEvent() returns true, the MotionEvent is intercepted, meaning it will not be passed on to the child, but rather to the onTouchEvent() method of the parent.

The setOnTouchListener, provides no such capability and hence most of the solutions, I explored were failing at some point of time (usually, if there is any child control at the point where the overlay container was getting dragged, the child ended up receiving event). Exploring the code of SlideLinearLayout.java, you will realize that the onInterceptTouchEvent method continues to return false until, it reaches to a point where delta different between the point at which event started and the current drag state becomes more than desired value (The code considers 30 as desired difference).

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            touchDownX = event.getX();
            dragStartX = touchDownX;
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            isLayoutDragging = false;
            break;
        case MotionEvent.ACTION_MOVE:
            float x = event.getX();
            float yDeltaTotal = x - dragStartX;
            if (Math.abs(yDeltaTotal) > dragDelta) {
                isLayoutDragging = true;
                dragStartX = x;
                return true;
            }
            break;
    }
    return false;
}

Have a look at the above code snippet. The method tracks the first touch event and stores coordinate in variables. Subsequently, the method checks if the user’s gesture has resulted in suitable state to mark the event as drag and it returns true to indicate that the event belongs to itself and any children should not receive that event.

Finally, the onTouchEvent takes appropriate decision of managing state of the overlay container. The logic is pretty simple, if the delta difference is not sufficient to complete the exit transition of a layout, reset its position. In case if the difference is more and user releases drag, instead of suddenly hiding the container, apply smooth transition.

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            isLayoutDragging = false;
            float upX = event.getX();
            float deltaX = upX - dragStartX;
            if (deltaX > dragDelta) {
                swipeRemove();
            } else {
                swipe(0);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            float x = event.getX();
            float xDelta = x - dragStartX;
            if (!isLayoutDragging && Math.abs(xDelta) > dragDelta) {
                isLayoutDragging = true;
            }
            if (isLayoutDragging) {
                swipe((int) xDelta);
            }
            break;
    }
    return true;
}

The entire source code for the layout is available at github along with simple example. You are free to modify the source code without any limitation and can use it in your project (Follows friendly MIT license).

Be Sociable, Share!

Leave a Reply

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