Last active
February 16, 2021 21:17
-
-
Save zerobranch/64359efd499ed38f6996390e79a5eadc to your computer and use it in GitHub Desktop.
This custom view provides an opportunity to perform swipe for any layout. SwipeLayout is just a 'Custom ViewGroup', extended from FrameLayout, which provides easy and fast to use the 'swipe to dismiss' function, without using the ItemTouchHelper, for any layout.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="SwipeLayout"> | |
<attr name="swipeDirection" format="integer"> | |
<flag name="left" value="1"/> | |
<flag name="right" value="2"/> | |
</attr> | |
<attr name="isEnabledSwipe" format="boolean"/> | |
<attr name="isFreeDragAfterOpen" format="boolean"/> | |
<attr name="isFreeHorizontalDrag" format="boolean"/> | |
<attr name="isContinuousSwipe" format="boolean"/> | |
<attr name="isTogether" format="boolean"/> | |
<attr name="rightItem" format="reference"/> | |
<attr name="leftItem" format="reference"/> | |
<attr name="draggedItem" format="reference"/> | |
<attr name="autoMovingSensitivity" format="integer"/> | |
<attr name="rightDragViewPadding" format="dimension"/> | |
<attr name="leftDragViewPadding" format="dimension"/> | |
</declare-styleable> | |
</resources> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment