1ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/* 2ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Copyright (C) 2016 The Android Open Source Project 3ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 4ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License"); 5ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * you may not use this file except in compliance with the License. 6ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * You may obtain a copy of the License at 7ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 8ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * http://www.apache.org/licenses/LICENSE-2.0 9ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 10ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Unless required by applicable law or agreed to in writing, software 11ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS, 12ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * See the License for the specific language governing permissions and 14ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * limitations under the License 15ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 16ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 17ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpackage com.android.incallui.answer.impl.affordance; 18ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 19ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.Animator; 20ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.AnimatorListenerAdapter; 21ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.animation.ValueAnimator; 22ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.content.Context; 23ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.support.annotation.Nullable; 24ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.MotionEvent; 25ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.VelocityTracker; 26ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.View; 27ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport android.view.ViewConfiguration; 28ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.incallui.answer.impl.utils.FlingAnimationUtils; 29ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianimport com.android.incallui.answer.impl.utils.Interpolators; 30ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 31ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian/** A touch handler of the swipe buttons */ 32ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanianpublic class SwipeButtonHelper { 33ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 34ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.87f; 35ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public static final long HINT_PHASE1_DURATION = 200; 36ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static final long HINT_PHASE2_DURATION = 350; 37ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f; 38ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private static final int HINT_CIRCLE_OPEN_DURATION = 500; 39ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 40ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private final Context context; 41ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private final Callback callback; 42ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 43ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private FlingAnimationUtils flingAnimationUtils; 44ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private VelocityTracker velocityTracker; 45ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean swipingInProgress; 46ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float initialTouchX; 47ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float initialTouchY; 48ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float translation; 49ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float translationOnDown; 50ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int touchSlop; 51ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int minTranslationAmount; 52ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int minFlingVelocity; 53ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int hintGrowAmount; 54ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable private SwipeButtonView leftIcon; 55ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable private SwipeButtonView rightIcon; 56ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private Animator swipeAnimator; 57ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int minBackgroundRadius; 58ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean motionCancelled; 59ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int touchTargetSize; 60ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private View targetedView; 61ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean touchSlopExeeded; 62ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private AnimatorListenerAdapter flingEndListener = 63ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian new AnimatorListenerAdapter() { 64ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 65ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onAnimationEnd(Animator animation) { 66ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator = null; 67ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipingInProgress = false; 68ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetedView = null; 69ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 70ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian }; 719050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian 729050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian private class AnimationEndRunnable implements Runnable { 739050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian private final boolean rightPage; 749050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian 759050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian public AnimationEndRunnable(boolean rightPage) { 769050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian this.rightPage = rightPage; 779050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian } 789050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian 799050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian @Override 809050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian public void run() { 819050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian callback.onAnimationToSideEnded(rightPage); 829050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian } 839050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian }; 84ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 85ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public SwipeButtonHelper(Callback callback, Context context) { 86ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian this.context = context; 87ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian this.callback = callback; 88ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian init(); 89ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 90ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 91ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void init() { 92ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initIcons(); 93ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon( 94ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian leftIcon, 95ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 0.0f, 96ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian leftIcon != null ? leftIcon.getRestingAlpha() : 0, 97ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 98ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 99ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian true, 100ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false); 101ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon( 102ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian rightIcon, 103ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 0.0f, 104ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian rightIcon != null ? rightIcon.getRestingAlpha() : 0, 105ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 106ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 107ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian true, 108ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false); 109ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initDimens(); 110ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 111ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 112ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void initDimens() { 113ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian final ViewConfiguration configuration = ViewConfiguration.get(context); 114ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian touchSlop = configuration.getScaledPagingTouchSlop(); 115ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian minFlingVelocity = configuration.getScaledMinimumFlingVelocity(); 116ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian minTranslationAmount = 117ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian context.getResources().getDimensionPixelSize(R.dimen.answer_min_swipe_amount); 118ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian minBackgroundRadius = 119ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian context 120ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian .getResources() 121ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian .getDimensionPixelSize(R.dimen.answer_affordance_min_background_radius); 122ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian touchTargetSize = 123ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian context.getResources().getDimensionPixelSize(R.dimen.answer_affordance_touch_target_size); 124ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian hintGrowAmount = 125ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian context.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways); 126ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian flingAnimationUtils = new FlingAnimationUtils(context, 0.4f); 127ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 128ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 129ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void initIcons() { 130ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian leftIcon = callback.getLeftIcon(); 131ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian rightIcon = callback.getRightIcon(); 132ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updatePreviews(); 133ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 134ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 135ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void updatePreviews() { 136ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (leftIcon != null) { 137ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian leftIcon.setPreviewView(callback.getLeftPreview()); 138ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 139ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (rightIcon != null) { 140ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian rightIcon.setPreviewView(callback.getRightPreview()); 141ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 142ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 143ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 144ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public boolean onTouchEvent(MotionEvent event) { 145ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian int action = event.getActionMasked(); 146ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (motionCancelled && action != MotionEvent.ACTION_DOWN) { 147ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return false; 148ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 149ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian final float y = event.getY(); 150ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian final float x = event.getX(); 151ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 152ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean isUp = false; 153ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian switch (action) { 154ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian case MotionEvent.ACTION_DOWN: 155ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian View targetView = getIconAtPosition(x, y); 156ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetView == null || (targetedView != null && targetedView != targetView)) { 157ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian motionCancelled = true; 158ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return false; 159ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 160ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetedView != null) { 161ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian cancelAnimation(); 162ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 163ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian touchSlopExeeded = false; 164ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 165ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian startSwiping(targetView); 166ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initialTouchX = x; 167ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initialTouchY = y; 168ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian translationOnDown = translation; 169ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initVelocityTracker(); 170ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian trackMovement(event); 171ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian motionCancelled = false; 172ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian break; 173ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian case MotionEvent.ACTION_POINTER_DOWN: 174ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian motionCancelled = true; 175ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian endMotion(true /* forceSnapBack */, x, y); 176ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian break; 177ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian case MotionEvent.ACTION_MOVE: 178ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian trackMovement(event); 179ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float xDist = x - initialTouchX; 180ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float yDist = y - initialTouchY; 181ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float distance = (float) Math.hypot(xDist, yDist); 182ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (!touchSlopExeeded && distance > touchSlop) { 183ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian touchSlopExeeded = true; 184ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 185ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (swipingInProgress) { 186ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetedView == rightIcon) { 187ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian distance = translationOnDown - distance; 188ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian distance = Math.min(0, distance); 189ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 190ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian distance = translationOnDown + distance; 191ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian distance = Math.max(0, distance); 192ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 193ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian setTranslation(distance, false /* isReset */, false /* animateReset */); 194ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 195ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian break; 196ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 197ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian case MotionEvent.ACTION_UP: 198ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian isUp = true; 199d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian // fall through 200ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian case MotionEvent.ACTION_CANCEL: 201ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean hintOnTheRight = targetedView == rightIcon; 202ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian trackMovement(event); 203ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian endMotion(!isUp, x, y); 204ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (!touchSlopExeeded && isUp) { 205ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian callback.onIconClicked(hintOnTheRight); 206ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 207ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian break; 208ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 209ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return true; 210ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 211ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 212ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void startSwiping(View targetView) { 213ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian callback.onSwipingStarted(targetView == rightIcon); 214ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipingInProgress = true; 215ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetedView = targetView; 216ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 217ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 218ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private View getIconAtPosition(float x, float y) { 219ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (leftSwipePossible() && isOnIcon(leftIcon, x, y)) { 220ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return leftIcon; 221ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 222ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (rightSwipePossible() && isOnIcon(rightIcon, x, y)) { 223ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return rightIcon; 224ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 225ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return null; 226ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 227ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 228ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public boolean isOnAffordanceIcon(float x, float y) { 229ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return isOnIcon(leftIcon, x, y) || isOnIcon(rightIcon, x, y); 230ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 231ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 232ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean isOnIcon(View icon, float x, float y) { 233ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float iconX = icon.getX() + icon.getWidth() / 2.0f; 234ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float iconY = icon.getY() + icon.getHeight() / 2.0f; 235ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian double distance = Math.hypot(x - iconX, y - iconY); 236ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return distance <= touchTargetSize / 2; 237ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 238ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 239ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void endMotion(boolean forceSnapBack, float lastX, float lastY) { 240ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (swipingInProgress) { 241ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian flingWithCurrentVelocity(forceSnapBack, lastX, lastY); 242ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 243ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetedView = null; 244ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 245ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (velocityTracker != null) { 246ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian velocityTracker.recycle(); 247ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian velocityTracker = null; 248ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 249ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 250ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 251ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean rightSwipePossible() { 252ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return rightIcon != null && rightIcon.getVisibility() == View.VISIBLE; 253ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 254ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 255ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean leftSwipePossible() { 256ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return leftIcon != null && leftIcon.getVisibility() == View.VISIBLE; 257ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 258ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 259ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void startHintAnimation(boolean right, @Nullable Runnable onFinishedListener) { 260ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian cancelAnimation(); 261ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian startHintAnimationPhase1(right, onFinishedListener); 262ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 263ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 264ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void startHintAnimationPhase1( 265ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian final boolean right, @Nullable final Runnable onFinishedListener) { 266ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian final SwipeButtonView targetView = right ? rightIcon : leftIcon; 267ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian ValueAnimator animator = getAnimatorToRadius(right, hintGrowAmount); 268ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (animator == null) { 269ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (onFinishedListener != null) { 270ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian onFinishedListener.run(); 271ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 272ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return; 273ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 274ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.addListener( 275ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian new AnimatorListenerAdapter() { 276ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean mCancelled; 277ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 278ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 279ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onAnimationCancel(Animator animation) { 280ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian mCancelled = true; 281ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 282ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 283ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 284ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onAnimationEnd(Animator animation) { 285ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (mCancelled) { 286ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator = null; 287ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetedView = null; 288ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (onFinishedListener != null) { 289ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian onFinishedListener.run(); 290ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 291ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 292ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian startUnlockHintAnimationPhase2(right, onFinishedListener); 293ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 294ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 295ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian }); 296ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 297ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.setDuration(HINT_PHASE1_DURATION); 298ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.start(); 299ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator = animator; 300ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetedView = targetView; 301ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 302ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 303ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** Phase 2: Move back. */ 304ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void startUnlockHintAnimationPhase2( 305ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean right, @Nullable final Runnable onFinishedListener) { 306ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian ValueAnimator animator = getAnimatorToRadius(right, 0); 307ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (animator == null) { 308ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (onFinishedListener != null) { 309ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian onFinishedListener.run(); 310ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 311ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return; 312ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 313ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.addListener( 314ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian new AnimatorListenerAdapter() { 315ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 316ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onAnimationEnd(Animator animation) { 317ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator = null; 318ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetedView = null; 319ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (onFinishedListener != null) { 320ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian onFinishedListener.run(); 321ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 322ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 323ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian }); 324ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); 325ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.setDuration(HINT_PHASE2_DURATION); 326ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION); 327ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.start(); 328ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator = animator; 329ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 330ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 331ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private ValueAnimator getAnimatorToRadius(final boolean right, int radius) { 332ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian final SwipeButtonView targetView = right ? rightIcon : leftIcon; 333ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetView == null) { 334ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return null; 335ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 336ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius); 337ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.addUpdateListener( 338ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian new ValueAnimator.AnimatorUpdateListener() { 339ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 340ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onAnimationUpdate(ValueAnimator animation) { 341ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float newRadius = (float) animation.getAnimatedValue(); 342ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetView.setCircleRadiusWithoutAnimation(newRadius); 343ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float translation = getTranslationFromRadius(newRadius); 344ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonHelper.this.translation = right ? -translation : translation; 345ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIconsFromTranslation(targetView); 346ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 347ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian }); 348ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return animator; 349ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 350ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 351ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void cancelAnimation() { 352ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (swipeAnimator != null) { 353ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator.cancel(); 354ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 355ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 356ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 357ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) { 358ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float vel = getCurrentVelocity(lastX, lastY); 359ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 360ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // We snap back if the current translation is not far enough 361ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean snapBack = isBelowFalsingThreshold(); 362ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 363ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // or if the velocity is in the opposite direction. 364ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean velIsInWrongDirection = vel * translation < 0; 365ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian snapBack |= Math.abs(vel) > minFlingVelocity && velIsInWrongDirection; 366ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian vel = snapBack ^ velIsInWrongDirection ? 0 : vel; 367ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian fling(vel, snapBack || forceSnapBack, translation < 0); 368ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 369ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 370ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private boolean isBelowFalsingThreshold() { 371ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return Math.abs(translation) < Math.abs(translationOnDown) + getMinTranslationAmount(); 372ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 373ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 374ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private int getMinTranslationAmount() { 375ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float factor = callback.getAffordanceFalsingFactor(); 376ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return (int) (minTranslationAmount * factor); 377ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 378ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 379ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void fling(float vel, final boolean snapBack, boolean right) { 380ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float target = 381ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian right ? -callback.getMaxTranslationDistance() : callback.getMaxTranslationDistance(); 382ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian target = snapBack ? 0 : target; 383ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 384ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian ValueAnimator animator = ValueAnimator.ofFloat(translation, target); 385ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian flingAnimationUtils.apply(animator, translation, target, vel); 386ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.addUpdateListener( 387ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian new ValueAnimator.AnimatorUpdateListener() { 388ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Override 389ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onAnimationUpdate(ValueAnimator animation) { 390ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian translation = (float) animation.getAnimatedValue(); 391ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 392ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian }); 393ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.addListener(flingEndListener); 394ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (!snapBack) { 3959050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian startFinishingCircleAnimation(vel * 0.375f, new AnimationEndRunnable(right), right); 396ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian callback.onAnimationToSideStarted(right, translation, vel); 397ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 398ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian reset(true); 399ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 400ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animator.start(); 401ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipeAnimator = animator; 402ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (snapBack) { 403ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian callback.onSwipingAborted(); 404ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 405ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 406ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 407ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void startFinishingCircleAnimation( 408ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float velocity, Runnable mAnimationEndRunnable, boolean right) { 409ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView targetView = right ? rightIcon : leftIcon; 410ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetView != null) { 411ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetView.finishAnimation(velocity, mAnimationEndRunnable); 412ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 413ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 414ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 415ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void setTranslation(float translation, boolean isReset, boolean animateReset) { 416ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian translation = rightSwipePossible() ? translation : Math.max(0, translation); 417ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian translation = leftSwipePossible() ? translation : Math.min(0, translation); 418ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float absTranslation = Math.abs(translation); 419ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (translation != this.translation || isReset) { 420ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView targetView = translation > 0 ? leftIcon : rightIcon; 421ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView otherView = translation > 0 ? rightIcon : leftIcon; 422ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float alpha = absTranslation / getMinTranslationAmount(); 423ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 424ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // We interpolate the alpha of the other icons to 0 425ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float fadeOutAlpha = 1.0f - alpha; 426ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f); 427ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 428ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean animateIcons = isReset && animateReset; 429ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean forceNoCircleAnimation = isReset && !animateReset; 430ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float radius = getRadiusFromTranslation(absTranslation); 431ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean slowAnimation = isReset && isBelowFalsingThreshold(); 432ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetView != null) { 433ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (!isReset) { 434ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon( 435ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetView, 436ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian radius, 437ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian alpha + fadeOutAlpha * targetView.getRestingAlpha(), 438ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 439ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 440ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 441ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false); 442ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 443ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon( 444ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetView, 445ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 0.0f, 446ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian fadeOutAlpha * targetView.getRestingAlpha(), 447ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animateIcons, 448ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian slowAnimation, 449ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 450ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian forceNoCircleAnimation); 451ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 452ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 453ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (otherView != null) { 454ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon( 455ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian otherView, 456ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 0.0f, 457ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian fadeOutAlpha * otherView.getRestingAlpha(), 458ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian animateIcons, 459ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian slowAnimation, 460ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian false, 461ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian forceNoCircleAnimation); 462ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 463ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 464ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian this.translation = translation; 465ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 466ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 467ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 468ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void updateIconsFromTranslation(SwipeButtonView targetView) { 469ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float absTranslation = Math.abs(translation); 470ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float alpha = absTranslation / getMinTranslationAmount(); 471ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 472ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // We interpolate the alpha of the other icons to 0 473ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float fadeOutAlpha = 1.0f - alpha; 474ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian fadeOutAlpha = Math.max(0.0f, fadeOutAlpha); 475ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 476ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // We interpolate the alpha of the targetView to 1 477ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView otherView = targetView == rightIcon ? leftIcon : rightIcon; 478ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false); 479ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (otherView != null) { 480ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false); 481ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 482ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 483ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 484ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float getTranslationFromRadius(float circleSize) { 485ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float translation = (circleSize - minBackgroundRadius) / BACKGROUND_RADIUS_SCALE_FACTOR; 486ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return translation > 0.0f ? translation + touchSlop : 0.0f; 487ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 488ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 489ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float getRadiusFromTranslation(float translation) { 490ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (translation <= touchSlop) { 491ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return 0.0f; 492ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 493ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return (translation - touchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + minBackgroundRadius; 494ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 495ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 496ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void animateHideLeftRightIcon() { 497ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian cancelAnimation(); 498ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon(rightIcon, 0f, 0f, true, false, false, false); 499ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon(leftIcon, 0f, 0f, true, false, false, false); 500ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 501ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 502ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void updateIcon( 503ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable SwipeButtonView view, 504ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float circleRadius, 505ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float alpha, 506ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean animate, 507ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean slowRadiusAnimation, 508ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean force, 509ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian boolean forceNoCircleAnimation) { 510ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (view == null) { 511ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return; 512ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 513ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (view.getVisibility() != View.VISIBLE && !force) { 514ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return; 515ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 516ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (forceNoCircleAnimation) { 517ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian view.setCircleRadiusWithoutAnimation(circleRadius); 518ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 519ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian view.setCircleRadius(circleRadius, slowRadiusAnimation); 520ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 521ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIconAlpha(view, alpha, animate); 522ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 523ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 524ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void updateIconAlpha(SwipeButtonView view, float alpha, boolean animate) { 525ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float scale = getScale(alpha, view); 526ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian alpha = Math.min(1.0f, alpha); 527ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian view.setImageAlpha(alpha, animate); 528ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian view.setImageScale(scale, animate); 529ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 530ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 531ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float getScale(float alpha, SwipeButtonView icon) { 532ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float scale = alpha / icon.getRestingAlpha() * 0.2f + SwipeButtonView.MIN_ICON_SCALE_AMOUNT; 533ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return Math.min(scale, SwipeButtonView.MAX_ICON_SCALE_AMOUNT); 534ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 535ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 536ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void trackMovement(MotionEvent event) { 537ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (velocityTracker != null) { 538ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian velocityTracker.addMovement(event); 539ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 540ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 541ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 542ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private void initVelocityTracker() { 543ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (velocityTracker != null) { 544ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian velocityTracker.recycle(); 545ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 546ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian velocityTracker = VelocityTracker.obtain(); 547ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 548ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 549ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian private float getCurrentVelocity(float lastX, float lastY) { 550ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (velocityTracker == null) { 551ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return 0; 552ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 553ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian velocityTracker.computeCurrentVelocity(1000); 554ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float aX = velocityTracker.getXVelocity(); 555ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float aY = velocityTracker.getYVelocity(); 556ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float bX = lastX - initialTouchX; 557ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float bY = lastY - initialTouchY; 558ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float bLen = (float) Math.hypot(bX, bY); 559ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // Project the velocity onto the distance vector: a * b / |b| 560ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float projectedVelocity = (aX * bX + aY * bY) / bLen; 561ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (targetedView == rightIcon) { 562ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian projectedVelocity = -projectedVelocity; 563ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 564ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return projectedVelocity; 565ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 566ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 567ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onConfigurationChanged() { 568ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initDimens(); 569ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initIcons(); 570ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 571ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 572ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void onRtlPropertiesChanged() { 573ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian initIcons(); 574ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 575ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 576ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void reset(boolean animate) { 577ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian cancelAnimation(); 578ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian setTranslation(0.0f, true, animate); 579ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian motionCancelled = true; 580ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (swipingInProgress) { 581ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian callback.onSwipingAborted(); 582ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian swipingInProgress = false; 583ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 584ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 585ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 586ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public boolean isSwipingInProgress() { 587ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return swipingInProgress; 588ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 589ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 590ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public void launchAffordance(boolean animate, boolean left) { 591ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView targetView = left ? leftIcon : rightIcon; 592ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (swipingInProgress || targetView == null) { 593ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian // We don't want to mess with the state if the user is actually swiping already. 594ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian return; 595ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 596ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView otherView = left ? rightIcon : leftIcon; 597ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian startSwiping(targetView); 598ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian if (animate) { 599ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian fling(0, false, !left); 600ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon(otherView, 0.0f, 0, true, false, true, false); 601ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } else { 602ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian callback.onAnimationToSideStarted(!left, translation, 0); 603ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian translation = 604ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian left ? callback.getMaxTranslationDistance() : callback.getMaxTranslationDistance(); 605ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian updateIcon(otherView, 0.0f, 0.0f, false, false, true, false); 606ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian targetView.instantFinishAnimation(); 607ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian flingEndListener.onAnimationEnd(null); 6089050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian new AnimationEndRunnable(!left).run(); 609ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 610ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 611ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 612ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** Callback interface for various actions */ 613ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian public interface Callback { 614ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 615ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** 616ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * Notifies the callback when an animation to a side page was started. 617ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * 618ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian * @param rightPage Is the page animated to the right page? 619ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian */ 620ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian void onAnimationToSideStarted(boolean rightPage, float translation, float vel); 621ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 622ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** Notifies the callback the animation to a side page has ended. */ 6239050823ccf6f512e06ad65c8a741cb17cbc4a833Eric Erfanian void onAnimationToSideEnded(boolean rightPage); 624ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 625ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float getMaxTranslationDistance(); 626ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 627ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian void onSwipingStarted(boolean rightIcon); 628ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 629ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian void onSwipingAborted(); 630ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 631ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian void onIconClicked(boolean rightIcon); 632ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 633ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable 634ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView getLeftIcon(); 635ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 636ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable 637ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian SwipeButtonView getRightIcon(); 638ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 639ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable 640ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian View getLeftPreview(); 641ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 642ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian @Nullable 643ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian View getRightPreview(); 644ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian 645ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian /** @return The factor the minimum swipe amount should be multiplied with. */ 646ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian float getAffordanceFalsingFactor(); 647ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian } 648ccca31529c07970e89419fb85a9e8153a5396838Eric Erfanian} 649