1 // Copyright 2018 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.content.browser.selection; 6 7 import android.animation.ValueAnimator; 8 import android.view.animation.LinearInterpolator; 9 10 import org.chromium.base.Log; 11 12 /** 13 * MagnifierAnimator adds animation to MagnifierWrapper when there is a change in y direction. 14 * MagnifierWrapper class isolated P APIs out so we could write test for MagnifierAnimator. 15 */ 16 public class MagnifierAnimator implements SelectionInsertionHandleObserver { 17 private static final boolean DEBUG = false; 18 private static final String TAG = "Magnifier"; 19 20 private static final long DURATION_MS = 100; 21 22 private MagnifierWrapper mMagnifier; 23 private ValueAnimator mAnimator; 24 25 private boolean mMagnifierIsShowing; 26 27 // Current point for showing magnifier. 28 private float mAnimationCurrentX; 29 private float mAnimationCurrentY; 30 31 // Start point for animation. 32 private float mAnimationStartX; 33 private float mAnimationStartY; 34 35 // Target point for end of animation. 36 private float mTargetX; 37 private float mTargetY; 38 39 /** 40 * Constructor. 41 */ MagnifierAnimator(MagnifierWrapper magnifier)42 public MagnifierAnimator(MagnifierWrapper magnifier) { 43 mMagnifier = magnifier; 44 45 createValueAnimator(); 46 47 mTargetX = -1.0f; 48 mTargetY = -1.0f; 49 } 50 51 @Override handleDragStartedOrMoved(float x, float y)52 public void handleDragStartedOrMoved(float x, float y) { 53 if (!mMagnifier.isAvailable()) return; 54 if (DEBUG) { 55 Log.i(TAG, 56 "handleDragStartedOrMoved: " 57 + "(" + x + ", " + y + ")"); 58 } 59 // We only do animation if this is not the first time to show magnifier and y coordinate 60 // is different from last target. 61 if (mMagnifierIsShowing && y != mTargetY) { 62 // If the animator is running, we cancel it and reset the starting point to current 63 // point, then start a new animation. Otherwise set the starting point to previous 64 // place. 65 if (mAnimator.isRunning()) { 66 mAnimator.cancel(); 67 // Create another ValueAnimator because the test uses 68 // ValueAnimator#setCurrentFraction() to adjust animation fraction, the internal 69 // fraction is not being reset in ValueAnimator#cancel(), so the next 70 // ValueAnimator#start() will use this fraction first, which will cause a jump. 71 createValueAnimator(); 72 mAnimationStartX = mAnimationCurrentX; 73 mAnimationStartY = mAnimationCurrentY; 74 } else { 75 mAnimationStartX = mTargetX; 76 mAnimationStartY = mTargetY; 77 } 78 mAnimator.start(); 79 } else { 80 if (!mAnimator.isRunning()) mMagnifier.show(x, y); 81 // if mAnimator is running, we simply change the target coordinate. x-coordinate won't 82 // change dramatically, so let the previous animation finish to the target point. 83 } 84 85 mTargetX = x; 86 mTargetY = y; 87 88 mMagnifierIsShowing = true; 89 } 90 91 @Override handleDragStopped()92 public void handleDragStopped() { 93 mMagnifier.dismiss(); 94 mAnimator.cancel(); 95 mMagnifierIsShowing = false; 96 } 97 getValueAnimatorForTesting()98 /* package */ ValueAnimator getValueAnimatorForTesting() { 99 return mAnimator; 100 } 101 currentValue(float start, float target, ValueAnimator animation)102 private float currentValue(float start, float target, ValueAnimator animation) { 103 return start + (target - start) * animation.getAnimatedFraction(); 104 } 105 createValueAnimator()106 private void createValueAnimator() { 107 mAnimator = ValueAnimator.ofFloat(0, 1); 108 mAnimator.setDuration(DURATION_MS); 109 mAnimator.setInterpolator(new LinearInterpolator()); 110 mAnimator.addUpdateListener(animation -> { 111 mAnimationCurrentX = currentValue(mAnimationStartX, mTargetX, animation); 112 mAnimationCurrentY = currentValue(mAnimationStartY, mTargetY, animation); 113 mMagnifier.show(mAnimationCurrentX, mAnimationCurrentY); 114 }); 115 } 116 } 117