1 /* 2 * Copyright (C) 2017 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 androidx.wear.widget; 18 19 import android.content.Context; 20 import android.util.AttributeSet; 21 22 import androidx.annotation.UiThread; 23 24 import java.util.ArrayList; 25 26 /** 27 * A layout enabling left-to-right swipe-to-dismiss, intended for use within an activity. 28 * 29 * <p>At least one listener must be {@link #addCallback(Callback) added} to act on a dismissal 30 * action. A listener will typically remove a containing view or fragment from the current 31 * activity. 32 * 33 * <p>To suppress a swipe-dismiss gesture, at least one contained view must be scrollable, 34 * indicating that it would like to consume any horizontal touch gestures in that direction. In 35 * this case this view will only allow swipe-to-dismiss on the very edge of the left-hand-side of 36 * the screen. If you wish to entirely disable the swipe-to-dismiss gesture, 37 * {@link #setSwipeable(boolean)} can be used for more direct control over the feature. 38 */ 39 @UiThread 40 public class SwipeDismissFrameLayout extends DismissibleFrameLayout { 41 42 public static final float DEFAULT_DISMISS_DRAG_WIDTH_RATIO = .33f; 43 44 45 /** Implement this callback to act on particular stages of the dismissal. */ 46 @UiThread 47 public abstract static class Callback { 48 49 /** 50 * Notifies listeners that the view is now being dragged as part of a dismiss gesture. 51 * 52 * @param layout The layout associated with this callback. 53 */ onSwipeStarted(SwipeDismissFrameLayout layout)54 public void onSwipeStarted(SwipeDismissFrameLayout layout) { 55 } 56 57 /** 58 * Notifies listeners that the swipe gesture has ended without a dismissal. 59 * 60 * @param layout The layout associated with this callback. 61 */ onSwipeCanceled(SwipeDismissFrameLayout layout)62 public void onSwipeCanceled(SwipeDismissFrameLayout layout) { 63 } 64 65 /** 66 * Notifies listeners that the dismissal is complete and the view is now off screen. 67 * @param layout The layout associated with this callback. 68 */ onDismissed(SwipeDismissFrameLayout layout)69 public void onDismissed(SwipeDismissFrameLayout layout) { 70 } 71 } 72 73 final ArrayList<Callback> mCallbacksCompat = new ArrayList<>(); 74 75 /** 76 * Simple constructor to use when creating a view from code. 77 * 78 * @param context The {@link Context} the view is running in, through which it can access the 79 * current theme, resources, etc. 80 */ SwipeDismissFrameLayout(Context context)81 public SwipeDismissFrameLayout(Context context) { 82 this(context, null, 0); 83 } 84 85 /** 86 * Constructor that is called when inflating a view from XML. This is called when a view is 87 * being constructed from an XML file, supplying attributes that were specified in the XML file. 88 * This version uses a default style of 0, so the only attribute values applied are those in the 89 * Context's Theme and the given AttributeSet. 90 * 91 * <p> 92 * 93 * <p>The method onFinishInflate() will be called after all children have been added. 94 * 95 * @param context The {@link Context} the view is running in, through which it can access the 96 * current theme, resources, etc. 97 * @param attrs The attributes of the XML tag that is inflating the view. 98 */ SwipeDismissFrameLayout(Context context, AttributeSet attrs)99 public SwipeDismissFrameLayout(Context context, AttributeSet attrs) { 100 this(context, attrs, 0); 101 } 102 103 /** 104 * Perform inflation from XML and apply a class-specific base style from a theme attribute. 105 * This constructor allows subclasses to use their own base style when they are inflating. 106 * 107 * @param context The {@link Context} the view is running in, through which it can access the 108 * current theme, resources, etc. 109 * @param attrs The attributes of the XML tag that is inflating the view. 110 * @param defStyle An attribute in the current theme that contains a reference to a style 111 * resource that supplies default values for the view. Can be 0 to not look for 112 * defaults. 113 */ SwipeDismissFrameLayout(Context context, AttributeSet attrs, int defStyle)114 public SwipeDismissFrameLayout(Context context, AttributeSet attrs, int defStyle) { 115 this(context, attrs, defStyle, 0); 116 } 117 118 /** 119 * Perform inflation from XML and apply a class-specific base style from a theme attribute. 120 * This constructor allows subclasses to use their own base style when they are inflating. 121 * 122 * @param context The {@link Context} the view is running in, through which it can access the 123 * current theme, resources, etc. 124 * @param attrs The attributes of the XML tag that is inflating the view. 125 * @param defStyle An attribute in the current theme that contains a reference to a style 126 * resource that supplies default values for the view. Can be 0 to not look for 127 * defaults. 128 * @param defStyleRes It allows a style resource to be specified when creating the view. 129 */ SwipeDismissFrameLayout(Context context, AttributeSet attrs, int defStyle, int defStyleRes)130 public SwipeDismissFrameLayout(Context context, AttributeSet attrs, int defStyle, 131 int defStyleRes) { 132 super(context, attrs, defStyle, defStyleRes); 133 } 134 135 /** Adds a callback for dismissal. */ addCallback(Callback callback)136 public void addCallback(Callback callback) { 137 if (callback == null) { 138 throw new NullPointerException("addCallback called with null callback"); 139 } 140 141 mCallbacksCompat.add(callback); 142 } 143 144 /** Removes a callback that was added with {@link #addCallback(Callback)}. */ removeCallback(Callback callback)145 public void removeCallback(Callback callback) { 146 if (callback == null) { 147 throw new NullPointerException("removeCallback called with null callback"); 148 } 149 if (!mCallbacksCompat.remove(callback)) { 150 throw new IllegalStateException("removeCallback called with nonexistent callback"); 151 } 152 } 153 154 /** 155 * Set the layout to be dismissible by swipe or not. 156 * @param swipeable Whether the layout should react to the swipe gesture. 157 */ setSwipeable(boolean swipeable)158 public void setSwipeable(boolean swipeable) { 159 super.setSwipeDismissible(swipeable); 160 } 161 162 /** Returns true if the frame layout can be dismissed by swipe gestures. */ isSwipeable()163 public boolean isSwipeable() { 164 return super.isDismissableBySwipe(); 165 } 166 167 /** 168 * Sets the minimum ratio of the screen after which the swipe gesture is treated as 169 * swipe-to-dismiss. 170 * 171 * @param ratio the ratio of the screen at which the swipe gesture is treated as 172 * swipe-to-dismiss. should be provided as a fraction of the screen 173 */ setDismissMinDragWidthRatio(float ratio)174 public void setDismissMinDragWidthRatio(float ratio) { 175 if (isSwipeable()) { 176 getSwipeDismissController().setDismissMinDragWidthRatio(ratio); 177 } 178 } 179 180 /** 181 * Gets the minimum ratio of the screen after which the swipe gesture is treated as 182 * swipe-to-dismiss. 183 */ getDismissMinDragWidthRatio()184 public float getDismissMinDragWidthRatio() { 185 if (isSwipeable()) { 186 return getSwipeDismissController().getDismissMinDragWidthRatio(); 187 } 188 return DEFAULT_DISMISS_DRAG_WIDTH_RATIO; 189 } 190 191 @Override performDismissFinishedCallbacks()192 protected void performDismissFinishedCallbacks() { 193 super.performDismissFinishedCallbacks(); 194 for (int i = mCallbacksCompat.size() - 1; i >= 0; i--) { 195 mCallbacksCompat.get(i).onDismissed(this); 196 } 197 } 198 199 @Override performDismissStartedCallbacks()200 protected void performDismissStartedCallbacks() { 201 super.performDismissStartedCallbacks(); 202 for (int i = mCallbacksCompat.size() - 1; i >= 0; i--) { 203 mCallbacksCompat.get(i).onSwipeStarted(this); 204 } 205 } 206 207 @Override performDismissCanceledCallbacks()208 protected void performDismissCanceledCallbacks() { 209 super.performDismissCanceledCallbacks(); 210 for (int i = mCallbacksCompat.size() - 1; i >= 0; i--) { 211 mCallbacksCompat.get(i).onSwipeCanceled(this); 212 } 213 } 214 }