diff options
Diffstat (limited to 'core/java')
| -rw-r--r-- | core/java/android/app/Activity.java | 7 | ||||
| -rw-r--r-- | core/java/android/app/Dialog.java | 4 | ||||
| -rw-r--r-- | core/java/android/service/dreams/DreamService.java | 4 | ||||
| -rw-r--r-- | core/java/android/view/Window.java | 12 | ||||
| -rw-r--r-- | core/java/com/android/internal/widget/SwipeDismissLayout.java | 282 |
5 files changed, 308 insertions, 1 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 3297fe0162b5..af4a3620fed8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2462,6 +2462,13 @@ public class Activity extends ContextThemeWrapper } return false; } + + /** + * Called when the main window associated with the activity has been dismissed. + */ + public void onWindowDismissed() { + finish(); + } /** * Called to process key events. You can override this to intercept all diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 255925493755..fb96d8d4d07d 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -707,6 +707,10 @@ public class Dialog implements DialogInterface, Window.Callback, public void onDetachedFromWindow() { } + + public void onWindowDismissed() { + dismiss(); + } /** * Called to process key events. You can override this to intercept all diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 1abb1d72a98b..7647c2258270 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -300,6 +300,10 @@ public class DreamService extends Service implements Window.Callback { public void onDetachedFromWindow() { } + @Override + public void onWindowDismissed() { + } + /** {@inheritDoc} */ @Override public void onPanelClosed(int featureId, Menu menu) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 24b824866c0a..0cd6325231f6 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -98,6 +98,10 @@ public abstract class Window { */ public static final int FEATURE_ACTION_MODE_OVERLAY = 10; /** + * Flag for requesting a decoration-free window that is dismissed by swiping from the left. + */ + public static final int FEATURE_SWIPE_TO_DISMISS = 11; + /** * Flag for requesting that window content changes should be represented * with scenes and transitions. * @@ -105,7 +109,7 @@ public abstract class Window { * * @see #setContentView */ - public static final int FEATURE_CONTENT_TRANSITIONS = 11; + public static final int FEATURE_CONTENT_TRANSITIONS = 12; /** * Max value used as a feature ID @@ -404,6 +408,12 @@ public abstract class Window { * @param mode The mode that was just finished. */ public void onActionModeFinished(ActionMode mode); + + /** + * Called when a window is dismissed. This informs the callback that the + * window is gone, and it should finish itself. + */ + public void onWindowDismissed(); } public Window(Context context) { diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java new file mode 100644 index 000000000000..cc8ce2c9e304 --- /dev/null +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2014 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.internal.widget; + +import android.animation.TimeInterpolator; +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; + +/** + * Special layout that finishes its activity when swiped away. + */ +public class SwipeDismissLayout extends FrameLayout { + private static final String TAG = "SwipeDismissLayout"; + + private static final float TRANSLATION_MIN_ALPHA = 0.5f; + + public interface OnDismissedListener { + void onDismissed(SwipeDismissLayout layout); + } + + public interface OnSwipeProgressChangedListener { + /** + * Called when the layout has been swiped and the position of the window should change. + * + * @param progress A number in [-1, 1] representing how far to the left + * or right the window has been swiped. Negative values are swipes + * left, and positives are right. + * @param translate A number in [-w, w], where w is the width of the + * layout. This is equivalent to progress * layout.getWidth(). + */ + void onSwipeProgressChanged(SwipeDismissLayout layout, float progress, float translate); + + void onSwipeCancelled(SwipeDismissLayout layout); + } + + // Cached ViewConfiguration and system-wide constant values + private int mSlop; + private int mMinFlingVelocity; + private int mMaxFlingVelocity; + private long mAnimationTime; + private TimeInterpolator mCancelInterpolator; + private TimeInterpolator mDismissInterpolator; + + // Transient properties + private int mActiveTouchId; + private float mDownX; + private float mDownY; + private boolean mSwiping; + private boolean mDismissed; + private boolean mDiscardIntercept; + private VelocityTracker mVelocityTracker; + private float mTranslationX; + + private OnDismissedListener mDismissedListener; + private OnSwipeProgressChangedListener mProgressListener; + + public SwipeDismissLayout(Context context) { + super(context); + init(context); + } + + public SwipeDismissLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public SwipeDismissLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + ViewConfiguration vc = ViewConfiguration.get(getContext()); + mSlop = vc.getScaledTouchSlop(); + mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16; + mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); + mAnimationTime = getContext().getResources().getInteger( + android.R.integer.config_shortAnimTime); + mCancelInterpolator = new DecelerateInterpolator(1.5f); + mDismissInterpolator = new AccelerateInterpolator(1.5f); + } + + public void setOnDismissedListener(OnDismissedListener listener) { + mDismissedListener = listener; + } + + public void setOnSwipeProgressChangedListener(OnSwipeProgressChangedListener listener) { + mProgressListener = listener; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + // offset because the view is translated during swipe + ev.offsetLocation(mTranslationX, 0); + + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + resetMembers(); + mDownX = ev.getRawX(); + mDownY = ev.getRawY(); + mActiveTouchId = ev.getPointerId(0); + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(ev); + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + resetMembers(); + break; + + case MotionEvent.ACTION_MOVE: + if (mVelocityTracker == null || mDiscardIntercept) { + break; + } + + int pointerIndex = ev.findPointerIndex(mActiveTouchId); + float dx = ev.getRawX() - mDownX; + float x = ev.getX(pointerIndex); + float y = ev.getY(pointerIndex); + if (dx != 0 && canScroll(this, false, dx, x, y)) { + mDiscardIntercept = true; + break; + } + updateSwiping(ev); + break; + } + + return !mDiscardIntercept && mSwiping; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (mVelocityTracker == null) { + return super.onTouchEvent(ev); + } + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_UP: + updateDismiss(ev); + if (mDismissed) { + dismiss(); + } else if (mSwiping) { + cancel(); + } + resetMembers(); + break; + + case MotionEvent.ACTION_CANCEL: + cancel(); + resetMembers(); + break; + + case MotionEvent.ACTION_MOVE: + mVelocityTracker.addMovement(ev); + updateSwiping(ev); + updateDismiss(ev); + if (mSwiping) { + setProgress(ev.getRawX() - mDownX); + break; + } + } + return true; + } + + private void setProgress(float deltaX) { + mTranslationX = deltaX; + if (mProgressListener != null) { + mProgressListener.onSwipeProgressChanged(this, deltaX / getWidth(), deltaX); + } + } + + private void dismiss() { + if (mDismissedListener != null) { + mDismissedListener.onDismissed(this); + } + } + + protected void cancel() { + if (mProgressListener != null) { + mProgressListener.onSwipeCancelled(this); + } + } + + /** + * Resets internal members when canceling. + */ + private void resetMembers() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + } + mVelocityTracker = null; + mTranslationX = 0; + mDownX = 0; + mDownY = 0; + mSwiping = false; + mDismissed = false; + mDiscardIntercept = false; + } + + private void updateSwiping(MotionEvent ev) { + if (!mSwiping) { + float deltaX = ev.getRawX() - mDownX; + float deltaY = ev.getRawY() - mDownY; + mSwiping = deltaX > mSlop * 2 && Math.abs(deltaY) < mSlop * 2; + } + } + + private void updateDismiss(MotionEvent ev) { + if (!mDismissed) { + mVelocityTracker.addMovement(ev); + mVelocityTracker.computeCurrentVelocity(1000); + + float deltaX = ev.getRawX() - mDownX; + float velocityX = mVelocityTracker.getXVelocity(); + float absVelocityX = Math.abs(velocityX); + float absVelocityY = Math.abs(mVelocityTracker.getYVelocity()); + + if (deltaX > getWidth() / 2) { + mDismissed = true; + } else if (absVelocityX >= mMinFlingVelocity + && absVelocityX <= mMaxFlingVelocity + && absVelocityY < absVelocityX / 2 + && velocityX > 0 + && deltaX > 0) { + mDismissed = true; + } + } + } + + /** + * Tests scrollability within child views of v in the direction of dx. + * + * @param v View to test for horizontal scrollability + * @param checkV Whether the view v passed should itself be checked for scrollability (true), + * or just its children (false). + * @param dx Delta scrolled in pixels. Only the sign of this is used. + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point + * @return true if child views of v can be scrolled by delta of dx. + */ + protected boolean canScroll(View v, boolean checkV, float dx, float x, float y) { + if (v instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) v; + final int scrollX = v.getScrollX(); + final int scrollY = v.getScrollY(); + final int count = group.getChildCount(); + for (int i = count - 1; i >= 0; i--) { + final View child = group.getChildAt(i); + if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && + y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && + canScroll(child, true, dx, x + scrollX - child.getLeft(), + y + scrollY - child.getTop())) { + return true; + } + } + } + + return checkV && v.canScrollHorizontally((int) -dx); + } +} |
