|
| 1 | +/* |
| 2 | + * Copyright (C) 2022 The Android Open Source Project |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package com.google.android.material.sidesheet; |
| 18 | + |
| 19 | +import static com.google.android.material.sidesheet.Sheet.STATE_DRAGGING; |
| 20 | +import static com.google.android.material.sidesheet.Sheet.STATE_EXPANDED; |
| 21 | +import static com.google.android.material.sidesheet.Sheet.STATE_HIDDEN; |
| 22 | +import static java.lang.Math.max; |
| 23 | + |
| 24 | +import android.view.View; |
| 25 | +import androidx.annotation.NonNull; |
| 26 | +import androidx.coordinatorlayout.widget.CoordinatorLayout; |
| 27 | +import androidx.core.view.ViewCompat; |
| 28 | +import androidx.customview.widget.ViewDragHelper; |
| 29 | +import com.google.android.material.sidesheet.Sheet.SheetEdge; |
| 30 | +import com.google.android.material.sidesheet.Sheet.StableSheetState; |
| 31 | + |
| 32 | +/** |
| 33 | + * A delegate for {@link SideSheetBehavior} to handle positioning logic for sheets based on the |
| 34 | + * right edge of the screen that expand from right to left. |
| 35 | + */ |
| 36 | +final class RightSheetDelegate extends SheetDelegate { |
| 37 | + |
| 38 | + final SideSheetBehavior<? extends View> sheetBehavior; |
| 39 | + |
| 40 | + RightSheetDelegate(@NonNull SideSheetBehavior<? extends View> sheetBehavior) { |
| 41 | + this.sheetBehavior = sheetBehavior; |
| 42 | + } |
| 43 | + |
| 44 | + @SheetEdge |
| 45 | + @Override |
| 46 | + int getSheetEdge() { |
| 47 | + return SideSheetBehavior.RIGHT; |
| 48 | + } |
| 49 | + |
| 50 | + /** Returns the sheet's offset in pixels from the origin edge when hidden. */ |
| 51 | + @Override |
| 52 | + int getHiddenOffset() { |
| 53 | + // Return the parent's width in pixels, which results in the sheet being offset entirely off of |
| 54 | + // screen. |
| 55 | + return sheetBehavior.getParentWidth(); |
| 56 | + } |
| 57 | + |
| 58 | + /** Returns the sheet's offset in pixels from the origin edge when expanded. */ |
| 59 | + @Override |
| 60 | + int getExpandedOffset() { |
| 61 | + // Calculate the expanded offset based on the width of the content. |
| 62 | + return max(0, getHiddenOffset() - sheetBehavior.getChildWidth()); |
| 63 | + } |
| 64 | + |
| 65 | + /** Whether the view has been released from a drag close to the origin edge. */ |
| 66 | + private boolean isReleasedCloseToOriginEdge(@NonNull View releasedChild) { |
| 67 | + // To be considered released close to the origin (right) edge, the released child's left must |
| 68 | + // be at least halfway to the origin (right) edge. |
| 69 | + return releasedChild.getLeft() > (getHiddenOffset() - getExpandedOffset()) / 2; |
| 70 | + } |
| 71 | + |
| 72 | + @Override |
| 73 | + @StableSheetState |
| 74 | + int calculateTargetStateOnViewReleased( |
| 75 | + @NonNull View releasedChild, float xVelocity, float yVelocity) { |
| 76 | + @StableSheetState int targetState; |
| 77 | + if (xVelocity < 0) { // Moving left, expanding outwards. |
| 78 | + targetState = STATE_EXPANDED; |
| 79 | + |
| 80 | + } else if (shouldHide(releasedChild, xVelocity)) { |
| 81 | + // Hide if the view was either released close to the origin/right edge or it was a significant |
| 82 | + // horizontal swipe; otherwise settle to expanded state. |
| 83 | + if (isSwipeSignificant(xVelocity, yVelocity) || isReleasedCloseToOriginEdge(releasedChild)) { |
| 84 | + targetState = STATE_HIDDEN; |
| 85 | + } else { |
| 86 | + targetState = STATE_EXPANDED; |
| 87 | + } |
| 88 | + } else if (xVelocity == 0f || !SheetUtils.isSwipeMostlyHorizontal(xVelocity, yVelocity)) { |
| 89 | + // If the X velocity is 0 or the swipe was mostly vertical, indicated by the Y |
| 90 | + // velocity being greater than the X velocity, settle to the nearest correct state. |
| 91 | + int currentLeft = releasedChild.getLeft(); |
| 92 | + if (Math.abs(currentLeft - getExpandedOffset()) < Math.abs(currentLeft - getHiddenOffset())) { |
| 93 | + targetState = STATE_EXPANDED; |
| 94 | + } else { |
| 95 | + targetState = STATE_HIDDEN; |
| 96 | + } |
| 97 | + } else { // Moving right; collapse inwards and hide. |
| 98 | + targetState = STATE_HIDDEN; |
| 99 | + } |
| 100 | + return targetState; |
| 101 | + } |
| 102 | + |
| 103 | + private boolean isSwipeSignificant(float xVelocity, float yVelocity) { |
| 104 | + return SheetUtils.isSwipeMostlyHorizontal(xVelocity, yVelocity) |
| 105 | + && yVelocity > sheetBehavior.getSignificantVelocityThreshold(); |
| 106 | + } |
| 107 | + |
| 108 | + @Override |
| 109 | + <V extends View> void setTargetStateOnNestedPreScroll( |
| 110 | + @NonNull CoordinatorLayout coordinatorLayout, |
| 111 | + @NonNull V child, |
| 112 | + @NonNull View target, |
| 113 | + int dx, |
| 114 | + int dy, |
| 115 | + @NonNull int[] consumed, |
| 116 | + int type) { |
| 117 | + int currentLeft = child.getLeft(); |
| 118 | + int newLeft = currentLeft - dx; |
| 119 | + if (dx < 0) { // Moving towards the left. |
| 120 | + if (newLeft > getExpandedOffset()) { |
| 121 | + consumed[1] = currentLeft - getExpandedOffset(); |
| 122 | + ViewCompat.offsetLeftAndRight(child, -consumed[1]); |
| 123 | + sheetBehavior.setStateInternal(STATE_EXPANDED); |
| 124 | + } else { |
| 125 | + if (!sheetBehavior.isDraggable()) { |
| 126 | + // Prevent dragging |
| 127 | + return; |
| 128 | + } |
| 129 | + |
| 130 | + consumed[1] = dx; |
| 131 | + ViewCompat.offsetLeftAndRight(child, -dx); |
| 132 | + sheetBehavior.setStateInternal(STATE_DRAGGING); |
| 133 | + } |
| 134 | + } else if (dx > 0) { // Moving towards the right. |
| 135 | + if (!target.canScrollHorizontally(-1)) { |
| 136 | + if (newLeft <= getHiddenOffset()) { |
| 137 | + if (!sheetBehavior.isDraggable()) { |
| 138 | + // Prevent dragging |
| 139 | + return; |
| 140 | + } |
| 141 | + |
| 142 | + consumed[1] = dx; |
| 143 | + ViewCompat.offsetLeftAndRight(child, dx); |
| 144 | + sheetBehavior.setStateInternal(STATE_DRAGGING); |
| 145 | + } else { |
| 146 | + consumed[1] = currentLeft - getHiddenOffset(); |
| 147 | + ViewCompat.offsetLeftAndRight(child, consumed[1]); |
| 148 | + sheetBehavior.setStateInternal(STATE_HIDDEN); |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + @Override |
| 155 | + @StableSheetState |
| 156 | + <V extends View> int calculateTargetStateOnStopNestedScroll(@NonNull V child) { |
| 157 | + @StableSheetState int targetState; |
| 158 | + if (sheetBehavior.getLastNestedScrollDx() > 0) { |
| 159 | + targetState = STATE_EXPANDED; |
| 160 | + } else if (sheetBehavior.shouldHide(child, sheetBehavior.getXVelocity())) { |
| 161 | + targetState = STATE_HIDDEN; |
| 162 | + } else if (sheetBehavior.getLastNestedScrollDx() == 0) { |
| 163 | + int currentLeft = child.getLeft(); |
| 164 | + |
| 165 | + if (Math.abs(currentLeft - getExpandedOffset()) < Math.abs(currentLeft - getHiddenOffset())) { |
| 166 | + targetState = STATE_EXPANDED; |
| 167 | + } else { |
| 168 | + targetState = STATE_HIDDEN; |
| 169 | + } |
| 170 | + } else { |
| 171 | + targetState = STATE_HIDDEN; |
| 172 | + } |
| 173 | + return targetState; |
| 174 | + } |
| 175 | + |
| 176 | + @Override |
| 177 | + <V extends View> boolean hasReachedExpandedOffset(@NonNull V child) { |
| 178 | + return child.getLeft() == getExpandedOffset(); |
| 179 | + } |
| 180 | + |
| 181 | + @Override |
| 182 | + boolean shouldHide(@NonNull View child, float velocity) { |
| 183 | + final float newRight = child.getRight() + velocity * sheetBehavior.getHideFriction(); |
| 184 | + return Math.abs(newRight) > sheetBehavior.getHideThreshold(); |
| 185 | + } |
| 186 | + |
| 187 | + @Override |
| 188 | + boolean isSettling(View child, int state, boolean isReleasingView) { |
| 189 | + int left = sheetBehavior.getOutwardEdgeOffsetForState(state); |
| 190 | + ViewDragHelper viewDragHelper = sheetBehavior.getViewDragHelper(); |
| 191 | + return viewDragHelper != null |
| 192 | + && (isReleasingView |
| 193 | + ? viewDragHelper.settleCapturedViewAt(left, child.getTop()) |
| 194 | + : viewDragHelper.smoothSlideViewTo(child, left, child.getTop())); |
| 195 | + } |
| 196 | + |
| 197 | + @Override |
| 198 | + <V extends View> int getOutwardEdge(@NonNull V child) { |
| 199 | + return child.getLeft(); |
| 200 | + } |
| 201 | +} |
0 commit comments