Merge "Make screenshot/clipboard dismiss faster" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
63a29eadbd
@@ -26,7 +26,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
android:src="@drawable/overlay_actions_background_protection"/>
|
android:src="@drawable/overlay_actions_background_protection"/>
|
||||||
<com.android.systemui.clipboardoverlay.DraggableConstraintLayout
|
<com.android.systemui.screenshot.DraggableConstraintLayout
|
||||||
android:id="@+id/clipboard_ui"
|
android:id="@+id/clipboard_ui"
|
||||||
android:theme="@style/FloatingOverlay"
|
android:theme="@style/FloatingOverlay"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -146,5 +146,5 @@
|
|||||||
android:layout_margin="@dimen/overlay_dismiss_button_margin"
|
android:layout_margin="@dimen/overlay_dismiss_button_margin"
|
||||||
android:src="@drawable/overlay_cancel"/>
|
android:src="@drawable/overlay_cancel"/>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
</com.android.systemui.clipboardoverlay.DraggableConstraintLayout>
|
</com.android.systemui.screenshot.DraggableConstraintLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -14,25 +14,25 @@
|
|||||||
~ See the License for the specific language governing permissions and
|
~ See the License for the specific language governing permissions and
|
||||||
~ limitations under the License.
|
~ limitations under the License.
|
||||||
-->
|
-->
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<com.android.systemui.screenshot.DraggableConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/screenshot_actions_container_background"
|
android:id="@+id/actions_container_background"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:elevation="1dp"
|
android:elevation="1dp"
|
||||||
android:background="@drawable/action_chip_container_background"
|
android:background="@drawable/action_chip_container_background"
|
||||||
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
|
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/screenshot_actions_container"
|
app:layout_constraintBottom_toBottomOf="@+id/actions_container"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/screenshot_actions_container"
|
app:layout_constraintTop_toTopOf="@+id/actions_container"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/screenshot_actions_container"/>
|
app:layout_constraintEnd_toEndOf="@+id/actions_container"/>
|
||||||
<HorizontalScrollView
|
<HorizontalScrollView
|
||||||
android:id="@+id/screenshot_actions_container"
|
android:id="@+id/actions_container"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
|
android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
|
||||||
@@ -130,4 +130,4 @@
|
|||||||
app:layout_constraintStart_toStartOf="@id/screenshot_preview"
|
app:layout_constraintStart_toStartOf="@id/screenshot_preview"
|
||||||
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
|
app:layout_constraintTop_toTopOf="@id/screenshot_preview"
|
||||||
android:elevation="@dimen/overlay_preview_elevation"/>
|
android:elevation="@dimen/overlay_preview_elevation"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</com.android.systemui.screenshot.DraggableConstraintLayout>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import com.android.internal.policy.PhoneWindow;
|
import com.android.internal.policy.PhoneWindow;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.screenshot.DraggableConstraintLayout;
|
||||||
import com.android.systemui.screenshot.FloatingWindowUtil;
|
import com.android.systemui.screenshot.FloatingWindowUtil;
|
||||||
import com.android.systemui.screenshot.OverlayActionChip;
|
import com.android.systemui.screenshot.OverlayActionChip;
|
||||||
import com.android.systemui.screenshot.TimeoutHandler;
|
import com.android.systemui.screenshot.TimeoutHandler;
|
||||||
@@ -166,8 +167,28 @@ public class ClipboardOverlayController {
|
|||||||
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
|
mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip));
|
||||||
mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
|
mDismissButton = requireNonNull(mView.findViewById(R.id.dismiss_button));
|
||||||
|
|
||||||
mView.setOnDismissEndCallback(this::hideImmediate);
|
mView.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
|
||||||
mView.setOnInteractionCallback(mTimeoutHandler::resetTimeout);
|
@Override
|
||||||
|
public void onInteraction() {
|
||||||
|
mTimeoutHandler.resetTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwipeDismissInitiated(Animator animator) {
|
||||||
|
animator.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animation) {
|
||||||
|
super.onAnimationStart(animation);
|
||||||
|
mContainer.animate().alpha(0).start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDismissComplete() {
|
||||||
|
hideImmediate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mDismissButton.setOnClickListener(view -> animateOut());
|
mDismissButton.setOnClickListener(view -> animateOut());
|
||||||
|
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 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 com.android.systemui.clipboardoverlay;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.graphics.Region;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewTreeObserver;
|
|
||||||
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
|
|
||||||
import com.android.systemui.R;
|
|
||||||
import com.android.systemui.screenshot.SwipeDismissHandler;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ConstraintLayout that is draggable when touched in a specific region
|
|
||||||
*/
|
|
||||||
public class DraggableConstraintLayout extends ConstraintLayout
|
|
||||||
implements ViewTreeObserver.OnComputeInternalInsetsListener {
|
|
||||||
private final SwipeDismissHandler mSwipeDismissHandler;
|
|
||||||
private final GestureDetector mSwipeDetector;
|
|
||||||
private Consumer<Animator> mOnDismissInitiated;
|
|
||||||
private Runnable mOnDismissComplete;
|
|
||||||
private Runnable mOnInteraction;
|
|
||||||
|
|
||||||
public DraggableConstraintLayout(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DraggableConstraintLayout(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
|
|
||||||
mSwipeDismissHandler = new SwipeDismissHandler(mContext, this,
|
|
||||||
new SwipeDismissHandler.SwipeDismissCallbacks() {
|
|
||||||
@Override
|
|
||||||
public void onInteraction() {
|
|
||||||
if (mOnInteraction != null) {
|
|
||||||
mOnInteraction.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSwipeDismissInitiated(Animator animator) {
|
|
||||||
if (mOnDismissInitiated != null) {
|
|
||||||
mOnDismissInitiated.accept(animator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDismissComplete() {
|
|
||||||
if (mOnDismissComplete != null) {
|
|
||||||
mOnDismissComplete.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setOnTouchListener(mSwipeDismissHandler);
|
|
||||||
|
|
||||||
mSwipeDetector = new GestureDetector(mContext,
|
|
||||||
new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
final Rect mActionsRect = new Rect();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onScroll(
|
|
||||||
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
|
|
||||||
View actionsContainer = findViewById(R.id.actions_container);
|
|
||||||
actionsContainer.getBoundsOnScreen(mActionsRect);
|
|
||||||
// return true if we aren't in the actions bar, or if we are but it isn't
|
|
||||||
// scrollable in the direction of movement
|
|
||||||
return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
|
|
||||||
|| !actionsContainer.canScrollHorizontally((int) distanceX);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mSwipeDetector.setIsLongpressEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
|
||||||
mSwipeDismissHandler.onTouch(this, ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mSwipeDetector.onTouchEvent(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismiss the view, with animation controlled by SwipeDismissHandler
|
|
||||||
*/
|
|
||||||
public void dismiss() {
|
|
||||||
mSwipeDismissHandler.dismiss();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the callback to be run after view is dismissed (before animation; receives animator as
|
|
||||||
* input)
|
|
||||||
*/
|
|
||||||
public void setOnDismissStartCallback(Consumer<Animator> callback) {
|
|
||||||
mOnDismissInitiated = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the callback to be run after view is dismissed
|
|
||||||
*/
|
|
||||||
public void setOnDismissEndCallback(Runnable callback) {
|
|
||||||
mOnDismissComplete = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the callback to be run when the view is interacted with (e.g. tapped)
|
|
||||||
*/
|
|
||||||
public void setOnInteractionCallback(Runnable callback) {
|
|
||||||
mOnInteraction = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onAttachedToWindow() {
|
|
||||||
super.onAttachedToWindow();
|
|
||||||
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDetachedFromWindow() {
|
|
||||||
super.onDetachedFromWindow();
|
|
||||||
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
|
||||||
// Only child views are touchable.
|
|
||||||
Region r = new Region();
|
|
||||||
Rect rect = new Rect();
|
|
||||||
for (int i = 0; i < getChildCount(); i++) {
|
|
||||||
getChildAt(i).getGlobalVisibleRect(rect);
|
|
||||||
r.op(rect, Region.Op.UNION);
|
|
||||||
}
|
|
||||||
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
|
||||||
inoutInfo.touchableRegion.set(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 com.android.systemui.screenshot;
|
||||||
|
|
||||||
|
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
|
||||||
|
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
|
||||||
|
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.Region;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.MathUtils;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
|
||||||
|
import com.android.systemui.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConstraintLayout that is draggable when touched in a specific region
|
||||||
|
*/
|
||||||
|
public class DraggableConstraintLayout extends ConstraintLayout
|
||||||
|
implements ViewTreeObserver.OnComputeInternalInsetsListener {
|
||||||
|
|
||||||
|
private final SwipeDismissHandler mSwipeDismissHandler;
|
||||||
|
private final GestureDetector mSwipeDetector;
|
||||||
|
private View mActionsContainer;
|
||||||
|
private SwipeDismissCallbacks mCallbacks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the callbacks when the view is interacted with or dismissed.
|
||||||
|
*/
|
||||||
|
public interface SwipeDismissCallbacks {
|
||||||
|
/**
|
||||||
|
* Run when the view is interacted with (touched)
|
||||||
|
*/
|
||||||
|
default void onInteraction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run when the view is dismissed (the distance threshold is met), pre-dismissal animation
|
||||||
|
*/
|
||||||
|
default void onSwipeDismissInitiated(Animator animator) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run when the view is dismissed (the distance threshold is met), post-dismissal animation
|
||||||
|
*/
|
||||||
|
default void onDismissComplete() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DraggableConstraintLayout(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DraggableConstraintLayout(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DraggableConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
mSwipeDismissHandler = new SwipeDismissHandler(mContext, this);
|
||||||
|
setOnTouchListener(mSwipeDismissHandler);
|
||||||
|
|
||||||
|
mSwipeDetector = new GestureDetector(mContext,
|
||||||
|
new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
final Rect mActionsRect = new Rect();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(
|
||||||
|
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
|
||||||
|
mActionsContainer.getBoundsOnScreen(mActionsRect);
|
||||||
|
// return true if we aren't in the actions bar, or if we are but it isn't
|
||||||
|
// scrollable in the direction of movement
|
||||||
|
return !mActionsRect.contains((int) ev2.getRawX(), (int) ev2.getRawY())
|
||||||
|
|| !mActionsContainer.canScrollHorizontally((int) distanceX);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mSwipeDetector.setIsLongpressEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallbacks(SwipeDismissCallbacks callbacks) {
|
||||||
|
mCallbacks = callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // View
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
mActionsContainer = findViewById(R.id.actions_container_background);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||||
|
mSwipeDismissHandler.onTouch(this, ev);
|
||||||
|
}
|
||||||
|
return mSwipeDetector.onTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVisibleRight() {
|
||||||
|
return mActionsContainer.getRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel current dismissal animation, if any
|
||||||
|
*/
|
||||||
|
public void cancelDismissal() {
|
||||||
|
mSwipeDismissHandler.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the view is currently dismissing
|
||||||
|
*/
|
||||||
|
public boolean isDismissing() {
|
||||||
|
return mSwipeDismissHandler.isDismissing();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dismiss the view, with animation controlled by SwipeDismissHandler
|
||||||
|
*/
|
||||||
|
public void dismiss() {
|
||||||
|
mSwipeDismissHandler.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow();
|
||||||
|
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
||||||
|
// Only child views are touchable.
|
||||||
|
Region r = new Region();
|
||||||
|
Rect rect = new Rect();
|
||||||
|
for (int i = 0; i < getChildCount(); i++) {
|
||||||
|
getChildAt(i).getGlobalVisibleRect(rect);
|
||||||
|
r.op(rect, Region.Op.UNION);
|
||||||
|
}
|
||||||
|
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
||||||
|
inoutInfo.touchableRegion.set(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not
|
||||||
|
* met
|
||||||
|
*/
|
||||||
|
private class SwipeDismissHandler implements OnTouchListener {
|
||||||
|
private static final String TAG = "SwipeDismissHandler";
|
||||||
|
|
||||||
|
// distance needed to register a dismissal
|
||||||
|
private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20;
|
||||||
|
|
||||||
|
private final DraggableConstraintLayout mView;
|
||||||
|
private final GestureDetector mGestureDetector;
|
||||||
|
private final DisplayMetrics mDisplayMetrics;
|
||||||
|
private ValueAnimator mDismissAnimation;
|
||||||
|
|
||||||
|
private float mStartX;
|
||||||
|
// Keeps track of the most recent direction (between the last two move events).
|
||||||
|
// -1 for left; +1 for right.
|
||||||
|
private int mDirectionX;
|
||||||
|
private float mPreviousX;
|
||||||
|
|
||||||
|
SwipeDismissHandler(Context context, DraggableConstraintLayout view) {
|
||||||
|
mView = view;
|
||||||
|
GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
|
||||||
|
mGestureDetector = new GestureDetector(context, gestureListener);
|
||||||
|
mDisplayMetrics = new DisplayMetrics();
|
||||||
|
context.getDisplay().getRealMetrics(mDisplayMetrics);
|
||||||
|
mCallbacks = new SwipeDismissCallbacks() {
|
||||||
|
}; // default to unimplemented callbacks
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View view, MotionEvent event) {
|
||||||
|
boolean gestureResult = mGestureDetector.onTouchEvent(event);
|
||||||
|
mCallbacks.onInteraction();
|
||||||
|
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
||||||
|
mStartX = event.getRawX();
|
||||||
|
mPreviousX = mStartX;
|
||||||
|
return true;
|
||||||
|
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
||||||
|
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isPastDismissThreshold()) {
|
||||||
|
dismiss();
|
||||||
|
} else {
|
||||||
|
// if we've moved, but not past the threshold, start the return animation
|
||||||
|
if (DEBUG_DISMISS) {
|
||||||
|
Log.d(TAG, "swipe gesture abandoned");
|
||||||
|
}
|
||||||
|
createSwipeReturnAnimation().start();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return gestureResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||||
|
@Override
|
||||||
|
public boolean onScroll(
|
||||||
|
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
|
||||||
|
mView.setTranslationX(ev2.getRawX() - mStartX);
|
||||||
|
mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1;
|
||||||
|
mPreviousX = ev2.getRawX();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
||||||
|
float velocityY) {
|
||||||
|
if (mView.getTranslationX() * velocityX > 0
|
||||||
|
&& (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
|
||||||
|
ValueAnimator dismissAnimator =
|
||||||
|
createSwipeDismissAnimation(velocityX / (float) 1000);
|
||||||
|
mCallbacks.onSwipeDismissInitiated(dismissAnimator);
|
||||||
|
dismiss(dismissAnimator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPastDismissThreshold() {
|
||||||
|
float translationX = mView.getTranslationX();
|
||||||
|
// Determines whether the absolute translation from the start is in the same direction
|
||||||
|
// as the current movement. For example, if the user moves most of the way to the right,
|
||||||
|
// but then starts dragging back left, we do not dismiss even though the absolute
|
||||||
|
// distance is greater than the threshold.
|
||||||
|
if (translationX * mDirectionX > 0) {
|
||||||
|
return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics,
|
||||||
|
DISMISS_DISTANCE_THRESHOLD_DP);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isDismissing() {
|
||||||
|
return (mDismissAnimation != null && mDismissAnimation.isRunning());
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
if (isDismissing()) {
|
||||||
|
if (DEBUG_ANIM) {
|
||||||
|
Log.d(TAG, "cancelling dismiss animation");
|
||||||
|
}
|
||||||
|
mDismissAnimation.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dismiss() {
|
||||||
|
ValueAnimator anim = createSwipeDismissAnimation(3);
|
||||||
|
mCallbacks.onSwipeDismissInitiated(anim);
|
||||||
|
dismiss(anim);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dismiss(ValueAnimator animator) {
|
||||||
|
mDismissAnimation = animator;
|
||||||
|
mDismissAnimation.addListener(new AnimatorListenerAdapter() {
|
||||||
|
private boolean mCancelled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
super.onAnimationCancel(animation);
|
||||||
|
mCancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
super.onAnimationEnd(animation);
|
||||||
|
if (!mCancelled) {
|
||||||
|
mCallbacks.onDismissComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mDismissAnimation.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueAnimator createSwipeDismissAnimation(float velocity) {
|
||||||
|
// velocity is measured in pixels per millisecond
|
||||||
|
velocity = Math.min(3, Math.max(1, velocity));
|
||||||
|
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||||
|
float startX = mView.getTranslationX();
|
||||||
|
// make sure the UI gets all the way off the screen in the direction of movement
|
||||||
|
// (the actions container background is guaranteed to be both the leftmost and
|
||||||
|
// rightmost UI element in LTR and RTL)
|
||||||
|
float finalX;
|
||||||
|
int layoutDir =
|
||||||
|
mView.getContext().getResources().getConfiguration().getLayoutDirection();
|
||||||
|
if (startX > 0 || (startX == 0 && layoutDir == LAYOUT_DIRECTION_RTL)) {
|
||||||
|
finalX = mDisplayMetrics.widthPixels;
|
||||||
|
} else {
|
||||||
|
finalX = -1 * mActionsContainer.getRight();
|
||||||
|
}
|
||||||
|
float distance = Math.abs(finalX - startX);
|
||||||
|
|
||||||
|
anim.addUpdateListener(animation -> {
|
||||||
|
float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
|
||||||
|
mView.setTranslationX(translation);
|
||||||
|
mView.setAlpha(1 - animation.getAnimatedFraction());
|
||||||
|
});
|
||||||
|
anim.setDuration((long) (distance / Math.abs(velocity)));
|
||||||
|
return anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueAnimator createSwipeReturnAnimation() {
|
||||||
|
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||||
|
float startX = mView.getTranslationX();
|
||||||
|
float finalX = 0;
|
||||||
|
|
||||||
|
anim.addUpdateListener(animation -> {
|
||||||
|
float translation = MathUtils.lerp(
|
||||||
|
startX, finalX, animation.getAnimatedFraction());
|
||||||
|
mView.setTranslationX(translation);
|
||||||
|
});
|
||||||
|
|
||||||
|
return anim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,7 +141,7 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
|
|
||||||
private ScreenshotSelectorView mScreenshotSelectorView;
|
private ScreenshotSelectorView mScreenshotSelectorView;
|
||||||
private ImageView mScrollingScrim;
|
private ImageView mScrollingScrim;
|
||||||
private View mScreenshotStatic;
|
private DraggableConstraintLayout mScreenshotStatic;
|
||||||
private ImageView mScreenshotPreview;
|
private ImageView mScreenshotPreview;
|
||||||
private View mScreenshotPreviewBorder;
|
private View mScreenshotPreviewBorder;
|
||||||
private ImageView mScrollablePreview;
|
private ImageView mScrollablePreview;
|
||||||
@@ -159,7 +159,6 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
private UiEventLogger mUiEventLogger;
|
private UiEventLogger mUiEventLogger;
|
||||||
private ScreenshotViewCallback mCallbacks;
|
private ScreenshotViewCallback mCallbacks;
|
||||||
private boolean mPendingSharedTransition;
|
private boolean mPendingSharedTransition;
|
||||||
private SwipeDismissHandler mSwipeDismissHandler;
|
|
||||||
private InputMonitorCompat mInputMonitor;
|
private InputMonitorCompat mInputMonitor;
|
||||||
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
|
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
|
||||||
private boolean mShowScrollablePreview;
|
private boolean mShowScrollablePreview;
|
||||||
@@ -332,19 +331,6 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override // ViewGroup
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
// scrolling scrim should not be swipeable; return early if we're on the scrim
|
|
||||||
if (!getSwipeRegion().contains((int) ev.getRawX(), (int) ev.getRawY())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// always pass through the down event so the swipe handler knows the initial state
|
|
||||||
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
|
||||||
mSwipeDismissHandler.onTouch(this, ev);
|
|
||||||
}
|
|
||||||
return mSwipeDetector.onTouchEvent(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // View
|
@Override // View
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
|
mScrollingScrim = requireNonNull(findViewById(R.id.screenshot_scrolling_scrim));
|
||||||
@@ -356,8 +342,8 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
mScreenshotPreview.setClipToOutline(true);
|
mScreenshotPreview.setClipToOutline(true);
|
||||||
|
|
||||||
mActionsContainerBackground = requireNonNull(findViewById(
|
mActionsContainerBackground = requireNonNull(findViewById(
|
||||||
R.id.screenshot_actions_container_background));
|
R.id.actions_container_background));
|
||||||
mActionsContainer = requireNonNull(findViewById(R.id.screenshot_actions_container));
|
mActionsContainer = requireNonNull(findViewById(R.id.actions_container));
|
||||||
mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
|
mActionsView = requireNonNull(findViewById(R.id.screenshot_actions));
|
||||||
mBackgroundProtection = requireNonNull(
|
mBackgroundProtection = requireNonNull(
|
||||||
findViewById(R.id.screenshot_actions_background));
|
findViewById(R.id.screenshot_actions_background));
|
||||||
@@ -395,26 +381,25 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
setFocusableInTouchMode(true);
|
setFocusableInTouchMode(true);
|
||||||
requestFocus();
|
requestFocus();
|
||||||
|
|
||||||
mSwipeDismissHandler = new SwipeDismissHandler(mContext, mScreenshotStatic,
|
mScreenshotStatic.setCallbacks(new DraggableConstraintLayout.SwipeDismissCallbacks() {
|
||||||
new SwipeDismissHandler.SwipeDismissCallbacks() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onInteraction() {
|
public void onInteraction() {
|
||||||
mCallbacks.onUserInteraction();
|
mCallbacks.onUserInteraction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwipeDismissInitiated(Animator anim) {
|
public void onSwipeDismissInitiated(Animator animator) {
|
||||||
if (DEBUG_DISMISS) {
|
if (DEBUG_DISMISS) {
|
||||||
Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
|
Log.d(ScreenshotView.TAG, "dismiss triggered via swipe gesture");
|
||||||
}
|
}
|
||||||
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
|
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, 0,
|
||||||
mPackageName);
|
mPackageName);
|
||||||
anim.addListener(new AnimatorListenerAdapter() {
|
animator.addListener(new AnimatorListenerAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onAnimationStart(Animator animation) {
|
public void onAnimationStart(Animator animation) {
|
||||||
super.onAnimationStart(animation);
|
super.onAnimationStart(animation);
|
||||||
mBackgroundProtection.animate()
|
mBackgroundProtection.animate()
|
||||||
.alpha(0).setDuration(anim.getDuration()).start();
|
.alpha(0).setDuration(animation.getDuration()).start();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -648,8 +633,6 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
requestLayout();
|
requestLayout();
|
||||||
|
|
||||||
createScreenshotActionsShadeAnimation().start();
|
createScreenshotActionsShadeAnimation().start();
|
||||||
|
|
||||||
setOnTouchListener(mSwipeDismissHandler);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -958,7 +941,7 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
boolean isDismissing() {
|
boolean isDismissing() {
|
||||||
return mSwipeDismissHandler.isDismissing();
|
return mScreenshotStatic.isDismissing();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isPendingSharedTransition() {
|
boolean isPendingSharedTransition() {
|
||||||
@@ -966,15 +949,14 @@ public class ScreenshotView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
void animateDismissal() {
|
void animateDismissal() {
|
||||||
mSwipeDismissHandler.dismiss();
|
mScreenshotStatic.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
if (DEBUG_UI) {
|
if (DEBUG_UI) {
|
||||||
Log.d(TAG, "reset screenshot view");
|
Log.d(TAG, "reset screenshot view");
|
||||||
}
|
}
|
||||||
|
mScreenshotStatic.cancelDismissal();
|
||||||
mSwipeDismissHandler.cancel();
|
|
||||||
if (DEBUG_WINDOW) {
|
if (DEBUG_WINDOW) {
|
||||||
Log.d(TAG, "removing OnComputeInternalInsetsListener");
|
Log.d(TAG, "removing OnComputeInternalInsetsListener");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,237 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 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 com.android.systemui.screenshot;
|
|
||||||
|
|
||||||
import static com.android.systemui.screenshot.LogConfig.DEBUG_ANIM;
|
|
||||||
import static com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS;
|
|
||||||
|
|
||||||
import android.animation.Animator;
|
|
||||||
import android.animation.AnimatorListenerAdapter;
|
|
||||||
import android.animation.ValueAnimator;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.DisplayMetrics;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.MathUtils;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows a view to be swipe-dismissed, or returned to its location if distance threshold is not met
|
|
||||||
*/
|
|
||||||
public class SwipeDismissHandler implements View.OnTouchListener {
|
|
||||||
private static final String TAG = "SwipeDismissHandler";
|
|
||||||
|
|
||||||
// distance needed to register a dismissal
|
|
||||||
private static final float DISMISS_DISTANCE_THRESHOLD_DP = 20;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores the callbacks when the view is interacted with or dismissed.
|
|
||||||
*/
|
|
||||||
public interface SwipeDismissCallbacks {
|
|
||||||
/**
|
|
||||||
* Run when the view is interacted with (touched)
|
|
||||||
*/
|
|
||||||
void onInteraction();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run when the view is dismissed (the distance threshold is met), pre-dismissal animation
|
|
||||||
*/
|
|
||||||
void onSwipeDismissInitiated(Animator animator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Run when the view is dismissed (the distance threshold is met), post-dismissal animation
|
|
||||||
*/
|
|
||||||
void onDismissComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final View mView;
|
|
||||||
private final SwipeDismissCallbacks mCallbacks;
|
|
||||||
private final GestureDetector mGestureDetector;
|
|
||||||
private DisplayMetrics mDisplayMetrics;
|
|
||||||
private ValueAnimator mDismissAnimation;
|
|
||||||
|
|
||||||
|
|
||||||
private float mStartX;
|
|
||||||
// Keeps track of the most recent direction (between the last two move events).
|
|
||||||
// -1 for left; +1 for right.
|
|
||||||
private int mDirectionX;
|
|
||||||
private float mPreviousX;
|
|
||||||
|
|
||||||
public SwipeDismissHandler(Context context, View view, SwipeDismissCallbacks callbacks) {
|
|
||||||
mView = view;
|
|
||||||
mCallbacks = callbacks;
|
|
||||||
GestureDetector.OnGestureListener gestureListener = new SwipeDismissGestureListener();
|
|
||||||
mGestureDetector = new GestureDetector(context, gestureListener);
|
|
||||||
mDisplayMetrics = new DisplayMetrics();
|
|
||||||
context.getDisplay().getRealMetrics(mDisplayMetrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View view, MotionEvent event) {
|
|
||||||
boolean gestureResult = mGestureDetector.onTouchEvent(event);
|
|
||||||
mCallbacks.onInteraction();
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
|
|
||||||
mStartX = event.getRawX();
|
|
||||||
mPreviousX = mStartX;
|
|
||||||
return true;
|
|
||||||
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
|
||||||
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (isPastDismissThreshold()) {
|
|
||||||
ValueAnimator dismissAnimator = createSwipeDismissAnimation(1);
|
|
||||||
mCallbacks.onSwipeDismissInitiated(dismissAnimator);
|
|
||||||
dismiss(dismissAnimator);
|
|
||||||
} else {
|
|
||||||
// if we've moved, but not past the threshold, start the return animation
|
|
||||||
if (DEBUG_DISMISS) {
|
|
||||||
Log.d(TAG, "swipe gesture abandoned");
|
|
||||||
}
|
|
||||||
createSwipeReturnAnimation().start();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return gestureResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SwipeDismissGestureListener extends GestureDetector.SimpleOnGestureListener {
|
|
||||||
@Override
|
|
||||||
public boolean onScroll(
|
|
||||||
MotionEvent ev1, MotionEvent ev2, float distanceX, float distanceY) {
|
|
||||||
mView.setTranslationX(ev2.getRawX() - mStartX);
|
|
||||||
mDirectionX = (ev2.getRawX() < mPreviousX) ? -1 : 1;
|
|
||||||
mPreviousX = ev2.getRawX();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
|
|
||||||
float velocityY) {
|
|
||||||
if (mView.getTranslationX() * velocityX > 0
|
|
||||||
&& (mDismissAnimation == null || !mDismissAnimation.isRunning())) {
|
|
||||||
ValueAnimator dismissAnimator =
|
|
||||||
createSwipeDismissAnimation(velocityX / (float) 1000);
|
|
||||||
mCallbacks.onSwipeDismissInitiated(dismissAnimator);
|
|
||||||
dismiss(dismissAnimator);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isPastDismissThreshold() {
|
|
||||||
float translationX = mView.getTranslationX();
|
|
||||||
// Determines whether the absolute translation from the start is in the same direction
|
|
||||||
// as the current movement. For example, if the user moves most of the way to the right,
|
|
||||||
// but then starts dragging back left, we do not dismiss even though the absolute
|
|
||||||
// distance is greater than the threshold.
|
|
||||||
if (translationX * mDirectionX > 0) {
|
|
||||||
return Math.abs(translationX) >= FloatingWindowUtil.dpToPx(mDisplayMetrics,
|
|
||||||
DISMISS_DISTANCE_THRESHOLD_DP);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return whether the view is currently being dismissed
|
|
||||||
*/
|
|
||||||
public boolean isDismissing() {
|
|
||||||
return (mDismissAnimation != null && mDismissAnimation.isRunning());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel the currently-running dismissal animation, if any.
|
|
||||||
*/
|
|
||||||
public void cancel() {
|
|
||||||
if (isDismissing()) {
|
|
||||||
if (DEBUG_ANIM) {
|
|
||||||
Log.d(TAG, "cancelling dismiss animation");
|
|
||||||
}
|
|
||||||
mDismissAnimation.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start dismissal animation (will run onDismiss callback when animation complete)
|
|
||||||
*/
|
|
||||||
public void dismiss() {
|
|
||||||
dismiss(createSwipeDismissAnimation(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void dismiss(ValueAnimator animator) {
|
|
||||||
mDismissAnimation = animator;
|
|
||||||
mDismissAnimation.addListener(new AnimatorListenerAdapter() {
|
|
||||||
private boolean mCancelled;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationCancel(Animator animation) {
|
|
||||||
super.onAnimationCancel(animation);
|
|
||||||
mCancelled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAnimationEnd(Animator animation) {
|
|
||||||
super.onAnimationEnd(animation);
|
|
||||||
if (!mCancelled) {
|
|
||||||
mCallbacks.onDismissComplete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mDismissAnimation.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueAnimator createSwipeDismissAnimation(float velocity) {
|
|
||||||
// velocity is measured in pixels per millisecond
|
|
||||||
velocity = Math.min(3, Math.max(1, velocity));
|
|
||||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
|
||||||
float startX = mView.getTranslationX();
|
|
||||||
// make sure the UI gets all the way off the screen in the direction of movement
|
|
||||||
// (the actions container background is guaranteed to be both the leftmost and
|
|
||||||
// rightmost UI element in LTR and RTL)
|
|
||||||
float finalX;
|
|
||||||
int layoutDir = mView.getContext().getResources().getConfiguration().getLayoutDirection();
|
|
||||||
if (startX > 0 || (startX == 0 && layoutDir == View.LAYOUT_DIRECTION_RTL)) {
|
|
||||||
finalX = mDisplayMetrics.widthPixels;
|
|
||||||
} else {
|
|
||||||
finalX = -1 * mView.getRight();
|
|
||||||
}
|
|
||||||
float distance = Math.abs(finalX - startX);
|
|
||||||
|
|
||||||
anim.addUpdateListener(animation -> {
|
|
||||||
float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
|
|
||||||
mView.setTranslationX(translation);
|
|
||||||
mView.setAlpha(1 - animation.getAnimatedFraction());
|
|
||||||
});
|
|
||||||
anim.setDuration((long) (distance / Math.abs(velocity)));
|
|
||||||
return anim;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValueAnimator createSwipeReturnAnimation() {
|
|
||||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
|
||||||
float startX = mView.getTranslationX();
|
|
||||||
float finalX = 0;
|
|
||||||
|
|
||||||
anim.addUpdateListener(animation -> {
|
|
||||||
float translation = MathUtils.lerp(
|
|
||||||
startX, finalX, animation.getAnimatedFraction());
|
|
||||||
mView.setTranslationX(translation);
|
|
||||||
});
|
|
||||||
|
|
||||||
return anim;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user