com.freecats.demo.swipebackdemo.view.SwipeBackLayout.java Source code

Java tutorial

Introduction

Here is the source code for com.freecats.demo.swipebackdemo.view.SwipeBackLayout.java

Source

package com.freecats.demo.swipebackdemo.view;

/*
 * Copyright 2015 Eric Liu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.app.Activity;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.ScrollView;

/**
 * Swipe or Pull to finish a Activity.
 * <p/>
 * This layout must be a root layout and contains only one direct child view.
 * <p/>
 * </style>
 * <p/>
 * Created by Eric on 15/1/8.
 *
 */
public class SwipeBackLayout extends ViewGroup {

    public enum DragEdge {
        LEFT,

        TOP,

        RIGHT,

        BOTTOM
    }

    private DragEdge dragEdge = DragEdge.LEFT;

    public void setDragEdge(DragEdge dragEdge) {
        this.dragEdge = dragEdge;
    }

    private static final double AUTO_FINISHED_SPEED_LIMIT = 2000.0;

    //    private final ViewDragHelper viewDragHelper;

    private View target;

    private View scrollChild;

    private int verticalDragRange = 0;

    private int horizontalDragRange = 0;

    private int draggingState = 0;

    private int draggingOffset;

    /**
     * Trigger range when pulling the edge,only in this range will trigger drag action
     *
     * @see SwipeBackLayout#isTrigger(MotionEvent)
     */
    private int triggerRange = 50;

    private boolean isCanTrigger = false;

    private float downX = 0;

    /**
     * Whether allow to pull this layout.
     */
    private boolean enablePullToBack = true;

    private static final float BACK_FACTOR = 0.2f;

    /**
     * the minimum distance of swipe action to finish activity
     */
    private static final int BACK_RANGE = 200;
    /**
     * the anchor of calling finish.
     */
    private float finishAnchor = 0;

    /**
     * Set the anchor of calling finish.
     *
     * @param offset
     */
    public void setFinishAnchor(float offset) {
        finishAnchor = offset;
    }

    private boolean enableFlingBack = true;

    /**
     * Whether allow to finish activity by fling the layout.
     *
     * @param b
     */
    public void setEnableFlingBack(boolean b) {
        enableFlingBack = b;
    }

    private SwipeBackListener swipeBackListener;

    @Deprecated
    public void setOnPullToBackListener(SwipeBackListener listener) {
        swipeBackListener = listener;
    }

    public void setOnSwipeBackListener(SwipeBackListener listener) {
        swipeBackListener = listener;
    }

    public void setTriggerRange(int triggerRange) {
        this.triggerRange = triggerRange;
    }

    public SwipeBackLayout(Context context) {
        this(context, null);
    }

    public SwipeBackLayout(Context context, AttributeSet attrs) {
        super(context, attrs);

        //        viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack());
    }

    public void setScrollChild(View view) {
        scrollChild = view;
    }

    public void setEnablePullToBack(boolean b) {
        enablePullToBack = b;
    }

    private void ensureTarget() {
        if (target == null) {
            if (getChildCount() > 1) {
                throw new IllegalStateException("SwipeBackLayout must contains only one direct child");
            }
            target = getChildAt(0);

            if (scrollChild == null && target != null) {
                if (target instanceof ViewGroup) {
                    findScrollView((ViewGroup) target);
                } else {
                    scrollChild = target;
                }

            }
        }
    }

    /**
     * Find out the scrollable child view from a ViewGroup.
     *
     * @param viewGroup
     */
    private void findScrollView(ViewGroup viewGroup) {
        scrollChild = viewGroup;
        if (viewGroup.getChildCount() > 0) {
            int count = viewGroup.getChildCount();
            View child;
            for (int i = 0; i < count; i++) {
                child = viewGroup.getChildAt(i);
                if (child instanceof AbsListView || child instanceof ScrollView || child instanceof ViewPager
                        || child instanceof WebView) {
                    scrollChild = child;
                    return;
                }
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        if (getChildCount() == 0)
            return;

        View child = getChildAt(0);

        int childWidth = width - getPaddingLeft() - getPaddingRight();
        int childHeight = height - getPaddingTop() - getPaddingBottom();
        int childLeft = getPaddingLeft();
        int childTop = getPaddingTop();
        int childRight = childLeft + childWidth;
        int childBottom = childTop + childHeight;
        child.layout(childLeft, childTop, childRight, childBottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (getChildCount() > 1) {
            throw new IllegalStateException("SwipeBackLayout must contains only one direct child.");
        }

        if (getChildCount() > 0) {
            int measureWidth = MeasureSpec.makeMeasureSpec(
                    getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
            int measureHeight = MeasureSpec.makeMeasureSpec(
                    getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
            getChildAt(0).measure(measureWidth, measureHeight);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        verticalDragRange = h;
        horizontalDragRange = w;

        switch (dragEdge) {
        case TOP:
        case BOTTOM:
            finishAnchor = finishAnchor > 0 ? finishAnchor : verticalDragRange * BACK_FACTOR;
            break;
        case LEFT:
        case RIGHT:
            finishAnchor = finishAnchor > 0 ? finishAnchor : horizontalDragRange * BACK_FACTOR;
            break;
        }
    }

    private int getDragRange() {
        switch (dragEdge) {
        case TOP:
        case BOTTOM:
            return verticalDragRange;
        case LEFT:
        case RIGHT:
            return horizontalDragRange;
        default:
            return verticalDragRange;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean handled = false;
        ensureTarget();
        if (isEnabled() && isTrigger(ev)) {
            //            handled = viewDragHelper.shouldInterceptTouchEvent(ev);
            handled = true;
        } else {
            //            viewDragHelper.cancel();
        }
        return !handled ? super.onInterceptTouchEvent(ev) : handled;
    }

    /**
     * decide whether should trigger drag action or not
     *
     * @param ev
     * @return true if (x,y) point of ACTION_DOWN is in the range of {@link SwipeBackLayout#triggerRange} ,false otherwise
     */
    private boolean isTrigger(MotionEvent ev) {

        if (MotionEvent.ACTION_DOWN == ev.getAction()) {
            switch (dragEdge) {
            case LEFT:
                isCanTrigger = ev.getX() < triggerRange;
                break;
            case TOP:
                isCanTrigger = ev.getY() < triggerRange;
                break;
            case RIGHT:
                isCanTrigger = this.getWidth() - ev.getX() < triggerRange;
                break;
            case BOTTOM:
                isCanTrigger = this.getHeight() - ev.getY() < triggerRange;
                break;
            default:
                break;
            }
        } else {
            return isCanTrigger;
        }
        return isCanTrigger;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (enablePullToBack && isTrigger(event)) {
            //            viewDragHelper.processTouchEvent(event);
            if (isCanFinish(event)) {
                finish();
            }

            return true;
        } else {
            return false;
        }
    }

    private boolean isCanFinish(MotionEvent event) {
        boolean result = false;
        float currentX = 0;
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            downX = event.getRawX();
            break;
        case MotionEvent.ACTION_MOVE:

            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            currentX = event.getRawX();
            break;
        default:
            break;
        }
        result = currentX - downX > BACK_RANGE;
        return result;
    }

    @Override
    public void computeScroll() {
        //        if (viewDragHelper.continueSettling(true)) {
        //            //ViewCompat.postInvalidateOnAnimation(this);
        //        }
    }

    public boolean canChildScrollUp() {
        return ViewCompat.canScrollVertically(scrollChild, -1);
    }

    public boolean canChildScrollDown() {
        return ViewCompat.canScrollVertically(scrollChild, 1);
    }

    private boolean canChildScrollRight() {
        return ViewCompat.canScrollHorizontally(scrollChild, -1);
    }

    private boolean canChildScrollLeft() {
        return ViewCompat.canScrollHorizontally(scrollChild, 1);
    }

    private void finish() {
        Activity act = (Activity) getContext();
        act.finish();
    }

    //    private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
    //
    //        @Override
    //        public boolean tryCaptureView(View child, int pointerId) {
    //            return child == target && enablePullToBack;
    //        }
    //
    //        @Override
    //        public int getViewVerticalDragRange(View child) {
    //            return verticalDragRange;
    //        }
    //
    //        @Override
    //        public int getViewHorizontalDragRange(View child) {
    //            return horizontalDragRange;
    //        }
    //
    //        @Override
    //        public int clampViewPositionVertical(View child, int top, int dy) {
    //
    //            int result = 0;
    //
    //            if (dragEdge == DragEdge.TOP && !canChildScrollUp() && top > 0) {
    //                final int topBound = getPaddingTop();
    //                final int bottomBound = verticalDragRange;
    //                result = Math.min(Math.max(top, topBound), bottomBound);
    //            } else if (dragEdge == DragEdge.BOTTOM && !canChildScrollDown() && top < 0) {
    //                final int topBound = -verticalDragRange;
    //                final int bottomBound = getPaddingTop();
    //                result = Math.min(Math.max(top, topBound), bottomBound);
    //            }
    //
    //            return result;
    //        }
    //
    //        @Override
    //        public int clampViewPositionHorizontal(View child, int left, int dx) {
    //
    //            int result = 0;
    //
    //            if (dragEdge == DragEdge.LEFT && !canChildScrollRight() && left > 0) {
    //                final int leftBound = getPaddingLeft();
    //                final int rightBound = horizontalDragRange;
    //                result = Math.min(Math.max(left, leftBound), rightBound);
    //            } else if (dragEdge == DragEdge.RIGHT && !canChildScrollLeft() && left < 0) {
    //                final int leftBound = -horizontalDragRange;
    //                final int rightBound = getPaddingLeft();
    //                result = Math.min(Math.max(left, leftBound), rightBound);
    //            }
    //
    //            return result;
    //        }
    //
    //        @Override
    //        public void onViewDragStateChanged(int state) {
    //            if (state == draggingState) return;
    //
    //            if ((draggingState == ViewDragHelper.STATE_DRAGGING || draggingState == ViewDragHelper.STATE_SETTLING) &&
    //                    state == ViewDragHelper.STATE_IDLE) {
    //                // the view stopped from moving.
    //                if (draggingOffset == getDragRange()) {
    //                    finish();
    //                }
    //            }
    //
    //            draggingState = state;
    //        }
    //
    //
    //        @Override
    //        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
    //            switch (dragEdge) {
    //                case TOP:
    //                case BOTTOM:
    //                    draggingOffset = Math.abs(top);
    //                    break;
    //                case LEFT:
    //                case RIGHT:
    //                    draggingOffset = Math.abs(left);
    //                    break;
    //                default:
    //                    break;
    //            }
    //
    //            //The proportion of the sliding.
    //            float fractionAnchor = (float) draggingOffset / finishAnchor;
    //            if (fractionAnchor >= 1) fractionAnchor = 1;
    //
    //            float fractionScreen = (float) draggingOffset / (float) getDragRange();
    //            if (fractionScreen >= 1) fractionScreen = 1;
    //
    //            if (swipeBackListener != null) {
    //                swipeBackListener.onViewPositionChanged(fractionAnchor, fractionScreen);
    //            }
    //        }
    //
    //        @Override
    //        public void onViewReleased(View releasedChild, float xvel, float yvel) {
    //            if (draggingOffset == 0) return;
    //
    //            if (draggingOffset == getDragRange()) return;
    //
    //            boolean isBack = false;
    //
    //            if (enableFlingBack && backBySpeed(xvel, yvel)) {
    //                isBack = !canChildScrollUp();
    //            } else if (draggingOffset >= finishAnchor) {
    //                isBack = true;
    //            } else if (draggingOffset < finishAnchor) {
    //                isBack = false;
    //            }
    //
    //            int finalLeft;
    //            int finalTop;
    //            switch (dragEdge) {
    //                case LEFT:
    //                    finalLeft = isBack ? horizontalDragRange : 0;
    //                    smoothScrollToX(finalLeft);
    //                    break;
    //                case RIGHT:
    //                    finalLeft = isBack ? -horizontalDragRange : 0;
    //                    smoothScrollToX(finalLeft);
    //                    break;
    //                case TOP:
    //                    finalTop = isBack ? verticalDragRange : 0;
    //                    smoothScrollToY(finalTop);
    //                    break;
    //                case BOTTOM:
    //                    finalTop = isBack ? -verticalDragRange : 0;
    //                    smoothScrollToY(finalTop);
    //                    break;
    //            }
    //
    //        }
    //    }

    private boolean backBySpeed(float xvel, float yvel) {
        switch (dragEdge) {
        case TOP:
        case BOTTOM:
            if (Math.abs(yvel) > Math.abs(xvel) && Math.abs(yvel) > AUTO_FINISHED_SPEED_LIMIT) {
                return dragEdge == DragEdge.TOP ? !canChildScrollUp() : !canChildScrollDown();
            }
            break;
        case LEFT:
        case RIGHT:
            if (Math.abs(xvel) > Math.abs(yvel) && Math.abs(xvel) > AUTO_FINISHED_SPEED_LIMIT) {
                return dragEdge == DragEdge.LEFT ? !canChildScrollLeft() : !canChildScrollRight();
            }
            break;
        }
        return false;
    }

    //    private void smoothScrollToX(int finalLeft) {
    //        if (viewDragHelper.settleCapturedViewAt(finalLeft, 0)) {
    //            ViewCompat.postInvalidateOnAnimation(SwipeBackLayout.this);
    //        }
    //    }

    //    private void smoothScrollToY(int finalTop) {
    //        if (viewDragHelper.settleCapturedViewAt(0, finalTop)) {
    //            ViewCompat.postInvalidateOnAnimation(SwipeBackLayout.this);
    //        }
    //    }

    public interface SwipeBackListener {

        /**
         * Return scrolled fraction of the layout.
         *
         * @param fractionAnchor relative to the anchor.
         * @param fractionScreen relative to the screen.
         */
        void onViewPositionChanged(float fractionAnchor, float fractionScreen);

    }

}