summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/SwipeHelper.java')
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java317
1 files changed, 317 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
new file mode 100644
index 0000000..eaffd1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2011 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;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.graphics.RectF;
+import android.util.Log;
+import android.view.animation.LinearInterpolator;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+
+public class SwipeHelper {
+ static final String TAG = "com.android.systemui.SwipeHelper";
+ private static final boolean DEBUG = true;
+ private static final boolean DEBUG_INVALIDATE = false;
+ private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
+
+ public static final int X = 0;
+ public static final int Y = 1;
+
+ private boolean CONSTRAIN_SWIPE = true;
+ private boolean FADE_OUT_DURING_SWIPE = true;
+ private boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
+
+ private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
+ private int MAX_ESCAPE_ANIMATION_DURATION = 500; // ms
+ private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; // ms
+
+ public static float ALPHA_FADE_START = 0.8f; // fraction of thumbnail width
+ // where fade starts
+ static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width
+ // beyond which alpha->0
+
+ private float mPagingTouchSlop;
+ private Callback mCallback;
+ private int mSwipeDirection;
+ private VelocityTracker mVelocityTracker;
+
+ private float mInitialTouchPos;
+ private boolean mDragging;
+ private View mCurrView;
+ private float mDensityScale;
+
+ public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
+ float pagingTouchSlop) {
+ mCallback = callback;
+ mSwipeDirection = swipeDirection;
+ mVelocityTracker = VelocityTracker.obtain();
+ mDensityScale = densityScale;
+ mPagingTouchSlop = pagingTouchSlop;
+ }
+
+ public void setDensityScale(float densityScale) {
+ mDensityScale = densityScale;
+ }
+
+ public void setPagingTouchSlop(float pagingTouchSlop) {
+ mPagingTouchSlop = pagingTouchSlop;
+ }
+
+ private float getPos(MotionEvent ev) {
+ return mSwipeDirection == X ? ev.getX() : ev.getY();
+ }
+
+ private float getPos(View v) {
+ return mSwipeDirection == X ? v.getX() : v.getY();
+ }
+
+ private float getVelocity(VelocityTracker vt) {
+ return mSwipeDirection == X ? vt.getXVelocity() :
+ vt.getYVelocity();
+ }
+
+ private ObjectAnimator createTranslationAnimation(View v, float newPos) {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v,
+ mSwipeDirection == X ? "translationX" : "translationY", newPos);
+ return anim;
+ }
+
+ private float getPerpendicularVelocity(VelocityTracker vt) {
+ return mSwipeDirection == X ? vt.getYVelocity() :
+ vt.getXVelocity();
+ }
+
+ private void setTranslation(View v, float translate) {
+ if (mSwipeDirection == X) {
+ v.setTranslationX(translate);
+ } else {
+ v.setTranslationY(translate);
+ }
+ }
+
+ private float getSize(View v) {
+ return mSwipeDirection == X ? v.getMeasuredWidth() :
+ v.getMeasuredHeight();
+ }
+
+ private float getContentSize(View v) {
+ View content = mCallback.getChildContentView(v);
+ return getSize(content);
+ }
+
+ private float getAlphaForOffset(View view, float thumbSize) {
+ final float fadeSize = ALPHA_FADE_END * thumbSize;
+ float result = 1.0f;
+ float pos = getPos(view);
+ if (pos >= thumbSize * ALPHA_FADE_START) {
+ result = 1.0f - (pos - thumbSize * ALPHA_FADE_START) / fadeSize;
+ } else if (pos < thumbSize * (1.0f - ALPHA_FADE_START)) {
+ result = 1.0f + (thumbSize * ALPHA_FADE_START + pos) / fadeSize;
+ }
+ return result;
+ }
+
+ void invalidateGlobalRegion(View view) {
+ RectF childBounds = new RectF(view.getLeft(), view.getTop(), view.getRight(), view
+ .getBottom());
+ childBounds.offset(view.getX(), view.getY());
+ if (DEBUG_INVALIDATE)
+ Log.v(TAG, "-------------");
+ while (view.getParent() != null && view.getParent() instanceof View) {
+ view = (View) view.getParent();
+ view.getMatrix().mapRect(childBounds);
+ view.invalidate((int) Math.floor(childBounds.left),
+ (int) Math.floor(childBounds.top),
+ (int) Math.ceil(childBounds.right),
+ (int) Math.ceil(childBounds.bottom));
+ if (DEBUG_INVALIDATE) {
+ Log.v(TAG, "INVALIDATE(" + (int) Math.floor(childBounds.left)
+ + "," + (int) Math.floor(childBounds.top)
+ + "," + (int) Math.ceil(childBounds.right)
+ + "," + (int) Math.ceil(childBounds.bottom));
+ }
+ }
+ }
+
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ final int action = ev.getAction();
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mDragging = false;
+ mCurrView = mCallback.getChildAtPosition(ev);
+ mVelocityTracker.clear();
+ mVelocityTracker.addMovement(ev);
+ mInitialTouchPos = getPos(ev);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mCurrView != null) {
+ mVelocityTracker.addMovement(ev);
+ float pos = getPos(ev);
+ float delta = pos - mInitialTouchPos;
+ if (Math.abs(delta) > mPagingTouchSlop) {
+ mCallback.onBeginDrag(mCurrView);
+ mDragging = true;
+ mInitialTouchPos = getPos(ev) - getPos(mCurrView);
+ }
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ mDragging = false;
+ mCurrView = null;
+ break;
+ }
+ return mDragging;
+ }
+
+ public void dismissChild(final View animView, float velocity) {
+ float newPos;
+ if (velocity < 0 || (velocity == 0 && getPos(animView) < 0)) {
+ newPos = -getSize(animView);
+ } else {
+ newPos = getSize(animView);
+ }
+ int duration = MAX_ESCAPE_ANIMATION_DURATION;
+ if (velocity != 0) {
+ duration = Math.min(duration,
+ (int) (Math.abs(newPos - getPos(animView)) * 1000f / Math
+ .abs(velocity)));
+ }
+ ObjectAnimator anim = createTranslationAnimation(animView, newPos);
+ anim.setInterpolator(new LinearInterpolator());
+ anim.setDuration(duration);
+ anim.addListener(new AnimatorListener() {
+ public void onAnimationStart(Animator animation) {
+ }
+
+ public void onAnimationRepeat(Animator animation) {
+ }
+
+ public void onAnimationEnd(Animator animation) {
+ mCallback.onChildDismissed(animView);
+ }
+
+ public void onAnimationCancel(Animator animation) {
+ mCallback.onChildDismissed(animView);
+ }
+ });
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (FADE_OUT_DURING_SWIPE) {
+ animView.setAlpha(getAlphaForOffset(animView, getContentSize(animView)));
+ }
+ invalidateGlobalRegion(animView);
+ }
+ });
+ anim.start();
+ }
+
+ public void snapChild(final View animView, float velocity) {
+ ObjectAnimator anim = createTranslationAnimation(animView, 0);
+ int duration = SNAP_ANIM_LEN;
+ anim.setDuration(duration);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (FADE_OUT_DURING_SWIPE) {
+ animView.setAlpha(getAlphaForOffset(animView, getContentSize(animView)));
+ }
+ invalidateGlobalRegion(animView);
+ }
+ });
+ anim.start();
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (!mDragging) {
+ return false;
+ }
+
+ mVelocityTracker.addMovement(ev);
+ final int action = ev.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_OUTSIDE:
+ case MotionEvent.ACTION_MOVE:
+ if (mCurrView != null) {
+ float delta = getPos(ev) - mInitialTouchPos;
+ // don't let items that can't be dismissed be dragged more than
+ // maxScrollDistance
+ if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
+ float size = getSize(mCurrView);
+ float maxScrollDistance = 0.15f * size;
+ if (Math.abs(delta) >= size) {
+ delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
+ } else {
+ delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
+ }
+ }
+ setTranslation(mCurrView, delta);
+ if (FADE_OUT_DURING_SWIPE) {
+ mCurrView.setAlpha(getAlphaForOffset(mCurrView, getContentSize(mCurrView)));
+ }
+ invalidateGlobalRegion(mCurrView);
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (mCurrView != null) {
+ float maxVelocity = 1000; // px/sec
+ mVelocityTracker.computeCurrentVelocity(1000 /* px/sec */, maxVelocity);
+ float escapeVelocity = SWIPE_ESCAPE_VELOCITY * mDensityScale;
+ float velocity = getVelocity(mVelocityTracker);
+ float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
+
+ // Decide whether to dismiss the current view
+ boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
+ Math.abs(getPos(mCurrView)) > 0.4 * getSize(mCurrView);
+ boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
+ (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
+ (velocity > 0) == (getPos(mCurrView) > 0);
+
+ boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) &&
+ (childSwipedFastEnough || childSwipedFarEnough);
+
+ if (dismissChild) {
+ // flingadingy
+ dismissChild(mCurrView, childSwipedFastEnough ? velocity : 0f);
+ } else {
+ // snappity
+ snapChild(mCurrView, velocity);
+ }
+ }
+ break;
+ }
+ return true;
+ }
+
+ public interface Callback {
+ View getChildAtPosition(MotionEvent ev);
+
+ View getChildContentView(View v);
+
+ boolean canChildBeDismissed(View v);
+
+ void onBeginDrag(View v);
+
+ void onChildDismissed(View v);
+ }
+}