SwipeDismissFrameLayout.java
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
package androidx.wear.widget;
import android.content.Context;
import android.util.AttributeSet;
import androidx.annotation.UiThread;
import java.util.ArrayList;
/**
* A layout enabling left-to-right swipe-to-dismiss, intended for use within an activity.
*
* <p>At least one listener must be {@link #addCallback(Callback) added} to act on a dismissal
* action. A listener will typically remove a containing view or fragment from the current
* activity.
*
* <p>To suppress a swipe-dismiss gesture, at least one contained view must be scrollable,
* indicating that it would like to consume any horizontal touch gestures in that direction. In
* this case this view will only allow swipe-to-dismiss on the very edge of the left-hand-side of
* the screen. If you wish to entirely disable the swipe-to-dismiss gesture,
* {@link #setSwipeable(boolean)} can be used for more direct control over the feature.
*/
@UiThread
public class SwipeDismissFrameLayout extends DismissibleFrameLayout {
public static final float DEFAULT_DISMISS_DRAG_WIDTH_RATIO = .33f;
/** Implement this callback to act on particular stages of the dismissal. */
@UiThread
public abstract static class Callback {
/**
* Notifies listeners that the view is now being dragged as part of a dismiss gesture.
*
* @param layout The layout associated with this callback.
*/
public void onSwipeStarted(SwipeDismissFrameLayout layout) {
}
/**
* Notifies listeners that the swipe gesture has ended without a dismissal.
*
* @param layout The layout associated with this callback.
*/
public void onSwipeCanceled(SwipeDismissFrameLayout layout) {
}
/**
* Notifies listeners that the dismissal is complete and the view is now off screen.
* @param layout The layout associated with this callback.
*/
public void onDismissed(SwipeDismissFrameLayout layout) {
}
}
final ArrayList<Callback> mCallbacksCompat = new ArrayList<>();
/**
* Simple constructor to use when creating a view from code.
*
* @param context The {@link Context} the view is running in, through which it can access the
* current theme, resources, etc.
*/
public SwipeDismissFrameLayout(Context context) {
this(context, null, 0);
}
/**
* Constructor that is called when inflating a view from XML. This is called when a view is
* being constructed from an XML file, supplying attributes that were specified in the XML file.
* This version uses a default style of 0, so the only attribute values applied are those in the
* Context's Theme and the given AttributeSet.
*
* <p>
*
* <p>The method onFinishInflate() will be called after all children have been added.
*
* @param context The {@link Context} the view is running in, through which it can access the
* current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
*/
public SwipeDismissFrameLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Perform inflation from XML and apply a class-specific base style from a theme attribute.
* This constructor allows subclasses to use their own base style when they are inflating.
*
* @param context The {@link Context} the view is running in, through which it can access the
* current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyle An attribute in the current theme that contains a reference to a style
* resource that supplies default values for the view. Can be 0 to not look for
* defaults.
*/
public SwipeDismissFrameLayout(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, 0);
}
/**
* Perform inflation from XML and apply a class-specific base style from a theme attribute.
* This constructor allows subclasses to use their own base style when they are inflating.
*
* @param context The {@link Context} the view is running in, through which it can access the
* current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyle An attribute in the current theme that contains a reference to a style
* resource that supplies default values for the view. Can be 0 to not look for
* defaults.
* @param defStyleRes It allows a style resource to be specified when creating the view.
*/
public SwipeDismissFrameLayout(Context context, AttributeSet attrs, int defStyle,
int defStyleRes) {
super(context, attrs, defStyle, defStyleRes);
}
/** Adds a callback for dismissal. */
public void addCallback(Callback callback) {
if (callback == null) {
throw new NullPointerException("addCallback called with null callback");
}
mCallbacksCompat.add(callback);
}
/** Removes a callback that was added with {@link #addCallback(Callback)}. */
public void removeCallback(Callback callback) {
if (callback == null) {
throw new NullPointerException("removeCallback called with null callback");
}
if (!mCallbacksCompat.remove(callback)) {
throw new IllegalStateException("removeCallback called with nonexistent callback");
}
}
/**
* Set the layout to be dismissible by swipe or not.
* @param swipeable Whether the layout should react to the swipe gesture.
*/
public void setSwipeable(boolean swipeable) {
super.setSwipeDismissible(swipeable);
}
/** Returns true if the frame layout can be dismissed by swipe gestures. */
public boolean isSwipeable() {
return super.isSwipeDismissible();
}
/**
* Sets the minimum ratio of the screen after which the swipe gesture is treated as
* swipe-to-dismiss.
*
* @param ratio the ratio of the screen at which the swipe gesture is treated as
* swipe-to-dismiss. should be provided as a fraction of the screen
*/
public void setDismissMinDragWidthRatio(float ratio) {
if (isSwipeable()) {
getSwipeDismissController().setDismissMinDragWidthRatio(ratio);
}
}
/**
* Gets the minimum ratio of the screen after which the swipe gesture is treated as
* swipe-to-dismiss.
*/
public float getDismissMinDragWidthRatio() {
if (isSwipeable()) {
return getSwipeDismissController().getDismissMinDragWidthRatio();
}
return DEFAULT_DISMISS_DRAG_WIDTH_RATIO;
}
@Override
protected void executeDismissedCallbacks() {
super.executeDismissedCallbacks();
for (int i = mCallbacksCompat.size() - 1; i >= 0; i--) {
mCallbacksCompat.get(i).onDismissed(this);
}
}
@Override
protected void executeDismissStartedCallbacks() {
super.executeDismissStartedCallbacks();
for (int i = mCallbacksCompat.size() - 1; i >= 0; i--) {
mCallbacksCompat.get(i).onSwipeStarted(this);
}
}
@Override
protected void executeDismissCanceledCallbacks() {
super.executeDismissCanceledCallbacks();
for (int i = mCallbacksCompat.size() - 1; i >= 0; i--) {
mCallbacksCompat.get(i).onSwipeCanceled(this);
}
}
}