/* * The MIT License (MIT) * * Copyright (c) 2018 Arman Sargsyan * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.zerobranch.layout; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Point; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.GestureDetectorCompat; import androidx.core.view.ViewCompat; import androidx.customview.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; /** * This project provides an opportunity to perform swipe for any layout, * in the direction specified by you. * <p> * Date: 2018-09-27 * Repository #https://github.com/zerobranch/SwipeLayout * * @author Arman Sargsyan */ public class SwipeLayout extends FrameLayout { public static final int LEFT = 1; public static final int RIGHT = LEFT << 1; public static final int HORIZONTAL = LEFT | RIGHT; private static final int CLOSE_POSITION = 0; private static final int NO_POSITION = -1; private static final int DEFAULT_AUTO_OPEN_SPEED = 1000; /** * Current direction of a swipe */ private int currentDirection; /** * The secondary view will move along with the main view */ private boolean isTogether; /** * Is enabled Swipe */ private boolean isEnabledSwipe; /** * Swipe to the end of the screen. * Can work without a secondary view {@link #staticLeftView} and {@link #staticRightView} * <p> * If a particular direction of the swipe is used ({@link #LEFT} or {@link #RIGHT}), * and this flag is set, then {@link #isFreeDragAfterOpen} always will be true. * <p> * If the left and right directions of the swipe are used simultaneously ({@link #HORIZONTAL}), * then this flag will be ignored */ private boolean isContinuousSwipe; /** * Moving the main view after it was open. * <p> * if {@link #isEmptyLeftView()} or {@link #isEmptyRightView()}, * then this flag will be ignored */ private boolean isFreeDragAfterOpen; /** * If a particular direction of the swipe is used ({@link #LEFT} or {@link #RIGHT}), * then this flag allows you to do the swipe in the opposite direction. * <p> * If the horizontal direction is used ({@link #HORIZONTAL}), * this flag allows you to move the main view continuously in both directions */ private boolean isFreeHorizontalDrag; /** * The right bounding border of the swipe for the main view */ private int rightDragViewPadding; /** * The left bounding border of the swipe for the main view */ private int leftDragViewPadding; /** * Sensitivity of automatic closing of the main view */ private double autoOpenSpeed; /** * Disable intercept touch event for draggable view */ private boolean disallowIntercept; private int currentDraggingState = ViewDragHelper.STATE_IDLE; private ViewDragHelper dragHelper; private GestureDetectorCompat gestureDetector; private int draggingViewLeft; private int horizontalWidth; private boolean isLeftOpen; private boolean isRightOpen; private int staticRightViewId; private int staticLeftViewId; private int draggedViewId; private View draggedView; private View staticRightView; private View staticLeftView; private SwipeActionsListener actionsListener; public SwipeLayout(Context context, AttributeSet attrs) { super(context, attrs); isLeftOpen = false; isRightOpen = false; final TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SwipeLayout); currentDirection = typedArray.getInteger(R.styleable.SwipeLayout_swipeDirection, LEFT); isFreeDragAfterOpen = typedArray.getBoolean(R.styleable.SwipeLayout_isFreeDragAfterOpen, false); isFreeHorizontalDrag = typedArray.getBoolean(R.styleable.SwipeLayout_isFreeHorizontalDrag, false); isContinuousSwipe = typedArray.getBoolean(R.styleable.SwipeLayout_isContinuousSwipe, false); isTogether = typedArray.getBoolean(R.styleable.SwipeLayout_isTogether, false); isEnabledSwipe = typedArray.getBoolean(R.styleable.SwipeLayout_isEnabledSwipe, true); staticLeftViewId = typedArray.getResourceId(R.styleable.SwipeLayout_leftItem, 0); staticRightViewId = typedArray.getResourceId(R.styleable.SwipeLayout_rightItem, 0); draggedViewId = typedArray.getResourceId(R.styleable.SwipeLayout_draggedItem, 0); autoOpenSpeed = typedArray.getInt(R.styleable.SwipeLayout_autoMovingSensitivity, DEFAULT_AUTO_OPEN_SPEED); rightDragViewPadding = (int) typedArray.getDimension(R.styleable.SwipeLayout_rightDragViewPadding, 0); leftDragViewPadding = (int) typedArray.getDimension(R.styleable.SwipeLayout_leftDragViewPadding, 0); parametersAdjustment(); typedArray.recycle(); } @Override protected void onSizeChanged(int w, int h, int oldW, int oldH) { horizontalWidth = w; super.onSizeChanged(w, h, oldW, oldH); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (disallowIntercept && isViewGroup(draggedView)) { final View neededScrollView = getNeededTouchView(event, (ViewGroup) draggedView); final Point touchPoint = new Point((int) event.getX(), (int) event.getY()); if (neededScrollView != null && isViewTouchTarget(neededScrollView, touchPoint)) { return false; } } return isSwipeViewTarget(event) && dragHelper.shouldInterceptTouchEvent(event); } @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { super.requestDisallowInterceptTouchEvent(disallowIntercept); this.disallowIntercept = disallowIntercept; } @Override protected void onFinishInflate() { if (draggedViewId != 0) { draggedView = findViewById(draggedViewId); } if (staticLeftViewId != 0) { staticLeftView = findViewById(staticLeftViewId); } if (staticRightViewId != 0) { staticRightView = findViewById(staticRightViewId); } if (draggedView == null) { throw new RuntimeException("'draggedItem' must be specified"); } else if (isTogether && currentDirection == LEFT && staticRightView == null) { throw new RuntimeException("If 'isTogether = true' 'rightItem' must be specified"); } else if (isTogether && currentDirection == RIGHT && staticLeftView == null) { throw new RuntimeException("If 'isTogether = true' 'leftItem' must be specified"); } else if (currentDirection == LEFT && !isContinuousSwipe && staticRightView == null) { throw new RuntimeException("Must be specified 'rightItem' or flag isContinuousSwipe = true"); } else if (currentDirection == RIGHT && !isContinuousSwipe && staticLeftView == null) { throw new RuntimeException("Must be specified 'leftItem' or flag isContinuousSwipe = true"); } else if (currentDirection == HORIZONTAL && (staticRightView == null || staticLeftView == null)) { throw new RuntimeException("'leftItem' and 'rightItem' must be specified"); } dragHelper = ViewDragHelper.create(this, 1.0f, dragHelperCallback); gestureDetector = new GestureDetectorCompat(getContext(), gestureDetectorCallBack); setupPost(); super.onFinishInflate(); } @Override public boolean onTouchEvent(MotionEvent event) { if (isSwipeViewTarget(event) || isMoving()) { gestureDetector.onTouchEvent(event); dragHelper.processTouchEvent(event); return true; } else { return super.onTouchEvent(event); } } @Override public void computeScroll() { if (dragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } /** * Is enabled Swipe * * @return True if swipe is enabled, false otherwise. */ public boolean isEnabledSwipe() { return isEnabledSwipe; } /** * Set the enabled swipe. * * @param enabledSwipe True if swipe is enabled, false otherwise. */ public void setEnabledSwipe(boolean enabledSwipe) { this.isEnabledSwipe = enabledSwipe; } /** * Performs manual swipe to the left * * @param animated - flag to animate opening */ public void openRight(boolean animated) { if (animated) { openRight(); } else if (isDragIdle(currentDraggingState) && ((currentDirection == LEFT && !isEmptyRightView()) || currentDirection == HORIZONTAL) && !isRightOpen) { if (isTogether) { staticRightView.offsetLeftAndRight(-1 * (isLeftOpen ? getRightViewWidth() * 2 : getRightViewWidth())); } draggedView.offsetLeftAndRight(-1 * (isLeftOpen ? getRightViewWidth() * 2 : getRightViewWidth())); draggingViewLeft -= (isLeftOpen ? getRightViewWidth() * 2 : getRightViewWidth()); updateState(); } } /** * Performs a full manual swipe to the left * * @param animated - flag to animate opening */ public void openRightCompletely(boolean animated) { if (animated) { openRightCompletely(); } else { if (isDragIdle(currentDraggingState) && currentDirection == LEFT) { if (isTogether) { staticRightView.offsetLeftAndRight(-horizontalWidth); } draggedView.offsetLeftAndRight(-horizontalWidth); draggingViewLeft -= horizontalWidth; updateState(); } } } /** * Performs manual swipe to the right * * @param animated - flag to animate opening */ public void openLeft(boolean animated) { if (animated) { openLeft(); } else if (isDragIdle(currentDraggingState) && ((currentDirection == RIGHT && !isEmptyLeftView()) || currentDirection == HORIZONTAL) && !isLeftOpen) { if (isTogether) { staticLeftView.offsetLeftAndRight((isRightOpen ? getLeftViewWidth() * 2 : getLeftViewWidth())); } draggedView.offsetLeftAndRight((isRightOpen ? getLeftViewWidth() * 2 : getLeftViewWidth())); draggingViewLeft += (isRightOpen ? getLeftViewWidth() * 2 : getLeftViewWidth()); updateState(); } } /** * Performs a full manual swipe to the right * * @param animated - flag to animate opening */ public void openLeftCompletely(boolean animated) { if (animated) { openRightCompletely(); } else { if (isDragIdle(currentDraggingState) && currentDirection == RIGHT) { if (isTogether) { staticRightView.offsetLeftAndRight(horizontalWidth); } draggedView.offsetLeftAndRight(horizontalWidth); draggingViewLeft += horizontalWidth; updateState(); } } } /** * Performs manual close * * @param animated - flag to animate closing */ public void close(boolean animated) { if (animated) { close(); } else { if (isTogether) { if (staticLeftView != null && currentDirection == RIGHT) { staticLeftView.layout(CLOSE_POSITION, staticLeftView.getTop(), staticLeftView.getWidth(), staticLeftView.getBottom()); } else if (staticRightView != null && currentDirection == LEFT) { staticRightView.layout(horizontalWidth - staticRightView.getWidth(), staticRightView.getTop(), horizontalWidth, staticRightView.getBottom()); } else if (currentDirection == HORIZONTAL && staticRightView != null && staticLeftView != null) { staticLeftView.layout(CLOSE_POSITION, staticLeftView.getTop(), staticLeftView.getWidth(), staticLeftView.getBottom()); staticRightView.layout(horizontalWidth - staticRightView.getWidth(), staticRightView.getTop(), horizontalWidth, staticRightView.getBottom()); } } draggedView.layout(CLOSE_POSITION, draggedView.getTop(), draggedView.getWidth(), draggedView.getBottom()); draggingViewLeft = CLOSE_POSITION; updateState(); } } /** * Performs manual swipe to the right */ public void openLeft() { if (isDragIdle(currentDraggingState) && ((currentDirection == RIGHT && !isEmptyLeftView()) || currentDirection == HORIZONTAL)) { moveTo(getLeftViewWidth()); } } /** * Performs manual swipe to the left */ public void openRight() { if (isDragIdle(currentDraggingState) && ((currentDirection == LEFT && !isEmptyRightView()) || currentDirection == HORIZONTAL)) { moveTo(-getRightViewWidth()); } } /** * Performs a full manual swipe to the right */ public void openLeftCompletely() { if (isDragIdle(currentDraggingState) && currentDirection == RIGHT) { moveTo(horizontalWidth); } } /** * Performs a full manual swipe to the left */ public void openRightCompletely() { if (isDragIdle(currentDraggingState) && currentDirection == LEFT) { moveTo(-horizontalWidth); } } /** * Performs manual close */ public void close() { moveTo(CLOSE_POSITION); } /** * Is moving main view */ public boolean isMoving() { return (currentDraggingState == ViewDragHelper.STATE_DRAGGING || currentDraggingState == ViewDragHelper.STATE_SETTLING); } /** * Is closed main view */ public boolean isClosed() { return draggingViewLeft == CLOSE_POSITION; } /** * Get current direction of a swipe */ public int getCurrentDirection() { return currentDirection; } /** * Set current direction of a swipe */ public SwipeLayout setCurrentDirection(int currentDirection) { this.currentDirection = currentDirection; return this; } /** * Is move the secondary view along with the main view */ public boolean isTogether() { return isTogether; } /** * The secondary view will move along with the main view */ public SwipeLayout setTogether(boolean together) { isTogether = together; return this; } /** * Swipe to the end of the screen. * Can work without a secondary view {@link #staticLeftView} and {@link #staticRightView} * <p> * If a particular direction of the swipe is used ({@link #LEFT} or {@link #RIGHT}), * and this flag is set, then {@link #isFreeDragAfterOpen} always will be true. * <p> * If the left and right directions of the swipe are used simultaneously ({@link #HORIZONTAL}), * then this flag will be ignored */ public boolean isContinuousSwipe() { return isContinuousSwipe; } /** * Swipe to the end of the screen. * Can work without a secondary view {@link #staticLeftView} and {@link #staticRightView} * <p> * If a particular direction of the swipe is used ({@link #LEFT} or {@link #RIGHT}), * and this flag is set, then {@link #isFreeDragAfterOpen} always will be true. * <p> * If the left and right directions of the swipe are used simultaneously ({@link #HORIZONTAL}), * then this flag will be ignored */ public SwipeLayout setContinuousSwipe(boolean continuousSwipe) { isContinuousSwipe = continuousSwipe; parametersAdjustment(); return this; } /** * Moving the main view after it was open. * <p> * if {@link #isEmptyLeftView()} or {@link #isEmptyRightView()}, * then this flag will be ignored */ public boolean isFreeDragAfterOpen() { return isFreeDragAfterOpen; } /** * Moving the main view after it was open. * <p> * if {@link #isEmptyLeftView()} or {@link #isEmptyRightView()}, * then this flag will be ignored */ public SwipeLayout setFreeDragAfterOpen(boolean freeDragAfterOpen) { isFreeDragAfterOpen = freeDragAfterOpen; parametersAdjustment(); return this; } /** * If a particular direction of the swipe is used ({@link #LEFT} or {@link #RIGHT}), * then this flag allows you to do the swipe in the opposite direction. * <p> * If the horizontal direction is used ({@link #HORIZONTAL}), * this flag allows you to move the main view continuously in both directions */ public boolean isFreeHorizontalDrag() { return isFreeHorizontalDrag; } /** * If a particular direction of the swipe is used ({@link #LEFT} or {@link #RIGHT}), * then this flag allows you to do the swipe in the opposite direction. * <p> * If the horizontal direction is used ({@link #HORIZONTAL}), * this flag allows you to move the main view continuously in both directions */ public SwipeLayout setFreeHorizontalDrag(boolean freeHorizontalDrag) { isFreeHorizontalDrag = freeHorizontalDrag; return this; } /** * Is open right view */ public boolean isRightOpen() { return isRightOpen; } /** * Is open left view */ public boolean isLeftOpen() { return isLeftOpen; } /** * Set swipe actions listener */ public SwipeLayout setOnActionsListener(@Nullable SwipeActionsListener actionsListener) { this.actionsListener = actionsListener; return this; } /** * Get the right bounding border of the swipe for the main view */ public int getRightDragViewPadding() { return rightDragViewPadding; } /** * Set the right bounding border of the swipe for the main view */ public SwipeLayout setRightDragViewPadding(int minRightDragViewPadding) { this.rightDragViewPadding = minRightDragViewPadding; parametersAdjustment(); return this; } /** * Get the left bounding border of the swipe for the main view */ public int getLeftDragViewPadding() { return leftDragViewPadding; } /** * Set the left bounding border of the swipe for the main view */ public SwipeLayout setLeftDragViewPadding(int minLeftDragViewPadding) { this.leftDragViewPadding = minLeftDragViewPadding; parametersAdjustment(); return this; } /** * Enable touch for ViewGroup */ public void enableTouchForViewGroup(@NonNull final ViewGroup viewGroup) { viewGroup.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { requestDisallowInterceptTouchEvent(true); return false; } }); } private void updateState() { if (isClosed()) { isLeftOpen = false; isRightOpen = false; if (actionsListener != null) { actionsListener.onClose(); } } else if (isLeftOpenCompletely() || isLeftViewOpen()) { isLeftOpen = true; isRightOpen = false; if (actionsListener != null) { actionsListener.onOpen(RIGHT, isLeftOpenCompletely()); } } else if (isRightOpenCompletely() || isRightViewOpen()) { isLeftOpen = false; isRightOpen = true; if (actionsListener != null) { actionsListener.onOpen(LEFT, isRightOpenCompletely()); } } } private GestureDetector.OnGestureListener gestureDetectorCallBack = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (getParent() != null) { getParent().requestDisallowInterceptTouchEvent(true); } return false; } }; private final ViewDragHelper.Callback dragHelperCallback = new ViewDragHelper.Callback() { @Override public void onViewDragStateChanged(int state) { if (state == currentDraggingState) return; if (isIdleAfterMoving(state)) { updateState(); } currentDraggingState = state; } @Override public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) { draggingViewLeft = left; if (isTogether) { if (currentDirection == LEFT) { staticRightView.offsetLeftAndRight(dx); } else if (currentDirection == RIGHT) { staticLeftView.offsetLeftAndRight(dx); } else if (currentDirection == HORIZONTAL) { staticLeftView.offsetLeftAndRight(dx); staticRightView.offsetLeftAndRight(dx); } } } @Override public int getViewHorizontalDragRange(@NonNull View child) { return horizontalWidth; } @Override public boolean tryCaptureView(@NonNull View view, int pointerId) { return view.getId() == draggedView.getId(); } @Override public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) { if (!isEnabledSwipe) { return CLOSE_POSITION; } switch (currentDirection) { case LEFT: return clampLeftViewPosition(left); case RIGHT: return clampRightViewPosition(left); case HORIZONTAL: return clampHorizontalViewPosition(left, dx); default: return CLOSE_POSITION; } } @Override public void onViewReleased(@NonNull View releasedChild, float xVel, float yVel) { int finalXDraggingView = CLOSE_POSITION; if (currentDirection == LEFT) { finalXDraggingView = getFinalXLeftDirection(xVel); } else if (currentDirection == RIGHT) { finalXDraggingView = getFinalXRightDirection(xVel); } else if (currentDirection == HORIZONTAL) { finalXDraggingView = getFinalXHorizontalDirection(xVel); if (finalXDraggingView == NO_POSITION) { finalXDraggingView = getPreviousPosition(); } } if (dragHelper.settleCapturedViewAt(finalXDraggingView, draggedView.getTop())) { ViewCompat.postInvalidateOnAnimation(SwipeLayout.this); } } }; private int clampLeftViewPosition(int left) { if (isContinuousSwipe && isEmptyRightView()) { if (isFreeHorizontalDrag) { return left > horizontalWidth ? CLOSE_POSITION : Math.max(left, -horizontalWidth); } else { return left > CLOSE_POSITION ? CLOSE_POSITION : Math.max(left, -horizontalWidth); } } if (isFreeDragAfterOpen) { if (isFreeHorizontalDrag) { return left > horizontalWidth ? CLOSE_POSITION : Math.max(left, leftDragViewPadding - horizontalWidth); } return left > CLOSE_POSITION ? CLOSE_POSITION : Math.max(left, leftDragViewPadding - horizontalWidth); } if (isFreeHorizontalDrag) { return left > horizontalWidth ? CLOSE_POSITION : Math.max(left, -getRightViewWidth()); } return left > CLOSE_POSITION ? CLOSE_POSITION : Math.max(left, -getRightViewWidth()); } private int clampRightViewPosition(int left) { if (isContinuousSwipe && isEmptyLeftView()) { if (isFreeHorizontalDrag) { return left < -horizontalWidth ? -horizontalWidth : Math.min(left, horizontalWidth); } else { return left < CLOSE_POSITION ? CLOSE_POSITION : Math.min(left, horizontalWidth); } } if (isFreeDragAfterOpen) { if (isFreeHorizontalDrag) { return left < -horizontalWidth ? -horizontalWidth : Math.min(left, horizontalWidth - rightDragViewPadding); } return left < CLOSE_POSITION ? CLOSE_POSITION : Math.min(left, horizontalWidth - rightDragViewPadding); } if (isFreeHorizontalDrag) { return left < -horizontalWidth ? -horizontalWidth : Math.min(left, getLeftViewWidth()); } return left < CLOSE_POSITION ? CLOSE_POSITION : Math.min(left, getLeftViewWidth()); } private int clampHorizontalViewPosition(int left, int dx) { if (!isFreeHorizontalDrag && isLeftOpen && dx < 0) { return Math.max(left, CLOSE_POSITION); } if (!isFreeHorizontalDrag && isRightOpen && dx > 0) { return Math.min(left, CLOSE_POSITION); } if (!isFreeDragAfterOpen && left > CLOSE_POSITION) { return Math.min(left, getLeftViewWidth()); } if (!isFreeDragAfterOpen && left < CLOSE_POSITION) { return Math.max(left, -getRightViewWidth()); } return left < CLOSE_POSITION ? Math.max(left, leftDragViewPadding - horizontalWidth) : Math.min(left, horizontalWidth - rightDragViewPadding); } private int getPreviousPosition() { if (isLeftOpen) { return getLeftViewWidth(); } else if (isRightOpen) { return -getRightViewWidth(); } else { return CLOSE_POSITION; } } private int getFinalXLeftDirection(float xVel) { if (isContinuousSwipe) { if (isEmptyRightView()) { if ((draggingViewLeft < CLOSE_POSITION && Math.abs(draggingViewLeft) > horizontalWidth / 2) || xVel < -autoOpenSpeed) { return -horizontalWidth; } return CLOSE_POSITION; } if (isContinuousSwipeToLeft(xVel)) { return -horizontalWidth; } } final boolean settleToOpen; if (xVel > autoOpenSpeed) { settleToOpen = false; } else if (xVel < -autoOpenSpeed) { settleToOpen = true; } else if (draggingViewLeft < CLOSE_POSITION && Math.abs(draggingViewLeft) > getRightViewWidth() / 2) { settleToOpen = true; } else if (draggingViewLeft < CLOSE_POSITION && Math.abs(draggingViewLeft) < getRightViewWidth() / 2) { settleToOpen = false; } else { settleToOpen = false; } return settleToOpen ? -getRightViewWidth() : CLOSE_POSITION; } private int getFinalXRightDirection(float xVel) { if (isContinuousSwipe) { if (isEmptyLeftView()) { if ((draggingViewLeft > CLOSE_POSITION && Math.abs(draggingViewLeft) > horizontalWidth / 2) || xVel > autoOpenSpeed) { return horizontalWidth; } return CLOSE_POSITION; } if (isContinuousSwipeToRight(xVel)) { return horizontalWidth; } } final boolean settleToOpen; if (xVel > autoOpenSpeed) { settleToOpen = true; } else if (xVel < -autoOpenSpeed) { settleToOpen = false; } else if (draggingViewLeft > CLOSE_POSITION && Math.abs(draggingViewLeft) > getLeftViewWidth() / 2) { settleToOpen = true; } else if (draggingViewLeft > CLOSE_POSITION && Math.abs(draggingViewLeft) < getLeftViewWidth() / 2) { settleToOpen = false; } else { settleToOpen = false; } return settleToOpen ? getLeftViewWidth() : CLOSE_POSITION; } private int getFinalXHorizontalDirection(float xVel) { if (isSwipeToOpenLeft(xVel)) { return getLeftViewWidth(); } else if (isSwipeToOpenRight(xVel)) { return -getRightViewWidth(); } else if (isSwipeToClose(xVel)) { return CLOSE_POSITION; } return NO_POSITION; } private boolean isContinuousSwipeToRight(float xVel) { return (xVel > autoOpenSpeed && Math.abs(draggingViewLeft) > getLeftViewWidth()) || (draggingViewLeft > CLOSE_POSITION && Math.abs(draggingViewLeft) > horizontalWidth / 2); } private boolean isContinuousSwipeToLeft(float xVel) { return (xVel < -autoOpenSpeed && Math.abs(draggingViewLeft) > getRightViewWidth()) || (draggingViewLeft < CLOSE_POSITION && Math.abs(draggingViewLeft) > horizontalWidth / 2); } private boolean isSwipeToOpenRight(float xVel) { if (xVel > 0) { return false; } return (draggingViewLeft < CLOSE_POSITION && xVel < -autoOpenSpeed) || (draggingViewLeft < CLOSE_POSITION && Math.abs(draggingViewLeft) > getRightViewWidth() / 2); } private boolean isSwipeToOpenLeft(float xVel) { if (xVel < 0) { return false; } return (draggingViewLeft > CLOSE_POSITION && xVel > autoOpenSpeed) || (draggingViewLeft > CLOSE_POSITION && Math.abs(draggingViewLeft) > getLeftViewWidth() / 2); } private boolean isSwipeToClose(float xVel) { return (draggingViewLeft >= CLOSE_POSITION && xVel < -autoOpenSpeed) || (draggingViewLeft <= CLOSE_POSITION && xVel > autoOpenSpeed) || (draggingViewLeft >= CLOSE_POSITION && Math.abs(draggingViewLeft) < getLeftViewWidth() / 2) || (draggingViewLeft <= CLOSE_POSITION && Math.abs(draggingViewLeft) < getRightViewWidth() / 2); } private void setupPost() { if (isTogether) { post(new Runnable() { @Override public void run() { if (currentDirection == LEFT) { staticRightView.setX(horizontalWidth); } else if (currentDirection == RIGHT) { staticLeftView.setX(-staticLeftView.getWidth()); } else if (currentDirection == HORIZONTAL) { staticRightView.setX(horizontalWidth); staticLeftView.setX(-staticLeftView.getWidth()); } } }); } } private boolean isViewTouchTarget(View view, Point point) { return point.y >= view.getTop() && point.y < view.getBottom() && point.x >= view.getLeft() && point.y < view.getRight(); } private View getNeededTouchView(MotionEvent event, ViewGroup rootView) { if (rootView.onInterceptTouchEvent(event)) { return rootView; } int count = rootView.getChildCount(); for (int i = 0; i < count; i++) { View view = rootView.getChildAt(i); if (!isViewGroup(view)) { continue; } View neededScrollView = getNeededTouchView(event, (ViewGroup) view); if (neededScrollView != null) { return neededScrollView; } } return null; } private boolean isViewGroup(View view) { return view instanceof ViewGroup; } private boolean isSwipeViewTarget(MotionEvent event) { final int[] swipeViewLocation = new int[2]; draggedView.getLocationOnScreen(swipeViewLocation); final int upperLimit = swipeViewLocation[1] + draggedView.getMeasuredHeight(); final int lowerLimit = swipeViewLocation[1]; final int y = (int) event.getRawY(); return (y > lowerLimit && y < upperLimit); } private boolean isIdleAfterMoving(int state) { return (currentDraggingState == ViewDragHelper.STATE_DRAGGING || currentDraggingState == ViewDragHelper.STATE_SETTLING) && state == ViewDragHelper.STATE_IDLE; } private boolean isDragIdle(int state) { return state == ViewDragHelper.STATE_IDLE; } private boolean isRightViewOpen() { return staticRightView != null && draggingViewLeft == -getRightViewWidth(); } private boolean isLeftViewOpen() { return staticLeftView != null && draggingViewLeft == getLeftViewWidth(); } private boolean isRightOpenCompletely() { return draggingViewLeft == -horizontalWidth; } private boolean isLeftOpenCompletely() { return draggingViewLeft == horizontalWidth; } private int getLeftViewWidth() { return staticLeftView.getWidth(); } private int getRightViewWidth() { return staticRightView.getWidth(); } private boolean isEmptyLeftView() { return staticLeftView == null; } private boolean isEmptyRightView() { return staticRightView == null; } private void parametersAdjustment() { if (isContinuousSwipe && currentDirection != HORIZONTAL) { isFreeDragAfterOpen = true; } if (currentDirection == HORIZONTAL) { rightDragViewPadding = 0; leftDragViewPadding = 0; } } private void moveTo(int x) { dragHelper.smoothSlideViewTo(draggedView, x, draggedView.getTop()); ViewCompat.postInvalidateOnAnimation(this); } public interface SwipeActionsListener { void onOpen(int direction, boolean isContinuous); void onClose(); } }