From 07d4046ea914c999b7aaa587b7bfe81d548121f7 Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Tue, 19 Jul 2011 10:54:38 -0700 Subject: Unifying swipe behavior for Recents and Notifications Change-Id: Ibdbb7cc4ac2bcfcf6d6353a218b13c23caeca5b9 --- .../src/com/android/systemui/SwipeHelper.java | 317 +++++++++++++++++++++ .../android/systemui/recent/RecentsCallback.java | 2 +- .../recent/RecentsHorizontalScrollView.java | 229 ++++----------- .../android/systemui/recent/RecentsPanelView.java | 2 +- .../systemui/recent/RecentsVerticalScrollView.java | 229 ++++----------- .../statusbar/policy/NotificationRowLayout.java | 182 ++++-------- 6 files changed, 478 insertions(+), 483 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/SwipeHelper.java 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); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java index 797f94c..5609ead 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsCallback.java @@ -25,6 +25,6 @@ public interface RecentsCallback { static final int SWIPE_DOWN = 3; void handleOnClick(View selectedView); - void handleSwipe(View selectedView, int direction); + void handleSwipe(View selectedView); void handleLongPress(View selectedView, View anchorView); } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java index 2a5d1dd..14efdd0 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java @@ -16,52 +16,35 @@ package com.android.systemui.recent; -import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Configuration; import android.database.DataSetObserver; -import android.graphics.RectF; 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.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; import android.widget.HorizontalScrollView; import android.widget.LinearLayout; import com.android.systemui.R; +import com.android.systemui.SwipeHelper; +import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; -public class RecentsHorizontalScrollView extends HorizontalScrollView { +public class RecentsHorizontalScrollView extends HorizontalScrollView + implements SwipeHelper.Callback { private static final String TAG = RecentsPanelView.TAG; - private static final boolean DEBUG_INVALIDATE = false; private static final boolean DEBUG = RecentsPanelView.DEBUG; private LinearLayout mLinearLayout; private ActivityDescriptionAdapter mAdapter; private RecentsCallback mCallback; protected int mLastScrollPosition; - private View mCurrentView; - private float mLastY; - private boolean mDragging; - private VelocityTracker mVelocityTracker; - private float mDensityScale; - private float mPagingTouchSlop; + private SwipeHelper mSwipeHelper; private OnLongClickListener mOnLongClick = new OnLongClickListener() { public boolean onLongClick(View v) { final View anchorView = v.findViewById(R.id.app_description); - mCurrentView = v; mCallback.handleLongPress(v, anchorView); - mCurrentView = null; // make sure we don't accept the return click from this return true; } }; @@ -72,8 +55,9 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { public RecentsHorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs, 0); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop); } private int scrollPositionOfMostRecent() { @@ -84,8 +68,16 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { mLinearLayout.removeAllViews(); for (int i = 0; i < mAdapter.getCount(); i++) { final View view = mAdapter.getView(i, null, mLinearLayout); - view.setClickable(true); + view.setLongClickable(true); view.setOnLongClickListener(mOnLongClick); + + final View thumbnail = getChildContentView(view); + // thumbnail is set to clickable in the layout file + thumbnail.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.handleOnClick(view); + } + }); mLinearLayout.addView(view); } // Scroll to end after layout. @@ -99,175 +91,52 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { @Override public void removeViewInLayout(final View view) { - ObjectAnimator anim = animateClosed(view, Constants.MAX_ESCAPE_ANIMATION_DURATION, - "y", view.getY(), view.getY() + view.getHeight()); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - RecentsHorizontalScrollView.super.removeView(view); - } - }); - anim.start(); + dismissChild(view); } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mDragging = false; - mLastY = ev.getY(); - final float x = ev.getX() + getScrollX(); - final float y = ev.getY() + getScrollY(); - mCurrentView = null; - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View item = mLinearLayout.getChildAt(i); - if (x >= item.getLeft() && x < item.getRight() - && y >= item.getTop() && y < item.getBottom()) { - mCurrentView = item; - if (DEBUG) Log.v(TAG, "Hit item " + item); - break; - } - } - break; - - case MotionEvent.ACTION_MOVE: - float delta = ev.getY() - mLastY; - if (DEBUG) Log.v(TAG, "ACTION_MOVE : " + delta); - if (Math.abs(delta) > mPagingTouchSlop) { - mDragging = true; - } - break; - - case MotionEvent.ACTION_UP: - if (mCurrentView != null) { - mCallback.handleOnClick(mCurrentView); - } - mDragging = false; - break; - } - return mDragging ? true : super.onInterceptTouchEvent(ev); - } - - private float getAlphaForOffset(View view, float thumbHeight) { - final float fadeHeight = Constants.ALPHA_FADE_END * thumbHeight; - float result = 1.0f; - if (view.getY() >= thumbHeight * Constants.ALPHA_FADE_START) { - result = 1.0f - (view.getY() - thumbHeight * Constants.ALPHA_FADE_START) / fadeHeight; - } else if (view.getY() < thumbHeight * (1.0f - Constants.ALPHA_FADE_START)) { - result = 1.0f + (thumbHeight * Constants.ALPHA_FADE_START + view.getY()) / fadeHeight; - } - return result; + return mSwipeHelper.onInterceptTouchEvent(ev) || + super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging) { - return super.onTouchEvent(ev); - } - - mVelocityTracker.addMovement(ev); - - final View animView = mCurrentView; - - switch (ev.getAction()) { - case MotionEvent.ACTION_MOVE: - if (animView != null) { - final float delta = ev.getY() - mLastY; - final View thumb = animView.findViewById(R.id.app_thumbnail); - animView.setY(animView.getY() + delta); - animView.setAlpha(getAlphaForOffset(animView, thumb.getHeight())); - invalidateGlobalRegion(animView); - } - mLastY = ev.getY(); - break; + return mSwipeHelper.onTouchEvent(ev) || + super.onTouchEvent(ev); + } - case MotionEvent.ACTION_UP: - final ObjectAnimator anim; - if (animView != null) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, 10000); - final float velocityX = velocityTracker.getXVelocity(); - final float velocityY = velocityTracker.getYVelocity(); - final float curY = animView.getY(); - final float newY = (velocityY >= 0.0f ? 1 : -1) * animView.getHeight(); - final float maxVelocity = Constants.ESCAPE_VELOCITY * mDensityScale; - if (Math.abs(velocityY) > Math.abs(velocityX) - && Math.abs(velocityY) > maxVelocity - && (velocityY >= 0.0f) == (animView.getY() >= 0)) { - long duration = - (long) (Math.abs(newY - curY) * 1000.0f / Math.abs(velocityY)); - duration = Math.min(duration, Constants.MAX_ESCAPE_ANIMATION_DURATION); - anim = animateClosed(animView, duration, "y", curY, newY); - } else { // Animate back to position - long duration = Math.abs(velocityY) > 0.0f ? - (long) (Math.abs(newY - curY) * 1000.0f / Math.abs(velocityY)) - : Constants.SNAP_BACK_DURATION; - duration = Math.min(duration, Constants.SNAP_BACK_DURATION); - anim = ObjectAnimator.ofFloat(animView, "y", animView.getY(), 0.0f); - anim.setInterpolator(new DecelerateInterpolator(4.0f)); - anim.setDuration(duration); - } + public boolean canChildBeDismissed(View v) { + return true; + } - final View thumb = animView.findViewById(R.id.app_thumbnail); - anim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - animView.setAlpha(getAlphaForOffset(animView, thumb.getHeight())); - invalidateGlobalRegion(animView); - } - }); - anim.start(); - } + public void dismissChild(View v) { + mSwipeHelper.dismissChild(v, 0); + } - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - } - return true; + public void onChildDismissed(View v) { + mLinearLayout.removeView(v); + mCallback.handleSwipe(v); } - private ObjectAnimator animateClosed(final View animView, long duration, - String attr, float from, float to) { - ObjectAnimator anim = ObjectAnimator.ofFloat(animView, attr, from, to); - anim.setInterpolator(new LinearInterpolator()); - final int swipeDirection = animView.getX() >= 0.0f ? - RecentsCallback.SWIPE_RIGHT : RecentsCallback.SWIPE_LEFT; - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - public void onAnimationCancel(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - }); - anim.setDuration(duration); - return anim; + public void onBeginDrag(View v) { } - 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 View getChildAtPosition(MotionEvent ev) { + final float x = ev.getX() + getScrollX(); + final float y = ev.getY() + getScrollY(); + for (int i = 0; i < mLinearLayout.getChildCount(); i++) { + View item = mLinearLayout.getChildAt(i); + if (x >= item.getLeft() && x < item.getRight() + && y >= item.getTop() && y < item.getBottom()) { + return item; } } + return null; + } + + public View getChildContentView(View v) { + return v.findViewById(R.id.app_thumbnail); } @Override @@ -283,8 +152,10 @@ public class RecentsHorizontalScrollView extends HorizontalScrollView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + mSwipeHelper.setDensityScale(densityScale); + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); } private void setOverScrollEffectPadding(int leftPadding, int i) { diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index ea54445..e988b68 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -529,7 +529,7 @@ public class RecentsPanelView extends RelativeLayout handleOnClick(view); } - public void handleSwipe(View view, int direction) { + public void handleSwipe(View view) { ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription; if (DEBUG) Log.v(TAG, "Jettison " + ad.label); mActivityDescriptions.remove(ad); diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java index 47ee4aa..1bcc413 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java @@ -16,53 +16,35 @@ package com.android.systemui.recent; -import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; - -import android.animation.Animator; -import android.animation.Animator.AnimatorListener; -import android.animation.AnimatorListenerAdapter; import android.animation.LayoutTransition; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; import android.content.res.Configuration; import android.database.DataSetObserver; -import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; -import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.ScrollView; import com.android.systemui.R; +import com.android.systemui.SwipeHelper; +import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter; -public class RecentsVerticalScrollView extends ScrollView { +public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback { private static final String TAG = RecentsPanelView.TAG; - private static final boolean DEBUG_INVALIDATE = false; private static final boolean DEBUG = RecentsPanelView.DEBUG; private LinearLayout mLinearLayout; private ActivityDescriptionAdapter mAdapter; private RecentsCallback mCallback; protected int mLastScrollPosition; - private View mCurrentView; - private float mLastX; - private boolean mDragging; - private VelocityTracker mVelocityTracker; - private float mDensityScale; - private float mPagingTouchSlop; + private SwipeHelper mSwipeHelper; + private OnLongClickListener mOnLongClick = new OnLongClickListener() { public boolean onLongClick(View v) { final View anchorView = v.findViewById(R.id.app_description); - mCurrentView = v; mCallback.handleLongPress(v, anchorView); - mCurrentView = null; // make sure we don't accept the return click from this return true; } }; @@ -73,8 +55,9 @@ public class RecentsVerticalScrollView extends ScrollView { public RecentsVerticalScrollView(Context context, AttributeSet attrs) { super(context, attrs, 0); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); } private int scrollPositionOfMostRecent() { @@ -87,6 +70,15 @@ public class RecentsVerticalScrollView extends ScrollView { final View view = mAdapter.getView(i, null, mLinearLayout); view.setClickable(true); view.setOnLongClickListener(mOnLongClick); + + final View thumbnail = getChildContentView(view); + // thumbnail is set to clickable in the layout file + thumbnail.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mCallback.handleOnClick(view); + } + }); + mLinearLayout.addView(view); } // Scroll to end after layout. @@ -100,175 +92,52 @@ public class RecentsVerticalScrollView extends ScrollView { @Override public void removeViewInLayout(final View view) { - ObjectAnimator anim = animateClosed(view, Constants.MAX_ESCAPE_ANIMATION_DURATION, - "x", view.getX(), view.getX() + view.getWidth()); - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - RecentsVerticalScrollView.super.removeView(view); - } - }); - anim.start(); + dismissChild(view); } - @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mDragging = false; - mLastX = ev.getX(); - final float x = ev.getX() + getScrollX(); - final float y = ev.getY() + getScrollY(); - mCurrentView = null; - for (int i = 0; i < mLinearLayout.getChildCount(); i++) { - View item = mLinearLayout.getChildAt(i); - if (x >= item.getLeft() && x < item.getRight() - && y >= item.getTop() && y < item.getBottom()) { - mCurrentView = item; - Log.v(TAG, "Hit item " + item); - break; - } - } - break; - - case MotionEvent.ACTION_MOVE: - float delta = ev.getX() - mLastX; - if (DEBUG) Log.v(TAG, "ACTION_MOVE : " + delta); - if (Math.abs(delta) > mPagingTouchSlop) { - mDragging = true; - } - break; - - case MotionEvent.ACTION_UP: - if (mCurrentView != null) { - mCallback.handleOnClick(mCurrentView); - } - mDragging = false; - break; - } - return mDragging ? true : super.onInterceptTouchEvent(ev); - } - - private float getAlphaForOffset(View view, float thumbWidth) { - final float fadeWidth = Constants.ALPHA_FADE_END * thumbWidth; - float result = 1.0f; - if (view.getX() >= thumbWidth*Constants.ALPHA_FADE_START) { - result = 1.0f - (view.getX() - thumbWidth*Constants.ALPHA_FADE_START) / fadeWidth; - } else if (view.getX() < thumbWidth* (1.0f - Constants.ALPHA_FADE_START)) { - result = 1.0f + (thumbWidth*Constants.ALPHA_FADE_START + view.getX()) / fadeWidth; - } - return result; + return mSwipeHelper.onInterceptTouchEvent(ev) || + super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging) { - return super.onTouchEvent(ev); - } - - mVelocityTracker.addMovement(ev); - - final View animView = mCurrentView; - - switch (ev.getAction()) { - case MotionEvent.ACTION_MOVE: - if (animView != null) { - final float delta = ev.getX() - mLastX; - final View thumb = animView.findViewById(R.id.app_thumbnail); - animView.setX(animView.getX() + delta); - animView.setAlpha(getAlphaForOffset(animView, thumb.getWidth())); - invalidateGlobalRegion(animView); - } - mLastX = ev.getX(); - break; + return mSwipeHelper.onTouchEvent(ev) || + super.onTouchEvent(ev); + } - case MotionEvent.ACTION_UP: - final ObjectAnimator anim; - if (animView != null) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, 10000); - final float velocityX = velocityTracker.getXVelocity(); - final float velocityY = velocityTracker.getYVelocity(); - final float curX = animView.getX(); - final float newX = (velocityX >= 0.0f ? 1 : -1) * animView.getWidth(); - final float maxVelocity = Constants.ESCAPE_VELOCITY * mDensityScale; - if (Math.abs(velocityX) > Math.abs(velocityY) - && Math.abs(velocityX) > maxVelocity - && (velocityX >= 0.0f) == (animView.getX() >= 0)) { - long duration = - (long) (Math.abs(newX-curX) * 1000.0f / Math.abs(velocityX)); - duration = Math.min(duration, Constants.MAX_ESCAPE_ANIMATION_DURATION); - anim = animateClosed(animView, duration, "x", curX, newX); - } else { // Animate back to position - long duration = Math.abs(velocityX) > 0.0f ? - (long) (Math.abs(newX-curX) * 1000.0f / Math.abs(velocityX)) - : Constants.SNAP_BACK_DURATION; - duration = Math.min(duration, Constants.SNAP_BACK_DURATION); - anim = ObjectAnimator.ofFloat(animView, "x", animView.getX(), 0.0f); - anim.setInterpolator(new DecelerateInterpolator(4.0f)); - anim.setDuration(duration); - } + public boolean canChildBeDismissed(View v) { + return true; + } - final View thumb = animView.findViewById(R.id.app_thumbnail); - anim.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - animView.setAlpha(getAlphaForOffset(animView, thumb.getWidth())); - invalidateGlobalRegion(animView); - } - }); - anim.start(); - } + public void dismissChild(View v) { + mSwipeHelper.dismissChild(v, 0); + } - mVelocityTracker.recycle(); - mVelocityTracker = null; - break; - } - return true; + public void onChildDismissed(View v) { + mLinearLayout.removeView(v); + mCallback.handleSwipe(v); } - private ObjectAnimator animateClosed(final View animView, long duration, - String attr, float from, float to) { - ObjectAnimator anim = ObjectAnimator.ofFloat(animView, attr, from, to); - anim.setInterpolator(new LinearInterpolator()); - final int swipeDirection = animView.getX() >= 0.0f ? - RecentsCallback.SWIPE_RIGHT : RecentsCallback.SWIPE_LEFT; - anim.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - public void onAnimationCancel(Animator animation) { - mLinearLayout.removeView(animView); - mCallback.handleSwipe(animView, swipeDirection); - } - }); - anim.setDuration(duration); - return anim; + public void onBeginDrag(View v) { } - 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 View getChildAtPosition(MotionEvent ev) { + final float x = ev.getX() + getScrollX(); + final float y = ev.getY() + getScrollY(); + for (int i = 0; i < mLinearLayout.getChildCount(); i++) { + View item = mLinearLayout.getChildAt(i); + if (x >= item.getLeft() && x < item.getRight() + && y >= item.getTop() && y < item.getBottom()) { + return item; } } + return null; + } + + public View getChildContentView(View v) { + return v.findViewById(R.id.app_thumbnail); } @Override @@ -284,8 +153,10 @@ public class RecentsVerticalScrollView extends ScrollView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mDensityScale = getResources().getDisplayMetrics().density; - mPagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + float densityScale = getResources().getDisplayMetrics().density; + mSwipeHelper.setDensityScale(densityScale); + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); } private void setOverScrollEffectPadding(int leftPadding, int i) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java index d5885bb..90234c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java @@ -21,47 +21,33 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeAnimator; -import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.Resources; +import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.util.Log; import android.util.Slog; -import android.view.LayoutInflater; 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.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import java.util.HashSet; import com.android.systemui.R; +import com.android.systemui.SwipeHelper; + +import java.util.HashSet; -public class NotificationRowLayout extends ViewGroup { +public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Callback { private static final String TAG = "NotificationRowLayout"; private static final boolean DEBUG = false; private static final boolean SLOW_ANIMATIONS = false; // DEBUG; private static final boolean ANIMATE_LAYOUT = true; - private static final boolean CLEAR_IF_SWIPED_FAR_ENOUGH = true; - - private static final boolean CONSTRAIN_SWIPE_ON_PERMANENT = true; - private static final int APPEAR_ANIM_LEN = SLOW_ANIMATIONS ? 5000 : 250; private static final int DISAPPEAR_ANIM_LEN = APPEAR_ANIM_LEN; - private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250; - - private static final float SWIPE_ESCAPE_VELOCITY = 1500f; - private static final float SWIPE_ANIM_VELOCITY_MIN = 1000f; Rect mTmpRect = new Rect(); int mNumRows = 0; @@ -71,10 +57,7 @@ public class NotificationRowLayout extends ViewGroup { HashSet mAppearingViews = new HashSet(); HashSet mDisappearingViews = new HashSet(); - VelocityTracker mVT; - float mInitialTouchX, mInitialTouchY; - View mSlidingChild = null; - float mLiftoffVelocity; + private SwipeHelper mSwipeHelper; public NotificationRowLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -83,8 +66,6 @@ public class NotificationRowLayout extends ViewGroup { public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - mVT = VelocityTracker.obtain(); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NotificationRowLayout, defStyle, 0); mRowHeight = a.getDimensionPixelSize(R.styleable.NotificationRowLayout_rowHeight, 0); @@ -107,117 +88,71 @@ public class NotificationRowLayout extends ViewGroup { setBackgroundColor(0x80FF8000); } + float densityScale = getResources().getDisplayMetrics().density; + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop); } - // Swipey code @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); -// if (DEBUG) Slog.d(TAG, "intercepting touch event: " + ev); - switch (action) { - case MotionEvent.ACTION_DOWN: - mVT.clear(); - mVT.addMovement(ev); - mInitialTouchX = ev.getX(); - mInitialTouchY = ev.getY(); - mSlidingChild = null; - break; - case MotionEvent.ACTION_MOVE: - mVT.addMovement(ev); - if (mSlidingChild == null) { - if (Math.abs(ev.getX() - mInitialTouchX) > 4) { // slide slop - - // find the view under the pointer, accounting for GONE views - final int count = getChildCount(); - int y = 0; - int childIdx = 0; - for (; childIdx < count; childIdx++) { - mSlidingChild = getChildAt(childIdx); - if (mSlidingChild.getVisibility() == GONE) { - continue; - } - y += mRowHeight; - if (mInitialTouchY < y) break; - } - - mInitialTouchX -= mSlidingChild.getTranslationX(); - mSlidingChild.animate().cancel(); - - if (DEBUG) { - Slog.d(TAG, String.format( - "now sliding child %d: %s (touchY=%.1f, rowHeight=%d, count=%d)", - childIdx, mSlidingChild, mInitialTouchY, mRowHeight, count)); - } - - - // We need to prevent the surrounding ScrollView from intercepting us now; - // the scroll position will be locked while we swipe - requestDisallowInterceptTouchEvent(true); - } - } - break; - } - return mSlidingChild != null; + if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()"); + return mSwipeHelper.onInterceptTouchEvent(ev) || + super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mSwipeHelper.onTouchEvent(ev) || + super.onTouchEvent(ev); } - protected boolean canBeCleared(View v) { + public boolean canChildBeDismissed(View v) { final View veto = v.findViewById(R.id.veto); return (veto != null && veto.getVisibility() != View.GONE); } - protected boolean clear(View v) { + public void onChildDismissed(View v) { final View veto = v.findViewById(R.id.veto); if (veto != null && veto.getVisibility() != View.GONE) { veto.performClick(); - return true; } - return false; } - @Override - public boolean onTouchEvent(MotionEvent ev) { - final int action = ev.getAction(); -// if (DEBUG) Slog.d(TAG, "touch event: " + ev + " sliding: " + mSlidingChild); - if (mSlidingChild != null) { - switch (action) { - case MotionEvent.ACTION_OUTSIDE: - case MotionEvent.ACTION_MOVE: - mVT.addMovement(ev); - - float delta = (ev.getX() - mInitialTouchX); - if (CONSTRAIN_SWIPE_ON_PERMANENT && !canBeCleared(mSlidingChild)) { - delta = Math.copySign( - Math.min(Math.abs(delta), - mSlidingChild.getMeasuredWidth() * 0.2f), delta); - } - mSlidingChild.setTranslationX(delta); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mVT.addMovement(ev); - mVT.computeCurrentVelocity(1000 /* px/sec */); - if (DEBUG) Slog.d(TAG, "exit velocity: " + mVT.getXVelocity()); - boolean restore = true; - mLiftoffVelocity = mVT.getXVelocity(); - if (Math.abs(mLiftoffVelocity) > SWIPE_ESCAPE_VELOCITY - || (CLEAR_IF_SWIPED_FAR_ENOUGH && - (mSlidingChild.getTranslationX() * 2) > mSlidingChild.getMeasuredWidth())) - { - - // flingadingy - restore = ! clear(mSlidingChild); - } - if (restore) { - // snappity - mSlidingChild.animate().translationX(0) - .setDuration(SNAP_ANIM_LEN) - .start(); - } - break; + public void onBeginDrag(View v) { + // We need to prevent the surrounding ScrollView from intercepting us now; + // the scroll position will be locked while we swipe + requestDisallowInterceptTouchEvent(true); + } + + public View getChildAtPosition(MotionEvent ev) { + // find the view under the pointer, accounting for GONE views + final int count = getChildCount(); + int y = 0; + int touchY = (int) ev.getY(); + int childIdx = 0; + View slidingChild; + for (; childIdx < count; childIdx++) { + slidingChild = getChildAt(childIdx); + if (slidingChild.getVisibility() == GONE) { + continue; } - return true; + y += mRowHeight; + if (touchY < y) return slidingChild; } - return false; + return null; + } + + public View getChildContentView(View v) { + return v; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + float densityScale = getResources().getDisplayMetrics().density; + mSwipeHelper.setDensityScale(densityScale); + float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop(); + mSwipeHelper.setPagingTouchSlop(pagingTouchSlop); } //** @@ -260,9 +195,10 @@ public class NotificationRowLayout extends ViewGroup { child.setPivotY(0); - final float velocity = (mSlidingChild == child) - ? Math.min(mLiftoffVelocity, SWIPE_ANIM_VELOCITY_MIN) - : SWIPE_ESCAPE_VELOCITY; + //final float velocity = (mSlidingChild == child) + // ? Math.min(mLiftoffVelocity, SWIPE_ANIM_VELOCITY_MIN) + // : SWIPE_ESCAPE_VELOCITY; + final float velocity = 0f; final TimeAnimator zoom = new TimeAnimator(); zoom.setTimeListener(new TimeAnimator.TimeListener() { @Override -- cgit v1.1