Merge "Implemented basic camera and phone affordance." into lmp-preview-dev

This commit is contained in:
Selim Cinek
2014-05-28 14:17:24 +00:00
committed by Android (Google) Code Review
11 changed files with 759 additions and 359 deletions

View File

@@ -22,7 +22,7 @@
android:layout_height="match_parent"
android:layout_width="match_parent"
>
<com.android.systemui.statusbar.phone.SwipeAffordanceView
<com.android.systemui.statusbar.AlphaImageView
android:id="@+id/camera_button"
android:layout_height="64dp"
android:layout_width="64dp"
@@ -30,10 +30,9 @@
android:tint="#ffffffff"
android:src="@drawable/ic_camera_alt_24dp"
android:scaleType="center"
android:contentDescription="@string/accessibility_camera_button"
systemui:swipeDirection="start"/>
android:contentDescription="@string/accessibility_camera_button" />
<com.android.systemui.statusbar.phone.SwipeAffordanceView
<com.android.systemui.statusbar.AlphaImageView
android:id="@+id/phone_button"
android:layout_height="64dp"
android:layout_width="64dp"
@@ -41,8 +40,7 @@
android:tint="#ffffffff"
android:src="@drawable/ic_phone_24dp"
android:scaleType="center"
android:contentDescription="@string/accessibility_phone_button"
systemui:swipeDirection="end"/>
android:contentDescription="@string/accessibility_phone_button" />
<com.android.systemui.statusbar.phone.KeyguardIndicationTextView
android:id="@+id/keyguard_indication_text"
@@ -54,15 +52,13 @@
android:textColor="#ffffff"
android:textAppearance="?android:attr/textAppearanceSmall"/>
<ImageView
<com.android.systemui.statusbar.AlphaImageView
android:id="@+id/lock_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_gravity="bottom|center_horizontal"
android:src="@drawable/ic_lock_24dp"
android:scaleType="center"
android:alpha="0.7"
android:layerType="hardware"
android:tint="#ffffffff"/>
android:tint="#ffffffff" />
</com.android.systemui.statusbar.phone.KeyguardBottomAreaView>

View File

@@ -43,12 +43,6 @@
<declare-styleable name="BatteryMeterView">
<attr name="frameColor" format="color" />
</declare-styleable>
<declare-styleable name="SwipeAffordanceView">
<attr name="swipeDirection" format="enum">
<enum name="start" value="0" />
<enum name="end" value="1" />
</attr>
</declare-styleable>
<declare-styleable name="Clock">
<attr name="amPmStyle" format="enum">
<enum name="normal" value="0" />

View File

@@ -294,6 +294,9 @@
<dimen name="keyguard_clock_notifications_margin_min">22dp</dimen>
<dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
<!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
<dimen name="keyguard_min_swipe_amount">75dp</dimen>
<!-- Volume panel dialog y offset -->
<dimen name="volume_panel_top">16dp</dimen>

View File

@@ -0,0 +1,48 @@
/*
* 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.systemui.statusbar;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* An ImageView which does not have overlapping renderings commands and therefore does not need a
* layer when alpha is changed.
*/
public class AlphaImageView extends ImageView {
public AlphaImageView(Context context) {
super(context);
}
public AlphaImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar;
import android.animation.ValueAnimator;
import android.content.Context;
import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
@@ -46,6 +47,8 @@ public class FlingAnimationUtils {
private float mMaxLengthSeconds;
private float mHighVelocityPxPerSecond;
private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
mMaxLengthSeconds = maxLengthSeconds;
mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1);
@@ -72,6 +75,20 @@ public class FlingAnimationUtils {
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
}
/**
* Applies the interpolator and length to the animator, such that the fling animation is
* consistent with the finger motion.
*
* @param animator the animator to apply
* @param currValue the current value
* @param endValue the end value of the animator
* @param velocity the current velocity of the motion
*/
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity) {
apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
}
/**
* Applies the interpolator and length to the animator, such that the fling animation is
* consistent with the finger motion.
@@ -85,13 +102,40 @@ public class FlingAnimationUtils {
*/
public void apply(ValueAnimator animator, float currValue, float endValue, float velocity,
float maxDistance) {
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
maxDistance);
animator.setDuration(properties.duration);
animator.setInterpolator(properties.interpolator);
}
/**
* Applies the interpolator and length to the animator, such that the fling animation is
* consistent with the finger motion.
*
* @param animator the animator to apply
* @param currValue the current value
* @param endValue the end value of the animator
* @param velocity the current velocity of the motion
* @param maxDistance the maximum distance for this interaction; the maximum animation length
* gets multiplied by the ratio between the actual distance and this value
*/
public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity, float maxDistance) {
AnimatorProperties properties = getProperties(currValue, endValue, velocity,
maxDistance);
animator.setDuration(properties.duration);
animator.setInterpolator(properties.interpolator);
}
private AnimatorProperties getProperties(float currValue,
float endValue, float velocity, float maxDistance) {
float maxLengthSeconds = (float) (mMaxLengthSeconds
* Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
float diff = Math.abs(endValue - currValue);
float velAbs = Math.abs(velocity);
float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs;
if (durationSeconds <= maxLengthSeconds) {
animator.setInterpolator(mLinearOutSlowIn);
mAnimatorProperties.interpolator = mLinearOutSlowIn;
} else if (velAbs >= mMinVelocityPxPerSecond) {
// Cross fade between fast-out-slow-in and linear interpolator with current velocity.
@@ -100,14 +144,15 @@ public class FlingAnimationUtils {
= new VelocityInterpolator(durationSeconds, velAbs, diff);
InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn);
animator.setInterpolator(superInterpolator);
mAnimatorProperties.interpolator = superInterpolator;
} else {
// Just use a normal interpolator which doesn't take the velocity into account.
durationSeconds = maxLengthSeconds;
animator.setInterpolator(mFastOutSlowIn);
mAnimatorProperties.interpolator = mFastOutSlowIn;
}
animator.setDuration((long) (durationSeconds * 1000));
mAnimatorProperties.duration = (long) (durationSeconds * 1000);
return mAnimatorProperties;
}
/**
@@ -124,6 +169,34 @@ public class FlingAnimationUtils {
*/
public void applyDismissing(ValueAnimator animator, float currValue, float endValue,
float velocity, float maxDistance) {
AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
maxDistance);
animator.setDuration(properties.duration);
animator.setInterpolator(properties.interpolator);
}
/**
* Applies the interpolator and length to the animator, such that the fling animation is
* consistent with the finger motion for the case when the animation is making something
* disappear.
*
* @param animator the animator to apply
* @param currValue the current value
* @param endValue the end value of the animator
* @param velocity the current velocity of the motion
* @param maxDistance the maximum distance for this interaction; the maximum animation length
* gets multiplied by the ratio between the actual distance and this value
*/
public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
float velocity, float maxDistance) {
AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
maxDistance);
animator.setDuration(properties.duration);
animator.setInterpolator(properties.interpolator);
}
private AnimatorProperties getDismissingProperties(float currValue, float endValue,
float velocity, float maxDistance) {
float maxLengthSeconds = (float) (mMaxLengthSeconds
* Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
float diff = Math.abs(endValue - currValue);
@@ -135,7 +208,7 @@ public class FlingAnimationUtils {
Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, 1, y2);
float durationSeconds = startGradient * diff / velAbs;
if (durationSeconds <= maxLengthSeconds) {
animator.setInterpolator(mLinearOutFasterIn);
mAnimatorProperties.interpolator = mLinearOutFasterIn;
} else if (velAbs >= mMinVelocityPxPerSecond) {
// Cross fade between linear-out-faster-in and linear interpolator with current
@@ -145,14 +218,15 @@ public class FlingAnimationUtils {
= new VelocityInterpolator(durationSeconds, velAbs, diff);
InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn);
animator.setInterpolator(superInterpolator);
mAnimatorProperties.interpolator = superInterpolator;
} else {
// Just use a normal interpolator which doesn't take the velocity into account.
durationSeconds = maxLengthSeconds;
animator.setInterpolator(mFastOutLinearIn);
mAnimatorProperties.interpolator = mFastOutLinearIn;
}
animator.setDuration((long) (durationSeconds * 1000));
mAnimatorProperties.duration = (long) (durationSeconds * 1000);
return mAnimatorProperties;
}
/**
@@ -221,4 +295,10 @@ public class FlingAnimationUtils {
return time * mVelocity / mDiff;
}
}
private static class AnimatorProperties {
Interpolator interpolator;
long duration;
}
}

View File

@@ -23,7 +23,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
@@ -33,26 +32,23 @@ import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.systemui.R;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
*/
public class KeyguardBottomAreaView extends FrameLayout
implements SwipeAffordanceView.AffordanceListener,
public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
UnlockMethodCache.OnUnlockMethodChangedListener {
final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
private SwipeAffordanceView mCameraButton;
private SwipeAffordanceView mPhoneButton;
private ImageView mCameraImageView;
private ImageView mPhoneImageView;
private ImageView mLockIcon;
private PowerManager mPowerManager;
private ActivityStarter mActivityStarter;
private UnlockMethodCache mUnlockMethodCache;
@@ -76,11 +72,9 @@ public class KeyguardBottomAreaView extends FrameLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mCameraButton = (SwipeAffordanceView) findViewById(R.id.camera_button);
mPhoneButton = (SwipeAffordanceView) findViewById(R.id.phone_button);
mCameraImageView = (ImageView) findViewById(R.id.camera_button);
mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
mLockIcon = (ImageView) findViewById(R.id.lock_icon);
mCameraButton.setAffordanceListener(this);
mPhoneButton.setAffordanceListener(this);
watchForDevicePolicyChanges();
watchForAccessibilityChanges();
updateCameraVisibility();
@@ -88,7 +82,6 @@ public class KeyguardBottomAreaView extends FrameLayout
mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
mUnlockMethodCache.addListener(this);
updateTrust();
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
public void setActivityStarter(ActivityStarter activityStarter) {
@@ -97,12 +90,12 @@ public class KeyguardBottomAreaView extends FrameLayout
private void updateCameraVisibility() {
boolean visible = !isCameraDisabledByDpm();
mCameraButton.setVisibility(visible ? View.VISIBLE : View.GONE);
mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
private void updatePhoneVisibility() {
boolean visible = isPhoneVisible();
mPhoneButton.setVisibility(visible ? View.VISIBLE : View.GONE);
mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
private boolean isPhoneVisible() {
@@ -162,33 +155,31 @@ public class KeyguardBottomAreaView extends FrameLayout
}
private void enableAccessibility(boolean touchExplorationEnabled) {
mCameraButton.enableAccessibility(touchExplorationEnabled);
mPhoneButton.enableAccessibility(touchExplorationEnabled);
mCameraImageView.setOnClickListener(touchExplorationEnabled ? this : null);
mCameraImageView.setClickable(touchExplorationEnabled);
mPhoneImageView.setOnClickListener(touchExplorationEnabled ? this : null);
mPhoneImageView.setClickable(touchExplorationEnabled);
}
private void launchCamera() {
@Override
public void onClick(View v) {
if (v == mCameraImageView) {
launchCamera();
} else if (v == mPhoneImageView) {
launchPhone();
}
}
public void launchCamera() {
mContext.startActivityAsUser(
new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE),
UserHandle.CURRENT);
}
private void launchPhone() {
public void launchPhone() {
mActivityStarter.startActivity(PHONE_INTENT);
}
@Override
public void onUserActivity(long when) {
mPowerManager.userActivity(when, false);
}
@Override
public void onActionPerformed(SwipeAffordanceView view) {
if (view == mCameraButton) {
launchCamera();
} else if (view == mPhoneButton) {
launchPhone();
}
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
@@ -208,6 +199,18 @@ public class KeyguardBottomAreaView extends FrameLayout
mLockIcon.setImageResource(iconRes);
}
public ImageView getPhoneImageView() {
return mPhoneImageView;
}
public ImageView getCameraImageView() {
return mCameraImageView;
}
public ImageView getLockIcon() {
return mLockIcon;
}
@Override
public void onMethodSecureChanged(boolean methodSecure) {
updateTrust();

View File

@@ -0,0 +1,398 @@
/*
* 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.systemui.statusbar.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.os.PowerManager;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import com.android.systemui.R;
import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.ArrayList;
/**
* A touch handler of the Keyguard which is responsible for swiping the content left or right.
*/
public class KeyguardPageSwipeHelper {
private static final float SWIPE_MAX_ICON_SCALE_AMOUNT = 2.0f;
private static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.7f;
private final Context mContext;
private FlingAnimationUtils mFlingAnimationUtils;
private Callback mCallback;
private int mTrackingPointer;
private VelocityTracker mVelocityTracker;
private boolean mSwipingInProgress;
private float mInitialTouchX;
private float mInitialTouchY;
private float mTranslation;
private float mTranslationOnDown;
private int mTouchSlop;
private int mMinTranslationAmount;
private int mMinFlingVelocity;
private PowerManager mPowerManager;
private final View mLeftIcon;
private final View mCenterIcon;
private final View mRightIcon;
private Interpolator mFastOutSlowIn;
private Animator mSwipeAnimator;
private boolean mCallbackCalled;
KeyguardPageSwipeHelper(Callback callback, Context context) {
mContext = context;
mCallback = callback;
mLeftIcon = mCallback.getLeftIcon();
mCenterIcon = mCallback.getCenterIcon();
mRightIcon = mCallback.getRightIcon();
updateIcon(mLeftIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
updateIcon(mCenterIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
updateIcon(mRightIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false);
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
initDimens();
}
private void initDimens() {
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_min_swipe_amount);
mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f);
mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.fast_out_slow_in);
}
public boolean onTouchEvent(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (mSwipingInProgress) {
cancelAnimations();
}
mInitialTouchY = y;
mInitialTouchX = x;
mTranslationOnDown = mTranslation;
initVelocityTracker();
trackMovement(event);
break;
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
mInitialTouchY = newY;
mInitialTouchX = newX;
mTranslationOnDown = mTranslation;
}
break;
case MotionEvent.ACTION_MOVE:
final float w = x - mInitialTouchX;
trackMovement(event);
if (((leftSwipePossible() && w > mTouchSlop)
|| (rightSwipePossible() && w < -mTouchSlop))
&& Math.abs(w) > Math.abs(y - mInitialTouchY)
&& !mSwipingInProgress) {
cancelAnimations();
mInitialTouchY = y;
mInitialTouchX = x;
mTranslationOnDown = mTranslation;
mSwipingInProgress = true;
}
if (mSwipingInProgress) {
setTranslation(mTranslationOnDown + x - mInitialTouchX, false);
onUserActivity(event.getEventTime());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTrackingPointer = -1;
trackMovement(event);
if (mSwipingInProgress) {
flingWithCurrentVelocity();
}
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
return true;
}
private boolean rightSwipePossible() {
return mRightIcon.getVisibility() == View.VISIBLE;
}
private boolean leftSwipePossible() {
return mLeftIcon.getVisibility() == View.VISIBLE;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
private void onUserActivity(long when) {
mPowerManager.userActivity(when, false);
}
private void cancelAnimations() {
ArrayList<View> targetViews = mCallback.getTranslationViews();
for (View target : targetViews) {
target.animate().cancel();
}
View targetView = mTranslation > 0 ? mLeftIcon : mRightIcon;
targetView.animate().cancel();
if (mSwipeAnimator != null) {
mSwipeAnimator.removeAllListeners();
mSwipeAnimator.cancel();
hideInactiveIcons(true);
}
}
private void flingWithCurrentVelocity() {
float vel = getCurrentVelocity();
// We snap back if the current translation is not far enough
boolean snapBack = Math.abs(mTranslation) < mMinTranslationAmount;
// or if the velocity is in the opposite direction.
boolean velIsInWrongDirection = vel * mTranslation < 0;
snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
fling(vel, snapBack);
}
private void fling(float vel, final boolean snapBack) {
float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth();
target = snapBack ? 0 : target;
// translation Animation
startTranslationAnimations(vel, target);
// animate left / right icon
startIconAnimation(vel, snapBack, target);
ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mTranslation = (float) animation.getAnimatedValue();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mSwipeAnimator = null;
mSwipingInProgress = false;
if (!snapBack && !mCallbackCalled) {
// ensure that the callback is called eventually
mCallback.onAnimationToSideStarted(mTranslation < 0);
mCallbackCalled = true;
}
}
});
if (!snapBack) {
mCallbackCalled = false;
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
int frameNumber;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (frameNumber == 2 && !mCallbackCalled) {
// we have to wait for the second frame for this call,
// until the render thread has definitely kicked in, to avoid a lag.
mCallback.onAnimationToSideStarted(mTranslation < 0);
mCallbackCalled = true;
}
frameNumber++;
}
});
} else {
showAllIcons(true);
}
animator.start();
mSwipeAnimator = animator;
}
private void startTranslationAnimations(float vel, float target) {
ArrayList<View> targetViews = mCallback.getTranslationViews();
for (View targetView : targetViews) {
ViewPropertyAnimator animator = targetView.animate();
mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
animator.translationX(target);
}
}
private void startIconAnimation(float vel, boolean snapBack, float target) {
float scale = snapBack ? 1.0f : SWIPE_MAX_ICON_SCALE_AMOUNT;
float alpha = snapBack ? SWIPE_RESTING_ALPHA_AMOUNT : 1.0f;
View targetView = mTranslation > 0
? mLeftIcon
: mRightIcon;
if (targetView.getVisibility() == View.VISIBLE) {
ViewPropertyAnimator iconAnimator = targetView.animate();
mFlingAnimationUtils.apply(iconAnimator, mTranslation, target, vel);
iconAnimator.scaleX(scale);
iconAnimator.scaleY(scale);
iconAnimator.alpha(alpha);
}
}
private void setTranslation(float translation, boolean isReset) {
translation = rightSwipePossible() ? translation : Math.max(0, translation);
translation = leftSwipePossible() ? translation : Math.min(0, translation);
if (translation != mTranslation) {
ArrayList<View> translatedViews = mCallback.getTranslationViews();
for (View view : translatedViews) {
view.setTranslationX(translation);
}
if (translation == 0.0f) {
boolean animate = !isReset;
showAllIcons(animate);
} else {
View targetView = translation > 0 ? mLeftIcon : mRightIcon;
float progress = Math.abs(translation) / mCallback.getPageWidth();
progress = Math.min(progress, 1.0f);
float alpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - progress) + progress;
float scale = (1.0f - progress) + progress * SWIPE_MAX_ICON_SCALE_AMOUNT;
updateIcon(targetView, scale, alpha, false);
View otherView = translation < 0 ? mLeftIcon : mRightIcon;
if (mTranslation * translation <= 0) {
// The sign of the translation has changed so we need to hide the other icons
updateIcon(otherView, 0, 0, true);
updateIcon(mCenterIcon, 0, 0, true);
}
}
mTranslation = translation;
}
}
private void showAllIcons(boolean animate) {
float scale = 1.0f;
float alpha = SWIPE_RESTING_ALPHA_AMOUNT;
updateIcon(mRightIcon, scale, alpha, animate);
updateIcon(mCenterIcon, scale, alpha, animate);
updateIcon(mLeftIcon, scale, alpha, animate);
}
private void hideInactiveIcons(boolean animate){
View otherView = mTranslation < 0 ? mLeftIcon : mRightIcon;
updateIcon(otherView, 0, 0, animate);
updateIcon(mCenterIcon, 0, 0, animate);
}
private void updateIcon(View view, float scale, float alpha, boolean animate) {
if (view.getVisibility() != View.VISIBLE) {
return;
}
if (!animate) {
view.setAlpha(alpha);
view.setScaleX(scale);
view.setScaleY(scale);
// TODO: remove this invalidate once the property setters invalidate it properly
view.invalidate();
} else {
if (view.getAlpha() != alpha || view.getScaleX() != scale) {
view.animate()
.setInterpolator(mFastOutSlowIn)
.alpha(alpha)
.scaleX(scale)
.scaleY(scale);
}
}
}
private void trackMovement(MotionEvent event) {
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(event);
}
}
private void initVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
}
mVelocityTracker = VelocityTracker.obtain();
}
private float getCurrentVelocity() {
if (mVelocityTracker == null) {
return 0;
}
mVelocityTracker.computeCurrentVelocity(1000);
return mVelocityTracker.getXVelocity();
}
public void onConfigurationChanged() {
initDimens();
}
public void reset() {
setTranslation(0.0f, true);
mSwipingInProgress = false;
}
public boolean isSwipingInProgress() {
return mSwipingInProgress;
}
public interface Callback {
/**
* Notifies the callback when an animation to a side page was started.
*
* @param rightPage Is the page animated to the right page?
*/
void onAnimationToSideStarted(boolean rightPage);
float getPageWidth();
ArrayList<View> getTranslationViews();
View getLeftIcon();
View getCenterIcon();
View getRightIcon();
}
}

View File

@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -40,10 +41,13 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.ArrayList;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
View.OnClickListener {
View.OnClickListener, KeyguardPageSwipeHelper.Callback {
private KeyguardPageSwipeHelper mPageSwiper;
PhoneStatusBar mStatusBar;
private StatusBarHeaderView mHeader;
private View mQsContainer;
@@ -58,7 +62,7 @@ public class NotificationPanelView extends PanelView implements
private int mTrackingPointer;
private VelocityTracker mVelocityTracker;
private boolean mTracking;
private boolean mQsTracking;
/**
* Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
@@ -92,6 +96,11 @@ public class NotificationPanelView extends PanelView implements
new KeyguardClockPositionAlgorithm();
private KeyguardClockPositionAlgorithm.Result mClockPositionResult =
new KeyguardClockPositionAlgorithm.Result();
private boolean mIsSwipedHorizontally;
private boolean mIsExpanding;
private KeyguardBottomAreaView mKeyguardBottomArea;
private boolean mBlockTouches;
private ArrayList<View> mSwipeTranslationViews = new ArrayList<>();
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -129,6 +138,10 @@ public class NotificationPanelView extends PanelView implements
mNotificationStackScroller.setOnHeightChangedListener(this);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.fast_out_slow_in);
mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
mSwipeTranslationViews.add(mNotificationStackScroller);
mSwipeTranslationViews.add(mKeyguardStatusView);
mPageSwiper = new KeyguardPageSwipeHelper(this, getContext());
}
@Override
@@ -247,6 +260,12 @@ public class NotificationPanelView extends PanelView implements
mQsExpansionEnabled = qsExpansionEnabled;
}
public void resetViews() {
mBlockTouches = false;
mPageSwiper.reset();
closeQs();
}
public void closeQs() {
cancelAnimation();
setQsExpansion(mQsMinExpansionHeight);
@@ -263,9 +282,7 @@ public class NotificationPanelView extends PanelView implements
public void fling(float vel, boolean always) {
GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
if (gr != null) {
gr.tag(
"fling " + ((vel > 0) ? "open" : "closed"),
"notifications,v=" + vel);
gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
}
super.fling(vel, always);
}
@@ -283,6 +300,9 @@ public class NotificationPanelView extends PanelView implements
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches) {
return false;
}
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -298,7 +318,7 @@ public class NotificationPanelView extends PanelView implements
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) {
if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
break;
@@ -316,7 +336,7 @@ public class NotificationPanelView extends PanelView implements
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
trackMovement(event);
if (mTracking) {
if (mQsTracking) {
// Already tracking because onOverscrolled was called. We need to update here
// so we don't stop for a frame until the next touch event gets handled in
@@ -327,12 +347,12 @@ public class NotificationPanelView extends PanelView implements
return true;
}
if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldIntercept(mInitialTouchX, mInitialTouchY, h)) {
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = y;
mInitialTouchX = x;
mTracking = true;
mQsTracking = true;
mIntercepting = false;
return true;
}
@@ -341,9 +361,9 @@ public class NotificationPanelView extends PanelView implements
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
trackMovement(event);
if (mTracking) {
flingWithCurrentVelocity();
mTracking = false;
if (mQsTracking) {
flingQsWithCurrentVelocity();
mQsTracking = false;
}
mIntercepting = false;
break;
@@ -362,7 +382,7 @@ public class NotificationPanelView extends PanelView implements
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
private void flingWithCurrentVelocity() {
private void flingQsWithCurrentVelocity() {
float vel = getCurrentVelocity();
// TODO: Better logic whether we should expand or not.
@@ -371,65 +391,83 @@ public class NotificationPanelView extends PanelView implements
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mBlockTouches) {
return false;
}
// TODO: Handle doublefinger swipe to notifications again. Look at history for a reference
// implementation.
if (mTracking) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
if (!mIsExpanding && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) {
mPageSwiper.onTouchEvent(event);
if (mPageSwiper.isSwipingInProgress()) {
return true;
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTracking = true;
mInitialTouchY = y;
mInitialTouchX = x;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
initVelocityTracker();
trackMovement(event);
break;
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
}
break;
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
setQsExpansion(h + mInitialHeightOnTouch);
trackMovement(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTracking = false;
mTrackingPointer = -1;
trackMovement(event);
flingWithCurrentVelocity();
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
return true;
}
if (mQsTracking || mQsExpanded) {
return onQsTouch(event);
}
// Consume touch events when QS are expanded.
return mQsExpanded || super.onTouchEvent(event);
super.onTouchEvent(event);
return true;
}
@Override
protected boolean hasConflictingGestures() {
return mStatusBar.getBarState() != StatusBarState.SHADE;
}
private boolean onQsTouch(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
mTrackingPointer = event.getPointerId(pointerIndex);
}
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mQsTracking = true;
mInitialTouchY = y;
mInitialTouchX = x;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
initVelocityTracker();
trackMovement(event);
break;
case MotionEvent.ACTION_POINTER_UP:
final int upPointer = event.getPointerId(event.getActionIndex());
if (mTrackingPointer == upPointer) {
// gesture is ongoing, find a new pointer to track
final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
final float newY = event.getY(newIndex);
final float newX = event.getX(newIndex);
mTrackingPointer = event.getPointerId(newIndex);
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = newY;
mInitialTouchX = newX;
}
break;
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
setQsExpansion(h + mInitialHeightOnTouch);
trackMovement(event);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mQsTracking = false;
mTrackingPointer = -1;
trackMovement(event);
flingQsWithCurrentVelocity();
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
return true;
}
@Override
@@ -439,7 +477,7 @@ public class NotificationPanelView extends PanelView implements
mInitialHeightOnTouch = mQsExpansionHeight;
mInitialTouchY = mLastTouchY;
mInitialTouchX = mLastTouchX;
mTracking = true;
mQsTracking = true;
}
}
@@ -569,7 +607,7 @@ public class NotificationPanelView extends PanelView implements
/**
* @return Whether we should intercept a gesture to open Quick Settings.
*/
private boolean shouldIntercept(float x, float y, float yDiff) {
private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
if (!mQsExpansionEnabled) {
return false;
}
@@ -647,12 +685,14 @@ public class NotificationPanelView extends PanelView implements
protected void onExpandingStarted() {
super.onExpandingStarted();
mNotificationStackScroller.onExpansionStarted();
mIsExpanding = true;
}
@Override
protected void onExpandingFinished() {
super.onExpandingFinished();
mNotificationStackScroller.onExpansionStopped();
mIsExpanding = false;
}
@Override
@@ -685,6 +725,12 @@ public class NotificationPanelView extends PanelView implements
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mPageSwiper.onConfigurationChanged();
}
@Override
public void onClick(View v) {
if (v == mHeader.getBackgroundView()) {
@@ -696,4 +742,40 @@ public class NotificationPanelView extends PanelView implements
}
}
}
@Override
public void onAnimationToSideStarted(boolean rightPage) {
if (rightPage) {
mKeyguardBottomArea.launchCamera();
} else {
mKeyguardBottomArea.launchPhone();
}
mBlockTouches = true;
}
@Override
public float getPageWidth() {
return getWidth();
}
@Override
public ArrayList<View> getTranslationViews() {
return mSwipeTranslationViews;
}
@Override
public View getLeftIcon() {
return mKeyguardBottomArea.getPhoneImageView();
}
@Override
public View getCenterIcon() {
return mKeyguardBottomArea.getLockIcon();
}
@Override
public View getRightIcon() {
return mKeyguardBottomArea.getCameraImageView();
}
}

View File

@@ -35,7 +35,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
public class PanelView extends FrameLayout {
public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = PanelBar.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
protected float mOverExpansion;
@@ -130,21 +130,24 @@ public class PanelView extends FrameLayout {
final float y = event.getY(pointerIndex);
final float x = event.getX(pointerIndex);
boolean waitForTouchSlop = hasConflictingGestures();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mTracking = true;
mInitialTouchY = y;
mInitialTouchX = x;
mInitialOffsetOnTouch = mExpandedHeight;
if (mVelocityTracker == null) {
initVelocityTracker();
}
trackMovement(event);
if (mHeightAnimator != null) {
mHeightAnimator.cancel(); // end any outstanding animations
if (!waitForTouchSlop || mHeightAnimator != null) {
if (mHeightAnimator != null) {
mHeightAnimator.cancel(); // end any outstanding animations
}
onTrackingStarted();
}
onTrackingStarted();
mInitialOffsetOnTouch = mExpandedHeight;
if (mExpandedHeight == 0) {
mJustPeeked = true;
runPeekAnimation();
@@ -166,15 +169,27 @@ public class PanelView extends FrameLayout {
break;
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY + mInitialOffsetOnTouch;
if (h > mPeekHeight) {
float h = y - mInitialTouchY;
if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop
&& Math.abs(h) > Math.abs(x - mInitialTouchX)) {
mInitialOffsetOnTouch = mExpandedHeight;
mInitialTouchX = x;
mInitialTouchY = y;
if (mHeightAnimator != null) {
mHeightAnimator.cancel(); // end any outstanding animations
}
onTrackingStarted();
h = 0;
}
final float newHeight = h + mInitialOffsetOnTouch;
if (newHeight > mPeekHeight) {
if (mPeekAnimator != null && mPeekAnimator.isStarted()) {
mPeekAnimator.cancel();
}
mJustPeeked = false;
}
if (!mJustPeeked) {
setExpandedHeightInternal(h);
if (!mJustPeeked && (!waitForTouchSlop || mTracking)) {
setExpandedHeightInternal(newHeight);
mBar.panelExpansionChanged(PanelView.this, mExpandedFraction);
}
@@ -183,7 +198,6 @@ public class PanelView extends FrameLayout {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mTracking = false;
mTrackingPointer = -1;
trackMovement(event);
boolean expand = flingWithCurrentVelocity();
@@ -194,14 +208,18 @@ public class PanelView extends FrameLayout {
}
break;
}
return true;
return !waitForTouchSlop || mTracking;
}
protected abstract boolean hasConflictingGestures();
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mBar.onTrackingStopped(PanelView.this, expand);
}
protected void onTrackingStarted() {
mTracking = true;
mBar.onTrackingStarted(PanelView.this);
onExpandingStarted();
}

View File

@@ -2774,7 +2774,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardStatusView.setVisibility(View.VISIBLE);
mKeyguardIndicationTextView.setVisibility(View.VISIBLE);
mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
mNotificationPanel.closeQs();
mNotificationPanel.resetViews();
} else {
mKeyguardStatusView.setVisibility(View.GONE);
mKeyguardIndicationTextView.setVisibility(View.GONE);

View File

@@ -1,222 +0,0 @@
/*
* 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.systemui.statusbar.phone;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.KeyButtonView;
/**
* A swipeable button for affordances on the lockscreen. This is used for the camera and phone
* affordance.
*/
public class SwipeAffordanceView extends KeyButtonView {
private static final int SWIPE_DIRECTION_START = 0;
private static final int SWIPE_DIRECTION_END = 1;
private static final int SWIPE_DIRECTION_LEFT = 0;
private static final int SWIPE_DIRECTION_RIGHT = 1;
private AffordanceListener mListener;
private int mScaledTouchSlop;
private float mDragDistance;
private int mResolvedSwipeDirection;
private int mSwipeDirection;
public SwipeAffordanceView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public SwipeAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SwipeAffordanceView,
0, 0);
try {
mSwipeDirection = a.getInt(R.styleable.SwipeAffordanceView_swipeDirection, 0);
} finally {
a.recycle();
}
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
if (!isLayoutRtl()) {
mResolvedSwipeDirection = mSwipeDirection;
} else {
mResolvedSwipeDirection = mSwipeDirection == SWIPE_DIRECTION_START
? SWIPE_DIRECTION_RIGHT
: SWIPE_DIRECTION_LEFT;
}
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mDragDistance = getResources().getDimension(R.dimen.affordance_drag_distance);
mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
public void enableAccessibility(boolean touchExplorationEnabled) {
// Add a touch handler or accessibility click listener for camera button.
if (touchExplorationEnabled) {
setOnTouchListener(null);
setOnClickListener(mClickListener);
} else {
setOnTouchListener(mTouchListener);
setOnClickListener(null);
}
}
public void setAffordanceListener(AffordanceListener listener) {
mListener = listener;
}
private void onActionPerformed() {
if (mListener != null) {
mListener.onActionPerformed(this);
}
}
private void onUserActivity(long when) {
if (mListener != null) {
mListener.onUserActivity(when);
}
}
private final OnClickListener mClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
onActionPerformed();
}
};
private final OnTouchListener mTouchListener = new OnTouchListener() {
private float mStartX;
private boolean mTouchSlopReached;
private boolean mSkipCancelAnimation;
@Override
public boolean onTouch(final View view, MotionEvent event) {
float realX = event.getRawX();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = realX;
mTouchSlopReached = false;
mSkipCancelAnimation = false;
break;
case MotionEvent.ACTION_MOVE:
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX > mStartX
: realX < mStartX) {
realX = mStartX;
}
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mDragDistance
: realX > mStartX + mDragDistance) {
view.setPressed(true);
onUserActivity(event.getEventTime());
} else {
view.setPressed(false);
}
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mScaledTouchSlop
: realX > mStartX + mScaledTouchSlop) {
mTouchSlopReached = true;
}
view.setTranslationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? Math.max(realX - mStartX, -mDragDistance)
: Math.min(realX - mStartX, mDragDistance));
break;
case MotionEvent.ACTION_UP:
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mDragDistance
: realX > mStartX + mDragDistance) {
onActionPerformed();
view.animate().x(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? -view.getWidth()
: ((View) view.getParent()).getWidth() + view.getWidth())
.setInterpolator(new AccelerateInterpolator(2f)).withEndAction(
new Runnable() {
@Override
public void run() {
view.setTranslationX(0);
}
});
mSkipCancelAnimation = true;
}
if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? realX < mStartX - mScaledTouchSlop
: realX > mStartX + mScaledTouchSlop) {
mTouchSlopReached = true;
}
if (!mTouchSlopReached) {
mSkipCancelAnimation = true;
view.animate().translationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT
? -mDragDistance / 2
: mDragDistance / 2).
setInterpolator(new DecelerateInterpolator()).withEndAction(
new Runnable() {
@Override
public void run() {
view.animate().translationX(0).
setInterpolator(new AccelerateInterpolator());
}
});
}
case MotionEvent.ACTION_CANCEL:
view.setPressed(false);
if (!mSkipCancelAnimation) {
view.animate().translationX(0)
.setInterpolator(new AccelerateInterpolator(2f));
}
break;
}
return true;
}
};
public interface AffordanceListener {
/**
* Called when the view would like to report user activity.
*
* @param when The timestamp of the user activity in {@link SystemClock#uptimeMillis} time
* base.
*/
void onUserActivity(long when);
/**
* Called when the action of the affordance has been performed.
*/
void onActionPerformed(SwipeAffordanceView view);
}
}