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