Browse Source

SharedElement bounds and scale animator (#1096)

Animate image scale and bounds

This commit properly animates images with different aspect ratios.
By default, regular scale animator is used which scales up or down the entire ImageView.
If `animateClipBounds={true}` is specified on the **to** `SharedElementTransition`, instead of scaling the View, the actual Drawable bounds and scale is animated which prevents the image from appearing distorted during the transition.
Guy Carmeli 7 years ago
parent
commit
c44599732d

+ 1
- 1
android/app/build.gradle View File

@@ -47,7 +47,7 @@ android {
47 47
 dependencies {
48 48
     compile fileTree(dir: "libs", include: ["*.jar"])
49 49
 
50
-	  compile 'com.android.support:design:25.0.1'
50
+	compile 'com.android.support:design:25.0.1'
51 51
     compile "com.android.support:appcompat-v7:25.0.1"
52 52
 
53 53
     // node_modules

+ 2
- 2
android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java View File

@@ -168,11 +168,11 @@ public class ViewUtils {
168 168
     public static Point getLocationOnScreen(View view) {
169 169
         int[] xy = new int[2];
170 170
         view.getLocationOnScreen(xy);
171
-        xy[1] -= getStatusBarPixelHeight();
171
+        xy[1] -= getStatusBarHeight();
172 172
         return new Point(xy[0], xy[1]);
173 173
     }
174 174
 
175
-    private static int getStatusBarPixelHeight() {
175
+    private static int getStatusBarHeight() {
176 176
         if (statusBarHeight > 0) {
177 177
             return statusBarHeight;
178 178
         }

+ 23
- 23
android/app/src/main/java/com/reactnativenavigation/views/sharedElementTransition/AnimatorValuesResolver.java View File

@@ -3,13 +3,17 @@ package com.reactnativenavigation.views.sharedElementTransition;
3 3
 import android.graphics.Rect;
4 4
 import android.widget.TextView;
5 5
 
6
+import com.facebook.drawee.drawable.ScalingUtils;
7
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
8
+import com.facebook.drawee.view.DraweeView;
9
+import com.facebook.react.views.image.ReactImageView;
6 10
 import com.reactnativenavigation.params.InterpolationParams;
7 11
 import com.reactnativenavigation.params.PathInterpolationParams;
8 12
 import com.reactnativenavigation.params.parsers.SharedElementTransitionParams;
9 13
 import com.reactnativenavigation.utils.ViewUtils;
10 14
 import com.reactnativenavigation.views.utils.Point;
11 15
 
12
-class AnimatorValuesResolver {
16
+public class AnimatorValuesResolver {
13 17
 
14 18
     final Point fromXy;
15 19
     final Point toXy;
@@ -31,44 +35,41 @@ class AnimatorValuesResolver {
31 35
     int endColor;
32 36
     Rect startDrawingRect = new Rect();
33 37
     Rect endDrawingRect = new Rect();
38
+    final Rect fromBounds;
39
+    final Rect toBounds;
40
+    final ScalingUtils.ScaleType fromScaleType;
41
+    final ScalingUtils.ScaleType toScaleType;
34 42
 
35 43
     AnimatorValuesResolver(SharedElementTransition from, SharedElementTransition to, SharedElementTransitionParams params) {
36
-        fromXy = calculateFromXY(from, to, params);
37
-        toXy = calculateToXY(to, from, params);
44
+        fromXy = ViewUtils.getLocationOnScreen(from.getSharedView());
45
+        toXy = ViewUtils.getLocationOnScreen(to.getSharedView());
38 46
         startScaleX = calculateStartScaleX(from, to);
39 47
         endScaleX = calculateEndScaleX(from, to);
40 48
         startScaleY = calculateStartScaleY(from, to);
41 49
         endScaleY = calculateEndScaleY(from, to);
42 50
         calculateColor(from, to);
43 51
         calculate(params.interpolation);
52
+        fromBounds = calculateBounds(from);
53
+        toBounds = calculateBounds(to);
54
+        fromScaleType = getScaleType(from);
55
+        toScaleType = getScaleType(to);
44 56
         calculateDrawingReacts(from, to);
45 57
     }
46 58
 
47
-    private Point calculateFromXY(SharedElementTransition from, SharedElementTransition to, SharedElementTransitionParams params) {
48
-        Point loc = ViewUtils.getLocationOnScreen(from.getSharedView());
49
-        if (params.animateClipBounds) {
50
-            if (from.getHeight() != to.getHeight()) {
51
-                if (from.getHeight() < to.getHeight()) {
52
-                    loc.y -= (to.getHeight() - from.getHeight()) / 2;
53
-                }
54
-            }
59
+    private ScalingUtils.ScaleType getScaleType(SharedElementTransition view) {
60
+        if (view.getSharedView() instanceof ReactImageView) {
61
+            return ((DraweeView<GenericDraweeHierarchy>) view.getSharedView()).getHierarchy().getActualImageScaleType();
55 62
         }
56
-        return loc;
63
+        return null;
57 64
     }
58 65
 
59
-    private Point calculateToXY(SharedElementTransition to, SharedElementTransition from, SharedElementTransitionParams params) {
60
-        Point loc = ViewUtils.getLocationOnScreen(to.getSharedView());
61
-        if (params.animateClipBounds) {
62
-            if (from.getHeight() != to.getHeight()) {
63
-                if (from.getHeight() > to.getHeight()) {
64
-                    loc.y -= (from.getHeight() - to.getHeight()) / 2;
65
-                }
66
-            }
66
+    private Rect calculateBounds(SharedElementTransition view) {
67
+        if (view.getSharedView() instanceof ReactImageView) {
68
+            return new Rect(0, 0, view.getSharedView().getWidth(), view.getSharedView().getHeight());
67 69
         }
68
-        return loc;
70
+        return null;
69 71
     }
70 72
 
71
-
72 73
     protected float calculateEndScaleY(SharedElementTransition from, SharedElementTransition to) {
73 74
         return 1;
74 75
     }
@@ -122,7 +123,6 @@ class AnimatorValuesResolver {
122 123
             endColor = ViewUtils.getForegroundColorSpans((TextView) to.getSharedView())[0].getForegroundColor();
123 124
         }
124 125
     }
125
-
126 126
     private void calculateDrawingReacts(SharedElementTransition from, SharedElementTransition to) {
127 127
         from.getDrawingRect(startDrawingRect);
128 128
         to.getDrawingRect(endDrawingRect);

+ 32
- 22
android/app/src/main/java/com/reactnativenavigation/views/sharedElementTransition/SharedElementAnimatorCreator.java View File

@@ -5,6 +5,9 @@ import android.animation.ObjectAnimator;
5 5
 import android.support.annotation.NonNull;
6 6
 import android.view.View;
7 7
 
8
+import com.facebook.drawee.drawable.ScalingUtils;
9
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
10
+import com.facebook.drawee.view.DraweeView;
8 11
 import com.reactnativenavigation.params.InterpolationParams;
9 12
 import com.reactnativenavigation.params.PathInterpolationParams;
10 13
 import com.reactnativenavigation.params.parsers.SharedElementTransitionParams;
@@ -49,54 +52,47 @@ class SharedElementAnimatorCreator {
49 52
                 result.add(createYAnimator(resolver, params));
50 53
             }
51 54
         }
52
-        if (shouldCreateScaleXAnimator(resolver, params)) {
55
+        if (shouldAddScaleXAnimator(resolver, params)) {
53 56
             result.add(createScaleXAnimator(resolver, params));
54 57
         }
55
-        if (shouldCreateScaleYAnimator(resolver, params)) {
58
+        if (shouldAddScaleYAnimator(resolver, params)) {
56 59
             result.add(createScaleYAnimator(resolver, params));
57 60
         }
58
-        if (shouldCreateColorAnimator(resolver)) {
61
+        if (shouldAddColorAnimator(resolver)) {
59 62
             result.add(createColorAnimator(resolver, params.duration));
60 63
         }
61
-        if (shouldCreateImageClipBoundsAnimator(params)) {
64
+        if (shouldAddImageClipBoundsAnimator(params)) {
62 65
             result.add(createImageClipBoundsAnimator(resolver, params.duration));
66
+            result.add(createImageTransformAnimator(resolver, params));
63 67
         }
64 68
         return result;
65 69
     }
66 70
 
67
-    private boolean shouldCreateScaleYAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
71
+    private boolean shouldAddScaleYAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
68 72
         return resolver.startScaleY != resolver.endScaleY && !params.animateClipBounds;
69 73
     }
70 74
 
71
-    private boolean shouldCreateScaleXAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
75
+    private boolean shouldAddScaleXAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
72 76
         return resolver.startScaleX != resolver.endScaleX && !params.animateClipBounds;
73 77
     }
74 78
 
75 79
     private boolean shouldAddLinearMotionXAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
76
-        if (params.animateClipBounds) {
77
-            return to.getWidth() - from.getWidth() != resolver.dx * 2;
78
-        } else {
79
-            return resolver.dx != 0;
80
-        }
80
+        return params.animateClipBounds || resolver.dx != 0;
81 81
     }
82 82
 
83 83
     private boolean shouldAddLinearMotionYAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
84
-        if (params.animateClipBounds) {
85
-            return to.getHeight() - from.getHeight() != resolver.dy * 2;
86
-        } else {
87
-            return resolver.dy != 0;
88
-        }
84
+        return params.animateClipBounds || resolver.dy != 0;
89 85
     }
90 86
 
91 87
     private boolean shouldAddCurvedMotionAnimator(AnimatorValuesResolver resolver, InterpolationParams interpolation) {
92 88
         return interpolation instanceof PathInterpolationParams && (resolver.dx != 0 || resolver.dy != 0);
93 89
     }
94 90
 
95
-    private boolean shouldCreateColorAnimator(AnimatorValuesResolver resolver) {
91
+    private boolean shouldAddColorAnimator(AnimatorValuesResolver resolver) {
96 92
         return resolver.startColor != resolver.endColor;
97 93
     }
98 94
 
99
-    private boolean shouldCreateImageClipBoundsAnimator(SharedElementTransitionParams params) {
95
+    private boolean shouldAddImageClipBoundsAnimator(SharedElementTransitionParams params) {
100 96
         return params.animateClipBounds;
101 97
     }
102 98
 
@@ -130,16 +126,18 @@ class SharedElementAnimatorCreator {
130 126
 
131 127
     private ObjectAnimator createScaleXAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
132 128
         to.getSharedView().setPivotX(0);
133
-        ObjectAnimator animator = ofFloat(to.getSharedView(), View.SCALE_X, resolver.startScaleX, resolver.endScaleX)
134
-                .setDuration(params.duration);
129
+        ObjectAnimator animator =
130
+                ofFloat(to.getSharedView(), View.SCALE_X, resolver.startScaleX, resolver.endScaleX)
131
+                        .setDuration(params.duration);
135 132
         animator.setInterpolator(params.interpolation.easing.getInterpolator());
136 133
         return animator;
137 134
     }
138 135
 
139 136
     private ObjectAnimator createScaleYAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
140 137
         to.getSharedView().setPivotY(0);
141
-        ObjectAnimator animator = ofFloat(to.getSharedView(), View.SCALE_Y, resolver.startScaleY, resolver.endScaleY)
142
-                .setDuration(params.duration);
138
+        ObjectAnimator animator =
139
+                ofFloat(to.getSharedView(), View.SCALE_Y, resolver.startScaleY, resolver.endScaleY)
140
+                        .setDuration(params.duration);
143 141
         animator.setInterpolator(params.interpolation.easing.getInterpolator());
144 142
         return animator;
145 143
     }
@@ -163,4 +161,16 @@ class SharedElementAnimatorCreator {
163 161
                 resolver.endDrawingRect)
164 162
                 .setDuration(duration);
165 163
     }
164
+    private Animator createImageTransformAnimator(AnimatorValuesResolver resolver, SharedElementTransitionParams params) {
165
+        ScalingUtils.InterpolatingScaleType ist = new ScalingUtils.InterpolatingScaleType(
166
+                resolver.fromScaleType,
167
+                resolver.toScaleType,
168
+                resolver.fromBounds,
169
+                resolver.toBounds
170
+        );
171
+        ((DraweeView<GenericDraweeHierarchy>) to.getSharedView()).getHierarchy().setActualImageScaleType(ist);
172
+        ObjectAnimator animator = ObjectAnimator.ofFloat(to, "matrixTransform", 0, 1).setDuration(params.duration);
173
+        animator.setInterpolator(params.interpolation.easing.getInterpolator());
174
+        return animator;
175
+    }
166 176
 }

+ 10
- 0
android/app/src/main/java/com/reactnativenavigation/views/sharedElementTransition/SharedElementTransition.java View File

@@ -13,6 +13,9 @@ import android.widget.FrameLayout;
13 13
 import android.widget.RelativeLayout;
14 14
 import android.widget.TextView;
15 15
 
16
+import com.facebook.drawee.drawable.ScalingUtils;
17
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
18
+import com.facebook.drawee.view.DraweeView;
16 19
 import com.facebook.react.views.image.ReactImageView;
17 20
 import com.reactnativenavigation.params.parsers.SharedElementParamsParser;
18 21
 import com.reactnativenavigation.params.parsers.SharedElementTransitionParams;
@@ -107,6 +110,13 @@ public class SharedElementTransition extends FrameLayout {
107 110
         }
108 111
     }
109 112
 
113
+    @Keep
114
+    public void setMatrixTransform(float value) {
115
+        GenericDraweeHierarchy hierarchy = ((DraweeView<GenericDraweeHierarchy>) child).getHierarchy();
116
+        ((ScalingUtils.InterpolatingScaleType) hierarchy.getActualImageScaleType()).setValue(value);
117
+        child.invalidate();
118
+    }
119
+
110 120
     public void attachChildToScreen() {
111 121
         ViewUtils.performOnParentScreen(this, new Task<Screen>() {
112 122
             @Override

+ 4
- 8
android/app/src/main/java/com/reactnativenavigation/views/utils/ClipBoundsEvaluator.java View File

@@ -18,11 +18,9 @@ public class ClipBoundsEvaluator implements TypeEvaluator<Rect> {
18 18
             result.bottom = toHeight;
19 19
         } else {
20 20
             if (toHeight > fromHeight) {
21
-                result.top = (int) (Math.abs(toHeight - fromHeight) / 2 * (1 - ratio));
22
-                result.bottom = toHeight- result.top;
21
+                result.bottom = (int) (toHeight - (toHeight - fromHeight) * (1 - ratio));
23 22
             } else {
24
-                result.top = (int) (Math.abs(toHeight - fromHeight) / 2 * ratio);
25
-                result.bottom = fromHeight - result.top;
23
+                result.bottom = (int) (toHeight + (fromHeight - toHeight) * (1 - ratio));
26 24
             }
27 25
         }
28 26
 
@@ -30,11 +28,9 @@ public class ClipBoundsEvaluator implements TypeEvaluator<Rect> {
30 28
             result.right = toWidth;
31 29
         } else {
32 30
             if (toWidth > fromWidth) {
33
-                result.left = (int) (Math.abs(toWidth - fromWidth) / 2 * (1 - ratio));
34
-                result.right = toWidth - result.left;
31
+                result.right = (int) (toWidth - (toWidth - fromWidth) * (1 - ratio));
35 32
             } else {
36
-                result.left = (int) (Math.abs(toWidth - fromWidth) / 2 * ratio);
37
-                result.right = fromWidth - result.left;
33
+                result.right = (int) (toWidth + (fromWidth - toWidth) * (1 - ratio));
38 34
             }
39 35
         }
40 36
         return result;