瀏覽代碼

[v2]Shared element transition android (#3632)

Implement Element transition on Android

This commit adds basic support for Element transition to Android.
Currently, only Shared Transition is supported and only when pushing screens.

There are a few caveats you need to be aware of if you intend to use
this feature in its current state:

1. Since elements need to be laid out before the transition can start, Set `waitForRender: true` when pushing the destination screen.
2. Fade animation has to be used when pushing screens with shared elements.

API

```js
options: {
  animations: {
    push: {
      waitForRender: true,
      content: {
        alpha: {
          from: 0,
          to: 1,
          duration: 250
        }
      }
    }
  },
  customTransition: {
    animations: [
    ]
  }
}
```
Guy Carmeli 6 年之前
父節點
當前提交
bd4e42fadd
No account linked to committer's email address
共有 53 個檔案被更改,包括 1595 行新增52 行删除
  1. 2
    1
      lib/android/app/build.gradle
  2. 22
    5
      lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java
  3. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java
  4. 50
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Transition.java
  5. 49
    0
      lib/android/app/src/main/java/com/reactnativenavigation/parse/Transitions.java
  6. 52
    0
      lib/android/app/src/main/java/com/reactnativenavigation/react/ElementViewManager.java
  7. 2
    2
      lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.java
  8. 18
    0
      lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java
  9. 34
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java
  10. 13
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ColorUtils.java
  11. 41
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/TextViewUtils.java
  12. 46
    0
      lib/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java
  13. 5
    0
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IReactView.java
  14. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java
  15. 16
    3
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java
  16. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java
  17. 2
    1
      lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java
  18. 8
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java
  19. 100
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/Element.java
  20. 42
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/ElementTransitionManager.java
  21. 52
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/TransitionAnimatorCreator.java
  22. 15
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/TransitionValidator.java
  23. 29
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/AntiRelativeSizeSpan.java
  24. 44
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/BackgroundColorAnimator.java
  25. 45
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ClipBoundsEvaluator.java
  26. 14
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/LabColorEvaluator.java
  27. 63
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/MatrixAnimator.java
  28. 63
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/PropertyAnimatorCreator.java
  29. 39
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ScaleXAnimator.java
  30. 39
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ScaleYAnimator.java
  31. 30
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/TextChangeAnimator.java
  32. 35
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/TextColorAnimator.java
  33. 33
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/TextSizeAnimator.java
  34. 40
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/XAnimator.java
  35. 40
    0
      lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/YAnimator.java
  36. 9
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleOverlay.java
  37. 10
    10
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java
  38. 9
    0
      lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestReactView.java
  39. 58
    0
      lib/android/app/src/test/java/com/reactnativenavigation/react/ElementViewManagerTest.java
  40. 100
    0
      lib/android/app/src/test/java/com/reactnativenavigation/views/element/ElementTransitionManagerTest.java
  41. 83
    0
      lib/android/app/src/test/java/com/reactnativenavigation/views/element/TransitionAnimatorCreatorTest.java
  42. 32
    0
      lib/android/app/src/test/java/com/reactnativenavigation/views/element/TransitionTestUtils.java
  43. 110
    0
      lib/android/app/src/test/java/com/reactnativenavigation/views/element/TransitionValidatorTest.java
  44. 12
    3
      lib/src/adapters/Element.tsx
  45. 8
    7
      package.json
  46. 1
    0
      playground/android/app/build.gradle
  47. 5
    1
      playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java
  48. 1
    0
      playground/android/build.gradle
  49. 3
    0
      playground/android/settings.gradle
  50. 1
    0
      playground/src/app.js
  51. 25
    5
      playground/src/screens/CustomTransitionDestination.js
  52. 34
    10
      playground/src/screens/CustomTransitionOrigin.js
  53. 2
    2
      playground/src/screens/OptionsScreen.js

+ 2
- 1
lib/android/app/build.gradle 查看文件

@@ -75,8 +75,9 @@ dependencies {
75 75
     implementation 'com.android.support:design:26.1.0'
76 76
     implementation 'com.android.support:appcompat-v7:26.1.0'
77 77
     implementation 'com.android.support:support-v4:26.1.0'
78
-    implementation 'com.github.wix-playground:ahbottomnavigation:2.4.5'
79 78
 
79
+    implementation 'com.github.wix-playground:ahbottomnavigation:2.4.5'
80
+    implementation 'com.github.wix-playground:reflow-animator:1.0.4'
80 81
     implementation 'com.github.clans:fab:1.6.4'
81 82
 
82 83
     //noinspection GradleDynamicVersion

+ 22
- 5
lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java 查看文件

@@ -6,24 +6,41 @@ import android.animation.AnimatorListenerAdapter;
6 6
 import android.animation.AnimatorSet;
7 7
 import android.content.Context;
8 8
 import android.view.View;
9
+import android.view.ViewGroup;
9 10
 
10 11
 import com.reactnativenavigation.parse.AnimationOptions;
11 12
 import com.reactnativenavigation.parse.NestedAnimationsOptions;
13
+import com.reactnativenavigation.parse.Transitions;
14
+import com.reactnativenavigation.views.element.Element;
15
+import com.reactnativenavigation.views.element.ElementTransitionManager;
16
+
17
+import java.util.Collections;
18
+import java.util.List;
19
+
20
+import static com.reactnativenavigation.utils.CollectionUtils.merge;
12 21
 
13 22
 @SuppressWarnings("ResourceType")
14 23
 public class NavigationAnimator extends BaseAnimator {
15 24
 
16
-    public NavigationAnimator(Context context) {
25
+    private final ElementTransitionManager transitionManager;
26
+
27
+    public NavigationAnimator(Context context, ElementTransitionManager transitionManager) {
17 28
         super(context);
29
+        this.transitionManager = transitionManager;
18 30
     }
19 31
 
20
-    public void push(View view, NestedAnimationsOptions push, Runnable onAnimationEnd) {
21
-        view.setVisibility(View.INVISIBLE);
22
-        AnimatorSet set = push.content.getAnimation(view, getDefaultPushAnimation(view));
32
+    public void push(ViewGroup view, NestedAnimationsOptions animation, Runnable onAnimationEnd) {
33
+        push(view, animation, new Transitions(), Collections.EMPTY_LIST, Collections.EMPTY_LIST, onAnimationEnd);
34
+    }
35
+
36
+    public void push(ViewGroup view, NestedAnimationsOptions animation, Transitions transitions, List<Element> fromElements, List<Element> toElements, Runnable onAnimationEnd) {
37
+        view.setAlpha(0);
38
+        AnimatorSet set = animation.content.getAnimation(view, getDefaultPushAnimation(view));
39
+        set.playTogether(merge(set.getChildAnimations(), transitionManager.createTransitions(transitions, fromElements, toElements)));
23 40
         set.addListener(new AnimatorListenerAdapter() {
24 41
             @Override
25 42
             public void onAnimationStart(Animator animation) {
26
-                view.setVisibility(View.VISIBLE);
43
+                view.setAlpha(1);
27 44
             }
28 45
 
29 46
             @Override

+ 5
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java 查看文件

@@ -28,6 +28,7 @@ public class Options {
28 28
         result.modal = ModalOptions.parse(json);
29 29
         result.statusBar = StatusBarOptions.parse(json.optJSONObject("statusBar"));
30 30
         result.layout = LayoutOptions.parse(json.optJSONObject("layout"));
31
+        result.transitions = Transitions.parse(json.optJSONObject("customTransition"));
31 32
 
32 33
         return result;
33 34
     }
@@ -44,6 +45,7 @@ public class Options {
44 45
     @NonNull public ModalOptions modal = new ModalOptions();
45 46
     @NonNull public StatusBarOptions statusBar = new StatusBarOptions();
46 47
     @NonNull public LayoutOptions layout = new LayoutOptions();
48
+    @NonNull public Transitions transitions = new Transitions();
47 49
 
48 50
     void setTopTabIndex(int i) {
49 51
         topTabOptions.tabIndex = i;
@@ -64,6 +66,7 @@ public class Options {
64 66
         result.modal.mergeWith(modal);
65 67
         result.statusBar.mergeWith(statusBar);
66 68
         result.layout.mergeWith(layout);
69
+        result.transitions.mergeWith(transitions);
67 70
         return result;
68 71
     }
69 72
 
@@ -81,6 +84,7 @@ public class Options {
81 84
         result.modal.mergeWith(other.modal);
82 85
         result.statusBar.mergeWith(other.statusBar);
83 86
         result.layout.mergeWith(other.layout);
87
+        result.transitions.mergeWith(transitions);
84 88
         return result;
85 89
     }
86 90
 
@@ -96,6 +100,7 @@ public class Options {
96 100
         modal.mergeWithDefault(defaultOptions.modal);
97 101
         statusBar.mergeWithDefault(defaultOptions.statusBar);
98 102
         layout.mergeWithDefault(defaultOptions.layout);
103
+        transitions.mergeWithDefault(defaultOptions.transitions);
99 104
         return this;
100 105
     }
101 106
 

+ 50
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/Transition.java 查看文件

@@ -0,0 +1,50 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import com.reactnativenavigation.parse.params.Fraction;
4
+import com.reactnativenavigation.parse.params.NullNumber;
5
+import com.reactnativenavigation.parse.params.NullText;
6
+import com.reactnativenavigation.parse.params.Number;
7
+import com.reactnativenavigation.parse.params.Text;
8
+import com.reactnativenavigation.parse.parsers.FractionParser;
9
+import com.reactnativenavigation.parse.parsers.TextParser;
10
+
11
+import org.json.JSONObject;
12
+
13
+public class Transition {
14
+    public Text fromId = new NullText();
15
+    public Text toId = new NullText();
16
+    public Number startDelay = new NullNumber();
17
+    public Number duration = new NullNumber();
18
+
19
+    public static Transition parse(JSONObject json) {
20
+        Transition transition = new Transition();
21
+        if (json == null) return transition;
22
+
23
+        transition.fromId = TextParser.parse(json, "fromId");
24
+        transition.toId = TextParser.parse(json, "toId");
25
+        Fraction startDelay = FractionParser.parse(json, "startDelay");
26
+        if (startDelay.hasValue()) {
27
+            transition.startDelay = new Number((int) (startDelay.get() * 1000));
28
+        }
29
+        Fraction duration = FractionParser.parse(json, "duration");
30
+        if (duration.hasValue()) {
31
+            transition.duration = new Number((int) (duration.get() * 1000));
32
+        }
33
+
34
+        return transition;
35
+    }
36
+
37
+    void mergeWith(Transition other) {
38
+        if (other.fromId.hasValue()) fromId = other.fromId;
39
+        if (other.toId.hasValue()) toId = other.toId;
40
+        if (other.startDelay.hasValue()) startDelay = other.startDelay;
41
+        if (other.duration.hasValue()) duration = other.duration;
42
+    }
43
+
44
+    void mergeWithDefault(Transition defaultOptions) {
45
+        if (!fromId.hasValue()) fromId = defaultOptions.fromId;
46
+        if (!toId.hasValue()) toId = defaultOptions.toId;
47
+        if (!startDelay.hasValue()) startDelay = defaultOptions.startDelay;
48
+        if (!duration.hasValue()) duration = defaultOptions.duration;
49
+    }
50
+}

+ 49
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/Transitions.java 查看文件

@@ -0,0 +1,49 @@
1
+package com.reactnativenavigation.parse;
2
+
3
+import android.support.annotation.RestrictTo;
4
+
5
+import org.json.JSONArray;
6
+import org.json.JSONObject;
7
+
8
+import java.util.ArrayList;
9
+import java.util.List;
10
+
11
+public class Transitions {
12
+    private List<Transition> transitions = new ArrayList<>();
13
+
14
+    public List<Transition> get() {
15
+        return transitions;
16
+    }
17
+
18
+    public Transitions() {
19
+
20
+    }
21
+
22
+    @RestrictTo(RestrictTo.Scope.TESTS)
23
+    public Transitions(List<Transition> transitions) {
24
+        this.transitions = transitions;
25
+    }
26
+
27
+    public static Transitions parse(JSONObject json) {
28
+        Transitions result = new Transitions();
29
+        if (json != null && json.has("animations")) {
30
+            JSONArray animations = json.optJSONArray("animations");
31
+            for (int i = 0; i < animations.length(); i++) {
32
+                result.transitions.add(Transition.parse(animations.optJSONObject(i)));
33
+            }
34
+        }
35
+        return result;
36
+    }
37
+
38
+    public boolean hasValue() {
39
+        return !transitions.isEmpty();
40
+    }
41
+
42
+    void mergeWith(final Transitions other) {
43
+        if (other.hasValue()) transitions = other.transitions;
44
+    }
45
+
46
+    void mergeWithDefault(Transitions defaultOptions) {
47
+        if (!hasValue()) transitions = defaultOptions.transitions;
48
+    }
49
+}

+ 52
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/ElementViewManager.java 查看文件

@@ -0,0 +1,52 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.uimanager.ThemedReactContext;
4
+import com.facebook.react.uimanager.ViewGroupManager;
5
+import com.facebook.react.uimanager.annotations.ReactProp;
6
+import com.reactnativenavigation.views.element.Element;
7
+
8
+import static com.reactnativenavigation.utils.UiUtils.runOnPreDrawOnce;
9
+import static com.reactnativenavigation.utils.ViewUtils.performOnParentReactView;
10
+
11
+public class ElementViewManager extends ViewGroupManager<Element> {
12
+
13
+    @Override
14
+    public String getName() {
15
+        return "RNNElement";
16
+    }
17
+
18
+    @Override
19
+    protected Element createViewInstance(ThemedReactContext reactContext) {
20
+        Element element = createView(reactContext);
21
+        register(element);
22
+        return element;
23
+    }
24
+
25
+    public Element createView(ThemedReactContext reactContext) {
26
+        return new Element(reactContext);
27
+    }
28
+
29
+    @Override
30
+    public void onDropViewInstance(Element element) {
31
+        super.onDropViewInstance(element);
32
+        unregister(element);
33
+    }
34
+
35
+    @ReactProp(name = "elementId")
36
+    public void setElementId(Element element, String id) {
37
+        element.setElementId(id);
38
+    }
39
+
40
+    @Override
41
+    public boolean needsCustomLayoutForChildren() {
42
+        return true;
43
+    }
44
+
45
+    private void register(Element element) {
46
+        runOnPreDrawOnce(element, () -> performOnParentReactView(element, (parent) -> parent.registerElement(element)));
47
+    }
48
+
49
+    private void unregister(Element element) {
50
+        performOnParentReactView(element, (parent) -> parent.unregisterElement(element));
51
+    }
52
+}

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationPackage.java 查看文件

@@ -25,6 +25,6 @@ public class NavigationPackage implements ReactPackage {
25 25
 
26 26
 	@Override
27 27
 	public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
28
-		return Collections.emptyList();
29
-	}
28
+        return Collections.singletonList(new ElementViewManager());
29
+    }
30 30
 }

+ 18
- 0
lib/android/app/src/main/java/com/reactnativenavigation/react/ReactView.java 查看文件

@@ -14,6 +14,10 @@ import com.facebook.react.uimanager.UIManagerModule;
14 14
 import com.facebook.react.uimanager.events.EventDispatcher;
15 15
 import com.reactnativenavigation.interfaces.ScrollEventListener;
16 16
 import com.reactnativenavigation.viewcontrollers.IReactView;
17
+import com.reactnativenavigation.views.element.Element;
18
+
19
+import java.util.ArrayList;
20
+import java.util.List;
17 21
 
18 22
 @SuppressLint("ViewConstructor")
19 23
 public class ReactView extends ReactRootView implements IReactView {
@@ -23,6 +27,7 @@ public class ReactView extends ReactRootView implements IReactView {
23 27
 	private final String componentName;
24 28
 	private boolean isAttachedToReactInstance = false;
25 29
     private final JSTouchDispatcher jsTouchDispatcher;
30
+    private ArrayList<Element> elements = new ArrayList<>();
26 31
 
27 32
     public ReactView(final Context context, ReactInstanceManager reactInstanceManager, String componentId, String componentName) {
28 33
 		super(context);
@@ -106,4 +111,17 @@ public class ReactView extends ReactRootView implements IReactView {
106 111
     public String getComponentName() {
107 112
         return componentName;
108 113
     }
114
+
115
+    public void registerElement(Element element) {
116
+        elements.add(element);
117
+    }
118
+
119
+    public void unregisterElement(Element element) {
120
+        elements.remove(element);
121
+    }
122
+
123
+    @Override
124
+    public List<Element> getElements() {
125
+        return elements;
126
+    }
109 127
 }

+ 34
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/CollectionUtils.java 查看文件

@@ -1,9 +1,43 @@
1 1
 package com.reactnativenavigation.utils;
2 2
 
3
+import java.util.ArrayList;
3 4
 import java.util.Collection;
5
+import java.util.HashMap;
6
+import java.util.List;
7
+import java.util.Map;
4 8
 
5 9
 public class CollectionUtils {
6 10
     public static boolean isNullOrEmpty(Collection collection) {
7 11
         return collection == null || collection.isEmpty();
8 12
     }
13
+
14
+    public interface Mapper<K, V> {
15
+        K map(V value);
16
+    }
17
+
18
+    public static <K, V> Map<K, V> map(Collection<V> elements, Mapper<K, V> mapper) {
19
+        Map<K, V> map = new HashMap<>();
20
+        for (V value : elements) {
21
+            map.put(mapper.map(value), value);
22
+        }
23
+        return map;
24
+    }
25
+
26
+    public interface Filter<T> {
27
+        boolean filter(T value);
28
+    }
29
+
30
+    public static <T> List<T> filter(Collection<T> list, Filter<T> filter) {
31
+        List<T> result = new ArrayList<>();
32
+        for (T t : list) {
33
+            if (filter.filter(t)) result.add(t);
34
+        }
35
+        return result;
36
+    }
37
+
38
+    public static <T> List<T> merge(Collection<T> a, Collection<T> b) {
39
+        List<T> result = new ArrayList<>(a);
40
+        result.addAll(b);
41
+        return result;
42
+    }
9 43
 }

+ 13
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ColorUtils.java 查看文件

@@ -0,0 +1,13 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+public class ColorUtils {
4
+    public static double[] colorToLAB(int color) {
5
+        final double[] result = new double[3];
6
+        android.support.v4.graphics.ColorUtils.colorToLAB(color, result);
7
+        return result;
8
+    }
9
+
10
+    public static int labToColor(double[] lab) {
11
+        return android.support.v4.graphics.ColorUtils.LABToColor(lab[0], lab[1], lab[2]);
12
+    }
13
+}

+ 41
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/TextViewUtils.java 查看文件

@@ -0,0 +1,41 @@
1
+package com.reactnativenavigation.utils;
2
+
3
+import android.graphics.Color;
4
+import android.support.annotation.ColorInt;
5
+import android.text.SpannableString;
6
+import android.text.Spanned;
7
+import android.text.SpannedString;
8
+import android.text.style.AbsoluteSizeSpan;
9
+import android.text.style.ForegroundColorSpan;
10
+import android.util.Log;
11
+import android.widget.TextView;
12
+
13
+import com.reactnativenavigation.views.element.animators.AntiRelativeSizeSpan;
14
+
15
+public class TextViewUtils {
16
+    @ColorInt
17
+    public static int getTextColor(TextView view) {
18
+        SpannedString text = new SpannedString(view.getText());
19
+        ForegroundColorSpan[] spans = text.getSpans(0, text.length(), ForegroundColorSpan.class);
20
+        return spans.length == 0 ? Color.WHITE : spans[0].getForegroundColor();
21
+    }
22
+
23
+    public static float getTextSize(TextView view) {
24
+        SpannedString text = new SpannedString(view.getText());
25
+        AbsoluteSizeSpan[] spans = text.getSpans(0, text.length(), AbsoluteSizeSpan.class);
26
+        return spans.length == 0 ? -1 : spans[0].getSize();
27
+    }
28
+
29
+    public static void setColor(SpannableString span, int color) {
30
+        span.setSpan(new ForegroundColorSpan(color), 0, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
31
+    }
32
+
33
+    public static void setRelativeTextSize(SpannableString span, float scale) {
34
+        Log.i("TextViewUtils", "setRelativeTextSize: " + scale + " - " + span);
35
+        span.setSpan(new AntiRelativeSizeSpan(scale), 0, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
36
+    }
37
+
38
+    public static void setAbsoluteTextSize(SpannableString span, float size) {
39
+        span.setSpan(new AbsoluteSizeSpan((int) size), 0, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
40
+    }
41
+}

+ 46
- 0
lib/android/app/src/main/java/com/reactnativenavigation/utils/ViewUtils.java 查看文件

@@ -1,8 +1,13 @@
1 1
 package com.reactnativenavigation.utils;
2 2
 
3
+import android.graphics.Point;
3 4
 import android.support.annotation.Nullable;
4 5
 import android.view.View;
5 6
 import android.view.ViewGroup;
7
+import android.view.ViewParent;
8
+
9
+import com.facebook.react.views.view.ReactViewBackgroundDrawable;
10
+import com.reactnativenavigation.react.ReactView;
6 11
 
7 12
 import java.util.ArrayList;
8 13
 import java.util.List;
@@ -79,4 +84,45 @@ public class ViewUtils {
79 84
         if (view.getLayoutParams() == null) return 0;
80 85
         return view.getLayoutParams().height < 0 ? view.getHeight() : view.getLayoutParams().height;
81 86
     }
87
+
88
+    public static void performOnParentReactView(View child, Task<ReactView> task) {
89
+        ReactView parent = findParentReactView(child.getParent());
90
+        if (parent != null) {
91
+            task.run(parent);
92
+        }
93
+    }
94
+
95
+    private static ReactView findParentReactView(ViewParent parent) {
96
+        if (parent == null) {
97
+            return null;
98
+        }
99
+        if (parent instanceof ReactView) {
100
+            return (ReactView) parent;
101
+        }
102
+        return findParentReactView(parent.getParent());
103
+    }
104
+
105
+    public static Point getLocationOnScreen(View view) {
106
+        int[] xy = new int[2];
107
+        view.getLocationOnScreen(xy);
108
+        return new Point(xy[0], xy[1]);
109
+    }
110
+
111
+    public static boolean areDimensionsEqual(View a, View b) {
112
+        return a.getWidth() == b.getWidth() && a.getHeight() == b.getHeight();
113
+    }
114
+
115
+    public static boolean instanceOf(Class clazz, View... views) {
116
+        for (View view : views) {
117
+            if (!view.getClass().isAssignableFrom(clazz)) return false;
118
+        }
119
+        return true;
120
+    }
121
+
122
+    public static int getBackgroundColor(View view) {
123
+        if (view.getBackground() instanceof ReactViewBackgroundDrawable) {
124
+            return ((ReactViewBackgroundDrawable) view.getBackground()).getColor();
125
+        }
126
+        throw new RuntimeException(view.getBackground().getClass().getSimpleName() + " is not ReactViewBackgroundDrawable");
127
+    }
82 128
 }

+ 5
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/IReactView.java 查看文件

@@ -4,6 +4,9 @@ import android.view.MotionEvent;
4 4
 import android.view.View;
5 5
 
6 6
 import com.reactnativenavigation.interfaces.ScrollEventListener;
7
+import com.reactnativenavigation.views.element.Element;
8
+
9
+import java.util.List;
7 10
 
8 11
 public interface IReactView extends Destroyable {
9 12
 
@@ -22,4 +25,6 @@ public interface IReactView extends Destroyable {
22 25
     void dispatchTouchEventToJs(MotionEvent event);
23 26
 
24 27
     boolean isRendered();
28
+
29
+    List<Element> getElements();
25 30
 }

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/Navigator.java 查看文件

@@ -17,6 +17,7 @@ import com.reactnativenavigation.utils.CommandListenerAdapter;
17 17
 import com.reactnativenavigation.utils.CompatUtils;
18 18
 import com.reactnativenavigation.viewcontrollers.modal.ModalStack;
19 19
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
20
+import com.reactnativenavigation.views.element.ElementTransitionManager;
20 21
 
21 22
 import java.util.Collection;
22 23
 import java.util.Collections;
@@ -107,7 +108,7 @@ public class Navigator extends ParentController {
107 108
         root = viewController;
108 109
         contentLayout.addView(viewController.getView());
109 110
         if (viewController.options.animations.startApp.hasAnimation()) {
110
-            new NavigationAnimator(viewController.getActivity())
111
+            new NavigationAnimator(viewController.getActivity(), new ElementTransitionManager())
111 112
                     .animateStartApp(viewController.getView(), viewController.options.animations.startApp, new AnimatorListenerAdapter() {
112 113
                         @Override
113 114
                         public void onAnimationEnd(Animator animation) {

+ 16
- 3
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ViewController.java 查看文件

@@ -18,13 +18,19 @@ import com.reactnativenavigation.presentation.FabOptionsPresenter;
18 18
 import com.reactnativenavigation.utils.CommandListener;
19 19
 import com.reactnativenavigation.utils.StringUtils;
20 20
 import com.reactnativenavigation.utils.Task;
21
+import com.reactnativenavigation.utils.UiThread;
21 22
 import com.reactnativenavigation.utils.UiUtils;
22 23
 import com.reactnativenavigation.viewcontrollers.stack.StackController;
23 24
 import com.reactnativenavigation.views.Component;
25
+import com.reactnativenavigation.views.element.Element;
26
+
27
+import java.util.Collections;
28
+import java.util.List;
24 29
 
25 30
 public abstract class ViewController<T extends ViewGroup> implements ViewTreeObserver.OnGlobalLayoutListener {
26 31
 
27 32
     private Runnable onAppearedListener;
33
+    private boolean appearEventPosted;
28 34
     private Bool waitForRender = new NullBool();
29 35
 
30 36
     public interface ViewVisibilityListener {
@@ -191,9 +197,12 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
191 197
             parentController.clearOptions();
192 198
             if (getView() instanceof Component) parentController.applyChildOptions(options, (Component) getView());
193 199
         });
194
-        if (onAppearedListener != null) {
195
-            onAppearedListener.run();
196
-            onAppearedListener = null;
200
+        if (onAppearedListener != null && !appearEventPosted) {
201
+            appearEventPosted = true;
202
+            UiThread.post(() -> {
203
+                onAppearedListener.run();
204
+                onAppearedListener = null;
205
+            });
197 206
         }
198 207
     }
199 208
 
@@ -264,4 +273,8 @@ public abstract class ViewController<T extends ViewGroup> implements ViewTreeObs
264 273
     void applyOnController(ViewController controller, Task<ViewController> task) {
265 274
         if (controller != null) task.run(controller);
266 275
     }
276
+
277
+    public List<Element> getElements() {
278
+        return getView() instanceof IReactView ? ((IReactView) view).getElements() : Collections.EMPTY_LIST;
279
+    }
267 280
 }

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackController.java 查看文件

@@ -135,7 +135,8 @@ public class StackController extends ParentController<StackLayout> {
135 135
         if (toRemove != null) {
136 136
             if (resolvedOptions.animations.push.enable.isTrueOrUndefined()) {
137 137
                 if (resolvedOptions.animations.push.waitForRender.isTrue()) {
138
-                    child.setOnAppearedListener(() -> animator.push(child.getView(), resolvedOptions.animations.push, () -> {
138
+                    child.getView().setAlpha(0);
139
+                    child.setOnAppearedListener(() -> animator.push(child.getView(), resolvedOptions.animations.push, resolvedOptions.transitions, toRemove.getElements(), child.getElements(), () -> {
139 140
                         getView().removeView(toRemove.getView());
140 141
                         listener.onSuccess(child.getId());
141 142
                     }));

+ 2
- 1
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerBuilder.java 查看文件

@@ -11,6 +11,7 @@ import com.reactnativenavigation.viewcontrollers.ReactViewCreator;
11 11
 import com.reactnativenavigation.viewcontrollers.ViewController;
12 12
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarBackgroundViewController;
13 13
 import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
14
+import com.reactnativenavigation.views.element.ElementTransitionManager;
14 15
 import com.reactnativenavigation.views.titlebar.TitleBarReactViewCreator;
15 16
 
16 17
 import java.util.ArrayList;
@@ -34,7 +35,7 @@ public class StackControllerBuilder {
34 35
     public StackControllerBuilder(Activity activity) {
35 36
         this.activity = activity;
36 37
         presenter = new OptionsPresenter(activity, new Options());
37
-        animator = new NavigationAnimator(activity);
38
+        animator = new NavigationAnimator(activity, new ElementTransitionManager());
38 39
     }
39 40
 
40 41
     public StackControllerBuilder setChildren(List<ViewController> children) {

+ 8
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/ComponentLayout.java 查看文件

@@ -13,9 +13,12 @@ import com.reactnativenavigation.utils.UiUtils;
13 13
 import com.reactnativenavigation.utils.ViewUtils;
14 14
 import com.reactnativenavigation.viewcontrollers.IReactView;
15 15
 import com.reactnativenavigation.viewcontrollers.TopBarButtonController;
16
+import com.reactnativenavigation.views.element.Element;
16 17
 import com.reactnativenavigation.views.topbar.TopBar;
17 18
 import com.reactnativenavigation.views.touch.OverlayTouchDelegate;
18 19
 
20
+import java.util.List;
21
+
19 22
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
20 23
 
21 24
 @SuppressLint("ViewConstructor")
@@ -104,6 +107,11 @@ public class ComponentLayout extends FrameLayout implements ReactComponent, TopB
104 107
         return reactView.isRendered();
105 108
     }
106 109
 
110
+    @Override
111
+    public List<Element> getElements() {
112
+        return reactView.getElements();
113
+    }
114
+
107 115
     @Override
108 116
     public void onPress(String buttonId) {
109 117
         reactView.sendOnNavigationButtonPressed(buttonId);

+ 100
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/Element.java 查看文件

@@ -0,0 +1,100 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.content.Context;
4
+import android.graphics.Rect;
5
+import android.support.annotation.Keep;
6
+import android.support.annotation.NonNull;
7
+import android.support.annotation.Nullable;
8
+import android.text.SpannableString;
9
+import android.text.SpannedString;
10
+import android.util.Log;
11
+import android.view.View;
12
+import android.widget.FrameLayout;
13
+import android.widget.TextView;
14
+
15
+import com.facebook.drawee.drawable.ScalingUtils;
16
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
17
+import com.facebook.drawee.view.DraweeView;
18
+import com.facebook.react.views.view.ReactViewBackgroundDrawable;
19
+import com.reactnativenavigation.utils.TextViewUtils;
20
+import com.reactnativenavigation.utils.UiUtils;
21
+
22
+import static com.reactnativenavigation.utils.ColorUtils.labToColor;
23
+import static com.reactnativenavigation.utils.TextViewUtils.setColor;
24
+
25
+public class Element extends FrameLayout {
26
+    private String elementId;
27
+    @Nullable private SpannableString spannableText;
28
+    private float originalTextSize;
29
+
30
+    public Element(@NonNull Context context) {
31
+        super(context);
32
+    }
33
+
34
+    public String getElementId() {
35
+        return elementId;
36
+    }
37
+
38
+    public void setElementId(String elementId) {
39
+        this.elementId = elementId;
40
+    }
41
+
42
+    public View getChild() {
43
+        return getChildAt(0);
44
+    }
45
+
46
+    @Override
47
+    public void onViewAdded(View child) {
48
+        super.onViewAdded(child);
49
+        UiUtils.runOnPreDrawOnce(child, () -> {
50
+            if (child instanceof TextView) {
51
+                SpannedString spannedText = new SpannedString(((TextView) child).getText());
52
+                spannableText = new SpannableString(spannedText);
53
+                ((TextView) child).setText(spannableText);
54
+
55
+                originalTextSize = TextViewUtils.getTextSize((TextView) child);
56
+            }
57
+        });
58
+    }
59
+
60
+    @Keep
61
+    public void setClipBounds(Rect clipBounds) {
62
+        getChild().setClipBounds(clipBounds);
63
+    }
64
+
65
+    @Keep
66
+    public void setMatrixTransform(float value) {
67
+        GenericDraweeHierarchy hierarchy = ((DraweeView<GenericDraweeHierarchy>) getChild()).getHierarchy();
68
+        if (hierarchy.getActualImageScaleType() != null) {
69
+            ((ScalingUtils.InterpolatingScaleType) hierarchy.getActualImageScaleType()).setValue(value);
70
+            getChild().invalidate();
71
+        }
72
+    }
73
+
74
+    @Keep
75
+    public void setBackgroundColor(double[] color) {
76
+        ((ReactViewBackgroundDrawable) getChild().getBackground()).setColor(labToColor(color));
77
+    }
78
+
79
+    @Keep
80
+    public void setTextColor(double[] color) {
81
+        if (spannableText != null) {
82
+            setColor(spannableText, labToColor(color));
83
+            ((TextView) getChild()).setText(spannableText);
84
+        }
85
+    }
86
+
87
+    @Keep
88
+    public void setTextSize(float size) {
89
+        if (spannableText != null) {
90
+            Log.d("Element", "setTextSize: " + size);
91
+//            ((ReactTextView) getChild()).setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
92
+
93
+//            ((ReactTextView) getChild()).updateView();
94
+//            TextViewUtils.setAbsoluteTextSize(spannableText, size);
95
+            TextViewUtils.setRelativeTextSize(spannableText, size);
96
+            ((TextView) getChild()).setText(spannableText);
97
+//            getChild().invalidate();
98
+        }
99
+    }
100
+}

+ 42
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/ElementTransitionManager.java 查看文件

@@ -0,0 +1,42 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.animation.Animator;
4
+import android.support.annotation.RestrictTo;
5
+import android.util.Log;
6
+
7
+import com.reactnativenavigation.parse.Transition;
8
+import com.reactnativenavigation.parse.Transitions;
9
+
10
+import java.util.Collection;
11
+import java.util.Collections;
12
+import java.util.List;
13
+import java.util.Map;
14
+
15
+import static com.reactnativenavigation.utils.CollectionUtils.filter;
16
+import static com.reactnativenavigation.utils.CollectionUtils.map;
17
+
18
+public class ElementTransitionManager {
19
+
20
+    private final TransitionValidator validator;
21
+    private final TransitionAnimatorCreator animatorCreator;
22
+
23
+    @RestrictTo(RestrictTo.Scope.TESTS)
24
+    ElementTransitionManager(TransitionValidator validator, TransitionAnimatorCreator animatorCreator) {
25
+        this.validator = validator;
26
+        this.animatorCreator = animatorCreator;
27
+    }
28
+
29
+    public ElementTransitionManager() {
30
+        validator = new TransitionValidator();
31
+        animatorCreator = new TransitionAnimatorCreator();
32
+    }
33
+
34
+    public Collection<Animator> createTransitions(Transitions transitions, List<Element> fromElements, List<Element> toElements) {
35
+        if (!transitions.hasValue() || fromElements.isEmpty() || toElements.isEmpty()) return Collections.EMPTY_LIST;
36
+        Map<String, Element> from = map(fromElements, Element::getElementId);
37
+        Map<String, Element> to = map(toElements, Element::getElementId);
38
+        List<Transition> validTransitions = filter(transitions.get(), (t) -> validator.validate(t, from, to));
39
+
40
+        return animatorCreator.create(validTransitions, from, to);
41
+    }
42
+}

+ 52
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/TransitionAnimatorCreator.java 查看文件

@@ -0,0 +1,52 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.animation.Animator;
4
+
5
+import com.reactnativenavigation.parse.Transition;
6
+import com.reactnativenavigation.views.element.animators.BackgroundColorAnimator;
7
+import com.reactnativenavigation.views.element.animators.MatrixAnimator;
8
+import com.reactnativenavigation.views.element.animators.PropertyAnimatorCreator;
9
+import com.reactnativenavigation.views.element.animators.ScaleXAnimator;
10
+import com.reactnativenavigation.views.element.animators.ScaleYAnimator;
11
+import com.reactnativenavigation.views.element.animators.TextChangeAnimator;
12
+import com.reactnativenavigation.views.element.animators.XAnimator;
13
+import com.reactnativenavigation.views.element.animators.YAnimator;
14
+
15
+import java.util.ArrayList;
16
+import java.util.Arrays;
17
+import java.util.Collection;
18
+import java.util.Collections;
19
+import java.util.List;
20
+import java.util.Map;
21
+
22
+public class TransitionAnimatorCreator {
23
+
24
+    public Collection<Animator> create(List<Transition> transitions, Map<String, Element> from, Map<String, Element> to) {
25
+        if (transitions.isEmpty()) return Collections.EMPTY_LIST;
26
+        List<Animator> animators = new ArrayList<>();
27
+        for (Transition transition : transitions) {
28
+            animators.addAll(create(transition, from.get(transition.fromId.get()), to.get(transition.toId.get())));
29
+        }
30
+        return animators;
31
+    }
32
+
33
+    protected Collection<? extends Animator> create(Transition transition, Element from, Element to) {
34
+        Collection<Animator> animators = new ArrayList<>();
35
+        for (PropertyAnimatorCreator creator : getAnimators(from, to)) {
36
+            if (creator.shouldAnimateProperty()) animators.add(creator.create(transition));
37
+        }
38
+        return animators;
39
+    }
40
+
41
+    protected List<PropertyAnimatorCreator> getAnimators(Element from, Element to) {
42
+        return Arrays.asList(
43
+                new XAnimator(from, to),
44
+                new YAnimator(from, to),
45
+                new MatrixAnimator(from, to),
46
+                new ScaleXAnimator(from, to),
47
+                new ScaleYAnimator(from, to),
48
+                new BackgroundColorAnimator(from, to),
49
+                new TextChangeAnimator(from, to)
50
+        );
51
+    }
52
+}

+ 15
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/TransitionValidator.java 查看文件

@@ -0,0 +1,15 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import com.reactnativenavigation.parse.Transition;
4
+
5
+import java.util.Map;
6
+
7
+public class TransitionValidator {
8
+    protected boolean validate(Transition transition, Map<String, Element> fromMap, Map<String, Element> toMap) {
9
+        return transition.fromId.hasValue() &&
10
+                fromMap.containsKey(transition.fromId.get()) &&
11
+               transition.toId.hasValue() &&
12
+               toMap.containsKey(transition.toId.get()) &&
13
+               transition.duration.hasValue();
14
+    }
15
+}

+ 29
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/AntiRelativeSizeSpan.java 查看文件

@@ -0,0 +1,29 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.text.TextPaint;
4
+import android.text.style.MetricAffectingSpan;
5
+import android.util.Log;
6
+
7
+public class AntiRelativeSizeSpan extends MetricAffectingSpan {
8
+    private final float size;
9
+
10
+    public AntiRelativeSizeSpan(float size) {
11
+        this.size = size;
12
+    }
13
+
14
+    @Override
15
+    public void updateDrawState(TextPaint ds) {
16
+        updateAnyState(ds);
17
+    }
18
+
19
+    @Override
20
+    public void updateMeasureState(TextPaint ds) {
21
+        updateAnyState(ds);
22
+    }
23
+
24
+    private void updateAnyState(TextPaint ds) {
25
+        Log.i("AntiRelativeSizeSpan", "updateAnyState: " + size + "|" + ds.density);
26
+        ds.setTextSize(size);
27
+    }
28
+}
29
+

+ 44
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/BackgroundColorAnimator.java 查看文件

@@ -0,0 +1,44 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.view.ViewGroup;
6
+
7
+import com.facebook.react.views.text.ReactTextView;
8
+import com.facebook.react.views.view.ReactViewBackgroundDrawable;
9
+import com.reactnativenavigation.utils.ColorUtils;
10
+import com.reactnativenavigation.utils.ViewUtils;
11
+import com.reactnativenavigation.views.element.Element;
12
+
13
+import java.util.Collections;
14
+import java.util.List;
15
+
16
+public class BackgroundColorAnimator extends PropertyAnimatorCreator<ViewGroup> {
17
+
18
+    public BackgroundColorAnimator(Element from, Element to) {
19
+        super(from, to);
20
+    }
21
+
22
+    @Override
23
+    public boolean shouldAnimateProperty(ViewGroup fromChild, ViewGroup toChild) {
24
+        return fromChild.getBackground() instanceof ReactViewBackgroundDrawable &&
25
+               toChild.getBackground() instanceof ReactViewBackgroundDrawable &&
26
+               ((ReactViewBackgroundDrawable) fromChild.getBackground()).getColor() != ((ReactViewBackgroundDrawable) toChild.getBackground()).getColor();
27
+    }
28
+
29
+    @Override
30
+    protected List<Class> excludedViews() {
31
+        return Collections.singletonList(ReactTextView.class);
32
+    }
33
+
34
+    @Override
35
+    public Animator create() {
36
+        return ObjectAnimator.ofObject(
37
+                to,
38
+                "backgroundColor",
39
+                new LabColorEvaluator(),
40
+                ColorUtils.colorToLAB(ViewUtils.getBackgroundColor(from.getChild())),
41
+                ColorUtils.colorToLAB(ViewUtils.getBackgroundColor(to.getChild()))
42
+        );
43
+    }
44
+}

+ 45
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ClipBoundsEvaluator.java 查看文件

@@ -0,0 +1,45 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.TypeEvaluator;
4
+import android.graphics.Rect;
5
+
6
+public class ClipBoundsEvaluator implements TypeEvaluator<Rect> {
7
+    private int fromWidth;
8
+    private int fromHeight;
9
+    private int toWidth;
10
+    private int toHeight;
11
+    private final Rect result = new Rect();
12
+
13
+    @Override
14
+    public Rect evaluate(float ratio, Rect from, Rect to) {
15
+        sync(from, to);
16
+
17
+        if (toHeight == fromHeight ) {
18
+            result.bottom = toHeight;
19
+        } else {
20
+            if (toHeight > fromHeight) {
21
+                result.bottom = (int) (toHeight - (toHeight - fromHeight) * (1 - ratio));
22
+            } else {
23
+                result.bottom = (int) (toHeight + (fromHeight - toHeight) * (1 - ratio));
24
+            }
25
+        }
26
+
27
+        if (toWidth == fromWidth) {
28
+            result.right = toWidth;
29
+        } else {
30
+            if (toWidth > fromWidth) {
31
+                result.right = (int) (toWidth - (toWidth - fromWidth) * (1 - ratio));
32
+            } else {
33
+                result.right = (int) (toWidth + (fromWidth - toWidth) * (1 - ratio));
34
+            }
35
+        }
36
+        return result;
37
+    }
38
+
39
+    private void sync(Rect from, Rect to) {
40
+        fromWidth = from.right;
41
+        fromHeight = from.bottom;
42
+        toWidth = to.right;
43
+        toHeight = to.bottom;
44
+    }
45
+}

+ 14
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/LabColorEvaluator.java 查看文件

@@ -0,0 +1,14 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.TypeEvaluator;
4
+import android.support.v4.graphics.ColorUtils;
5
+
6
+public class LabColorEvaluator implements TypeEvaluator<double[]> {
7
+    private final double[] color = new double[3];
8
+
9
+    @Override
10
+    public double[] evaluate(float ratio, double[] from, double[] to) {
11
+        ColorUtils.blendLAB(from, to, ratio, color);
12
+        return color;
13
+    }
14
+}

+ 63
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/MatrixAnimator.java 查看文件

@@ -0,0 +1,63 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.AnimatorSet;
5
+import android.animation.ObjectAnimator;
6
+import android.graphics.Rect;
7
+import android.view.View;
8
+
9
+import com.facebook.drawee.drawable.ScalingUtils;
10
+import com.facebook.drawee.generic.GenericDraweeHierarchy;
11
+import com.facebook.drawee.view.DraweeView;
12
+import com.facebook.react.views.image.ReactImageView;
13
+import com.reactnativenavigation.views.element.Element;
14
+
15
+import static com.reactnativenavigation.utils.ViewUtils.areDimensionsEqual;
16
+
17
+public class MatrixAnimator extends PropertyAnimatorCreator<ReactImageView> {
18
+
19
+    public MatrixAnimator(Element from, Element to) {
20
+        super(from, to);
21
+    }
22
+
23
+    @Override
24
+    public boolean shouldAnimateProperty(ReactImageView fromChild, ReactImageView toChild) {
25
+        return !areDimensionsEqual(from.getChild(), to.getChild());
26
+    }
27
+
28
+    @Override
29
+    public Animator create() {
30
+        AnimatorSet set = new AnimatorSet();
31
+        set.playTogether(clipBoundsAnimator(), imageTransformAnimator());
32
+        return set;
33
+    }
34
+
35
+    private Animator clipBoundsAnimator() {
36
+        Rect startDrawingRect = new Rect(); from.getDrawingRect(startDrawingRect);
37
+        Rect endDrawingRect = new Rect(); to.getDrawingRect(endDrawingRect);
38
+        return ObjectAnimator.ofObject(to,
39
+                "clipBounds",
40
+                new ClipBoundsEvaluator(),
41
+                startDrawingRect,
42
+                endDrawingRect);
43
+    }
44
+
45
+    private Animator imageTransformAnimator() {
46
+        ScalingUtils.InterpolatingScaleType ist = new ScalingUtils.InterpolatingScaleType(
47
+                getScaleType(from.getChild()),
48
+                getScaleType(to.getChild()),
49
+                calculateBounds(from.getChild()),
50
+                calculateBounds(to.getChild())
51
+        );
52
+        ((DraweeView<GenericDraweeHierarchy>) to.getChild()).getHierarchy().setActualImageScaleType(ist);
53
+        return ObjectAnimator.ofFloat(to, "matrixTransform", 0, 1);
54
+    }
55
+
56
+    private ScalingUtils.ScaleType getScaleType(View child) {
57
+        return ((DraweeView<GenericDraweeHierarchy>) child).getHierarchy().getActualImageScaleType();
58
+    }
59
+
60
+    private Rect calculateBounds(View view) {
61
+        return new Rect(0, 0, view.getWidth(), view.getHeight());
62
+    }
63
+}

+ 63
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/PropertyAnimatorCreator.java 查看文件

@@ -0,0 +1,63 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.AnimatorListenerAdapter;
5
+import android.support.annotation.CallSuper;
6
+import android.view.ViewGroup;
7
+
8
+import com.reactnativenavigation.parse.Transition;
9
+import com.reactnativenavigation.views.element.Element;
10
+
11
+import java.lang.reflect.ParameterizedType;
12
+import java.util.Collections;
13
+import java.util.List;
14
+
15
+public abstract class PropertyAnimatorCreator<T> {
16
+
17
+    protected Element from;
18
+    protected Element to;
19
+
20
+    PropertyAnimatorCreator(Element from, Element to) {
21
+        this.from = from;
22
+        this.to = to;
23
+    }
24
+
25
+    @CallSuper
26
+    public boolean shouldAnimateProperty() {
27
+        Class<T> type = getChildClass();
28
+        return type.isInstance(from.getChild()) &&
29
+               type.isInstance(to.getChild()) &&
30
+               !excludedViews().contains(from.getChild().getClass()) &&
31
+               !excludedViews().contains(to.getChild().getClass()) &&
32
+               shouldAnimateProperty((T) from.getChild(), (T) to.getChild());
33
+    }
34
+
35
+    protected abstract boolean shouldAnimateProperty(T fromChild, T toChild);
36
+
37
+    protected List<Class> excludedViews() {
38
+        return Collections.EMPTY_LIST;
39
+    }
40
+
41
+    public Animator create(Transition transition) {
42
+        Animator animator = create().setDuration(transition.duration.get());
43
+        animator.addListener(new AnimatorListenerAdapter() {
44
+            private final boolean originalClipChildren = ((ViewGroup) to.getParent()).getClipChildren();
45
+            @Override
46
+            public void onAnimationStart(Animator animation) {
47
+                ((ViewGroup) to.getParent()).setClipChildren(false);
48
+            }
49
+
50
+            @Override
51
+            public void onAnimationEnd(Animator animation) {
52
+                ((ViewGroup) to.getParent()).setClipChildren(originalClipChildren);
53
+            }
54
+        });
55
+        return animator;
56
+    }
57
+
58
+    protected abstract Animator create();
59
+
60
+    private Class<T> getChildClass() {
61
+        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
62
+    }
63
+}

+ 39
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ScaleXAnimator.java 查看文件

@@ -0,0 +1,39 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.view.View;
6
+import android.view.ViewGroup;
7
+
8
+import com.facebook.react.views.text.ReactTextView;
9
+import com.reactnativenavigation.views.element.Element;
10
+
11
+import java.util.Collections;
12
+import java.util.List;
13
+
14
+public class ScaleXAnimator extends PropertyAnimatorCreator<ViewGroup> {
15
+
16
+    public ScaleXAnimator(Element from, Element to) {
17
+        super(from, to);
18
+    }
19
+
20
+    @Override
21
+    public boolean shouldAnimateProperty(ViewGroup fromChild, ViewGroup toChild) {
22
+        return fromChild.getChildCount() == 0 && toChild.getChildCount() == 0;
23
+    }
24
+
25
+    @Override
26
+    protected List<Class> excludedViews() {
27
+        return Collections.singletonList(ReactTextView.class);
28
+    }
29
+
30
+    @Override
31
+    public Animator create() {
32
+        return ObjectAnimator.ofFloat(
33
+                to.getChild(),
34
+                View.SCALE_X,
35
+                ((float) from.getChild().getWidth()) / to.getChild().getWidth(),
36
+                1
37
+        );
38
+    }
39
+}

+ 39
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/ScaleYAnimator.java 查看文件

@@ -0,0 +1,39 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.view.View;
6
+import android.view.ViewGroup;
7
+
8
+import com.facebook.react.views.text.ReactTextView;
9
+import com.reactnativenavigation.views.element.Element;
10
+
11
+import java.util.Collections;
12
+import java.util.List;
13
+
14
+public class ScaleYAnimator extends PropertyAnimatorCreator<ViewGroup> {
15
+
16
+    public ScaleYAnimator(Element from, Element to) {
17
+        super(from, to);
18
+    }
19
+
20
+    @Override
21
+    public boolean shouldAnimateProperty(ViewGroup fromChild, ViewGroup toChild) {
22
+        return fromChild.getChildCount() == 0 && toChild.getChildCount() == 0;
23
+    }
24
+
25
+    @Override
26
+    protected List<Class> excludedViews() {
27
+        return Collections.singletonList(ReactTextView.class);
28
+    }
29
+
30
+    @Override
31
+    public Animator create() {
32
+        return ObjectAnimator.ofFloat(
33
+                to.getChild(),
34
+                View.SCALE_Y,
35
+                ((float) from.getChild().getHeight()) / to.getChild().getHeight(),
36
+                1
37
+        );
38
+    }
39
+}

+ 30
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/TextChangeAnimator.java 查看文件

@@ -0,0 +1,30 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.widget.TextView;
5
+
6
+import com.facebook.react.views.text.ReactTextView;
7
+import com.reactnativenavigation.views.element.Element;
8
+import com.shazam.android.widget.text.reflow.ReflowTextAnimatorHelper;
9
+
10
+import static com.reactnativenavigation.utils.TextViewUtils.getTextSize;
11
+
12
+public class TextChangeAnimator extends PropertyAnimatorCreator<ReactTextView> {
13
+
14
+    public TextChangeAnimator(Element from, Element to) {
15
+        super(from, to);
16
+    }
17
+
18
+    @Override
19
+    protected boolean shouldAnimateProperty(ReactTextView fromChild, ReactTextView toChild) {
20
+        return getTextSize(fromChild) != getTextSize(toChild);
21
+    }
22
+
23
+    @Override
24
+    public Animator create() {
25
+        return new ReflowTextAnimatorHelper
26
+                .Builder((TextView) from.getChild(), (TextView) to.getChild())
27
+                .calculateDuration(false)
28
+                .buildAnimator();
29
+    }
30
+}

+ 35
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/TextColorAnimator.java 查看文件

@@ -0,0 +1,35 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.widget.TextView;
6
+
7
+import com.facebook.react.views.text.ReactTextView;
8
+import com.reactnativenavigation.utils.ColorUtils;
9
+import com.reactnativenavigation.utils.TextViewUtils;
10
+import com.reactnativenavigation.views.element.Element;
11
+
12
+import static com.reactnativenavigation.utils.TextViewUtils.getTextColor;
13
+
14
+public class TextColorAnimator extends PropertyAnimatorCreator<ReactTextView> {
15
+
16
+    public TextColorAnimator(Element from, Element to) {
17
+        super(from, to);
18
+    }
19
+
20
+    @Override
21
+    protected boolean shouldAnimateProperty(ReactTextView fromChild, ReactTextView toChild) {
22
+        return getTextColor(fromChild) != getTextColor(toChild);
23
+    }
24
+
25
+    @Override
26
+    public Animator create() {
27
+        return ObjectAnimator.ofObject(
28
+                to,
29
+                "textColor",
30
+                new LabColorEvaluator(),
31
+                ColorUtils.colorToLAB(TextViewUtils.getTextColor((TextView) from.getChild())),
32
+                ColorUtils.colorToLAB(TextViewUtils.getTextColor((TextView) to.getChild()))
33
+        );
34
+    }
35
+}

+ 33
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/TextSizeAnimator.java 查看文件

@@ -0,0 +1,33 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.widget.TextView;
6
+
7
+import com.facebook.react.views.text.ReactTextView;
8
+import com.reactnativenavigation.utils.TextViewUtils;
9
+import com.reactnativenavigation.views.element.Element;
10
+
11
+import static com.reactnativenavigation.utils.TextViewUtils.getTextSize;
12
+
13
+public class TextSizeAnimator extends PropertyAnimatorCreator<ReactTextView> {
14
+
15
+    public TextSizeAnimator(Element from, Element to) {
16
+        super(from, to);
17
+    }
18
+
19
+    @Override
20
+    protected boolean shouldAnimateProperty(ReactTextView fromChild, ReactTextView toChild) {
21
+        return getTextSize(fromChild) != getTextSize(toChild);
22
+    }
23
+
24
+    @Override
25
+    public Animator create() {
26
+        return ObjectAnimator.ofFloat(
27
+                to,
28
+                "textSize",
29
+                TextViewUtils.getTextSize((TextView) from.getChild()),
30
+                TextViewUtils.getTextSize((TextView) to.getChild())
31
+        );
32
+    }
33
+}

+ 40
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/XAnimator.java 查看文件

@@ -0,0 +1,40 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.graphics.Point;
6
+import android.view.View;
7
+
8
+import com.facebook.react.views.text.ReactTextView;
9
+import com.reactnativenavigation.utils.ViewUtils;
10
+import com.reactnativenavigation.views.element.Element;
11
+
12
+import java.util.Collections;
13
+import java.util.List;
14
+
15
+public class XAnimator extends PropertyAnimatorCreator<View> {
16
+
17
+    private final int dx;
18
+
19
+    public XAnimator(Element from, Element to) {
20
+        super(from, to);
21
+        final Point fromXy = ViewUtils.getLocationOnScreen(from.getChild());
22
+        final Point toXy = ViewUtils.getLocationOnScreen(to.getChild());
23
+        dx = fromXy.x - toXy.x;
24
+    }
25
+
26
+    @Override
27
+    protected List<Class> excludedViews() {
28
+        return Collections.singletonList(ReactTextView.class);
29
+    }
30
+
31
+    @Override
32
+    public boolean shouldAnimateProperty(View fromChild, View toChild) {
33
+        return dx != 0;
34
+    }
35
+
36
+    @Override
37
+    public Animator create() {
38
+        return ObjectAnimator.ofFloat(to.getChild(), View.TRANSLATION_X, dx, 0);
39
+    }
40
+}

+ 40
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/element/animators/YAnimator.java 查看文件

@@ -0,0 +1,40 @@
1
+package com.reactnativenavigation.views.element.animators;
2
+
3
+import android.animation.Animator;
4
+import android.animation.ObjectAnimator;
5
+import android.graphics.Point;
6
+import android.view.View;
7
+
8
+import com.facebook.react.views.text.ReactTextView;
9
+import com.reactnativenavigation.utils.ViewUtils;
10
+import com.reactnativenavigation.views.element.Element;
11
+
12
+import java.util.Collections;
13
+import java.util.List;
14
+
15
+public class YAnimator extends PropertyAnimatorCreator<View> {
16
+
17
+    private final int dy;
18
+
19
+    public YAnimator(Element from, Element to) {
20
+        super(from, to);
21
+        final Point fromXy = ViewUtils.getLocationOnScreen(from.getChild());
22
+        final Point toXy = ViewUtils.getLocationOnScreen(to.getChild());
23
+        dy = fromXy.y - toXy.y;
24
+    }
25
+
26
+    @Override
27
+    protected List<Class> excludedViews() {
28
+        return Collections.singletonList(ReactTextView.class);
29
+    }
30
+
31
+    @Override
32
+    public boolean shouldAnimateProperty(View fromChild, View toChild) {
33
+        return dy != 0;
34
+    }
35
+
36
+    @Override
37
+    public Animator create() {
38
+        return ObjectAnimator.ofFloat(to.getChild(), View.TRANSLATION_Y, dy, 0);
39
+    }
40
+}

+ 9
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleOverlay.java 查看文件

@@ -8,6 +8,10 @@ import android.widget.RelativeLayout;
8 8
 
9 9
 import com.reactnativenavigation.interfaces.ScrollEventListener;
10 10
 import com.reactnativenavigation.viewcontrollers.IReactView;
11
+import com.reactnativenavigation.views.element.Element;
12
+
13
+import java.util.Collections;
14
+import java.util.List;
11 15
 
12 16
 public class SimpleOverlay extends RelativeLayout implements IReactView {
13 17
     public SimpleOverlay(Context context) {
@@ -61,4 +65,9 @@ public class SimpleOverlay extends RelativeLayout implements IReactView {
61 65
     public boolean isRendered() {
62 66
         return getChildCount() >= 1;
63 67
     }
68
+
69
+    @Override
70
+    public List<Element> getElements() {
71
+        return Collections.EMPTY_LIST;
72
+    }
64 73
 }

+ 10
- 10
lib/android/app/src/test/java/com/reactnativenavigation/mocks/SimpleViewController.java 查看文件

@@ -4,22 +4,23 @@ import android.app.Activity;
4 4
 import android.content.Context;
5 5
 import android.support.annotation.NonNull;
6 6
 import android.view.MotionEvent;
7
-import android.view.View;
8
-import android.widget.FrameLayout;
9 7
 import android.widget.RelativeLayout;
10 8
 
9
+import com.facebook.react.ReactInstanceManager;
11 10
 import com.reactnativenavigation.interfaces.ScrollEventListener;
12 11
 import com.reactnativenavigation.parse.Options;
13 12
 import com.reactnativenavigation.presentation.OptionsPresenter;
13
+import com.reactnativenavigation.react.ReactView;
14 14
 import com.reactnativenavigation.utils.ViewUtils;
15 15
 import com.reactnativenavigation.viewcontrollers.ChildController;
16 16
 import com.reactnativenavigation.viewcontrollers.ChildControllersRegistry;
17 17
 import com.reactnativenavigation.views.ReactComponent;
18 18
 import com.reactnativenavigation.views.topbar.TopBar;
19 19
 
20
+import org.mockito.Mockito;
21
+
20 22
 public class SimpleViewController extends ChildController<SimpleViewController.SimpleView> {
21 23
 
22
-    private SimpleView simpleView;
23 24
 
24 25
     public SimpleViewController(Activity activity, ChildControllersRegistry childRegistry, String id, Options options) {
25 26
         this(activity, childRegistry, id, new OptionsPresenter(activity, new Options()), options);
@@ -31,13 +32,12 @@ public class SimpleViewController extends ChildController<SimpleViewController.S
31 32
 
32 33
     @Override
33 34
     protected SimpleView createView() {
34
-        simpleView = new SimpleView(getActivity());
35
-        return simpleView;
35
+        return new SimpleView(getActivity());
36 36
     }
37 37
 
38 38
     @Override
39 39
     public void sendOnNavigationButtonPressed(String buttonId) {
40
-        simpleView.sendOnNavigationButtonPressed(buttonId);
40
+        getView().sendOnNavigationButtonPressed(buttonId);
41 41
     }
42 42
 
43 43
     @Override
@@ -51,10 +51,10 @@ public class SimpleViewController extends ChildController<SimpleViewController.S
51 51
         super.mergeOptions(options);
52 52
     }
53 53
 
54
-    public static class SimpleView extends FrameLayout implements ReactComponent {
54
+    public static class SimpleView extends ReactView implements ReactComponent {
55 55
 
56 56
         public SimpleView(@NonNull Context context) {
57
-            super(context);
57
+            super(context, Mockito.mock(ReactInstanceManager.class), "compId", "compName");
58 58
         }
59 59
 
60 60
         @Override
@@ -88,8 +88,8 @@ public class SimpleViewController extends ChildController<SimpleViewController.S
88 88
         }
89 89
 
90 90
         @Override
91
-        public View asView() {
92
-            return null;
91
+        public ReactView asView() {
92
+            return this;
93 93
         }
94 94
 
95 95
         @Override

+ 9
- 0
lib/android/app/src/test/java/com/reactnativenavigation/mocks/TestReactView.java 查看文件

@@ -8,6 +8,10 @@ import android.widget.FrameLayout;
8 8
 
9 9
 import com.reactnativenavigation.interfaces.ScrollEventListener;
10 10
 import com.reactnativenavigation.viewcontrollers.IReactView;
11
+import com.reactnativenavigation.views.element.Element;
12
+
13
+import java.util.Collections;
14
+import java.util.List;
11 15
 
12 16
 public class TestReactView extends FrameLayout implements IReactView {
13 17
 
@@ -59,4 +63,9 @@ public class TestReactView extends FrameLayout implements IReactView {
59 63
     public boolean isRendered() {
60 64
         return getChildCount() >= 1;
61 65
     }
66
+
67
+    @Override
68
+    public List<Element> getElements() {
69
+        return Collections.EMPTY_LIST;
70
+    }
62 71
 }

+ 58
- 0
lib/android/app/src/test/java/com/reactnativenavigation/react/ElementViewManagerTest.java 查看文件

@@ -0,0 +1,58 @@
1
+package com.reactnativenavigation.react;
2
+
3
+import com.facebook.react.uimanager.ThemedReactContext;
4
+import com.reactnativenavigation.BaseTest;
5
+import com.reactnativenavigation.mocks.SimpleViewController;
6
+import com.reactnativenavigation.views.element.Element;
7
+
8
+import org.junit.Test;
9
+import org.mockito.Mockito;
10
+import org.robolectric.Shadows;
11
+
12
+import static org.assertj.core.api.Java6Assertions.assertThat;
13
+import static org.mockito.Mockito.spy;
14
+import static org.mockito.Mockito.verify;
15
+
16
+public class ElementViewManagerTest extends BaseTest {
17
+    private ElementViewManager uut;
18
+    private Element view;
19
+    private ThemedReactContext reactContext = Mockito.mock(ThemedReactContext.class);
20
+    private SimpleViewController.SimpleView parentView;
21
+
22
+    @Override
23
+    public void beforeEach() {
24
+        view = new Element(newActivity());
25
+        uut = new ElementViewManager() {
26
+            @Override
27
+            public Element createView(ThemedReactContext reactContext) {
28
+                return view;
29
+            }
30
+        };
31
+        parentView = Mockito.mock(SimpleViewController.SimpleView.class);
32
+        Shadows.shadowOf(view).setMyParent(parentView);
33
+    }
34
+
35
+    @Test
36
+    public void createViewInstance() {
37
+        Element element = uut.createViewInstance(reactContext);
38
+        assertThat(element).isNotNull();
39
+    }
40
+
41
+    @Test
42
+    public void createViewInstance_registersInParentReactView() {
43
+        ElementViewManager spy = spy(uut);
44
+
45
+        parentView.addView(view);
46
+        spy.createViewInstance(reactContext);
47
+
48
+        dispatchPreDraw(view);
49
+        dispatchOnGlobalLayout(view);
50
+        verify(parentView).registerElement(view);
51
+    }
52
+
53
+    @Test
54
+    public void onDropViewInstance() {
55
+        uut.onDropViewInstance(view);
56
+        verify(parentView).unregisterElement(view);
57
+    }
58
+}

+ 100
- 0
lib/android/app/src/test/java/com/reactnativenavigation/views/element/ElementTransitionManagerTest.java 查看文件

@@ -0,0 +1,100 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.animation.Animator;
4
+import android.app.Activity;
5
+import android.view.View;
6
+
7
+import com.reactnativenavigation.BaseTest;
8
+import com.reactnativenavigation.parse.Transition;
9
+import com.reactnativenavigation.parse.Transitions;
10
+import com.reactnativenavigation.parse.params.Text;
11
+import com.reactnativenavigation.views.element.animators.PropertyAnimatorCreator;
12
+
13
+import org.junit.Test;
14
+
15
+import java.util.Arrays;
16
+import java.util.Collection;
17
+import java.util.Collections;
18
+import java.util.List;
19
+import java.util.Map;
20
+
21
+import static com.reactnativenavigation.views.element.TransitionTestUtils.createElement;
22
+import static com.reactnativenavigation.views.element.TransitionTestUtils.createTransition;
23
+import static org.assertj.core.api.Java6Assertions.assertThat;
24
+import static org.mockito.ArgumentMatchers.any;
25
+import static org.mockito.ArgumentMatchers.eq;
26
+import static org.mockito.Mockito.spy;
27
+import static org.mockito.Mockito.verify;
28
+
29
+public class ElementTransitionManagerTest extends BaseTest {
30
+    private ElementTransitionManager uut;
31
+    private Transition validTransition;
32
+    private Transition invalidTransition;
33
+    private Element from1;
34
+    private Element to1;
35
+    private TransitionValidator validator;
36
+    private TransitionAnimatorCreator animatorCreator;
37
+
38
+    @Override
39
+    public void beforeEach() {
40
+        validator = spy(new TransitionValidator());
41
+        animatorCreator = spy(new TransitionAnimatorCreator() {
42
+            @Override
43
+            protected List<PropertyAnimatorCreator> getAnimators(Element from, Element to) {
44
+                return Collections.EMPTY_LIST;
45
+            }
46
+        });
47
+        uut = new ElementTransitionManager(validator, animatorCreator);
48
+        Activity activity = newActivity();
49
+        from1 = createElement(activity, "from1Id");
50
+        to1 = createElement(activity, "to1Id");
51
+        validTransition = createTransition(from1, to1);
52
+        invalidTransition = createTransition(from1, to1); invalidTransition.toId = new Text("nonexistentElement");
53
+    }
54
+
55
+    @Test
56
+    public void createElementTransitions_returnsOnEmptyTransitions() {
57
+        Collection<? extends Animator> result = uut.createTransitions(
58
+                new Transitions(),
59
+                Collections.singletonList(from1),
60
+                Collections.singletonList(to1)
61
+        );
62
+        assertThat(result).isEmpty();
63
+    }
64
+
65
+    @Test
66
+    public void createElementTransitions_returnsIfNoElements() {
67
+        Collection<? extends Animator> result = uut.createTransitions(
68
+                new Transitions(Collections.singletonList(validTransition)),
69
+                Collections.EMPTY_LIST,
70
+                Collections.EMPTY_LIST
71
+        );
72
+        assertThat(result).isEmpty();
73
+    }
74
+
75
+    @Test
76
+    public void createElementTransitions_returnsIfNoMatchingElements() {
77
+        Transition invalidTransition = new Transition();
78
+        invalidTransition.fromId = new Text("from1Id");
79
+        invalidTransition.toId = new Text("nonExistentElement");
80
+        Transitions transitions = new Transitions(Collections.singletonList(invalidTransition));
81
+
82
+        Collection<? extends Animator> result = uut.createTransitions(
83
+                transitions,
84
+                Collections.singletonList(from1),
85
+                Collections.singletonList(to1)
86
+        );
87
+        verify(validator).validate(eq(invalidTransition), any(), any());
88
+        assertThat(result).isEmpty();
89
+    }
90
+
91
+    @Test
92
+    public void createElementTransitions_delegatesAnimatorCreationToCreator() {
93
+        uut.createTransitions(
94
+                new Transitions(Arrays.asList(validTransition, invalidTransition)),
95
+                Collections.singletonList(from1),
96
+                Collections.singletonList(to1)
97
+        );
98
+        verify(animatorCreator).create(any(List.class), any(Map.class), any(Map.class));
99
+    }
100
+}

+ 83
- 0
lib/android/app/src/test/java/com/reactnativenavigation/views/element/TransitionAnimatorCreatorTest.java 查看文件

@@ -0,0 +1,83 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.app.Activity;
4
+import android.view.View;
5
+
6
+import com.reactnativenavigation.BaseTest;
7
+import com.reactnativenavigation.parse.Transition;
8
+import com.reactnativenavigation.views.element.animators.PropertyAnimatorCreator;
9
+
10
+import org.junit.Test;
11
+import org.mockito.Mockito;
12
+
13
+import java.util.Arrays;
14
+import java.util.List;
15
+
16
+import static com.reactnativenavigation.utils.CollectionUtils.map;
17
+import static com.reactnativenavigation.views.element.TransitionTestUtils.createElement;
18
+import static com.reactnativenavigation.views.element.TransitionTestUtils.createTransition;
19
+import static org.mockito.Mockito.spy;
20
+import static org.mockito.Mockito.times;
21
+import static org.mockito.Mockito.verify;
22
+import static org.mockito.Mockito.verifyNoMoreInteractions;
23
+import static org.mockito.Mockito.when;
24
+
25
+public class TransitionAnimatorCreatorTest extends BaseTest {
26
+    private TransitionAnimatorCreator uut;
27
+    private Transition t1;
28
+    private Transition t2;
29
+    private Element e1;
30
+    private Element e2;
31
+    private Element e3;
32
+    private Element e4;
33
+    private PropertyAnimatorCreator animator1 = Mockito.mock(PropertyAnimatorCreator.class);
34
+    private PropertyAnimatorCreator animator2 = Mockito.mock(PropertyAnimatorCreator.class);
35
+    private PropertyAnimatorCreator animator3 = Mockito.mock(PropertyAnimatorCreator.class);
36
+
37
+    @Override
38
+    public void beforeEach() {
39
+        when(animator1.shouldAnimateProperty()).thenReturn(true);
40
+        when(animator2.shouldAnimateProperty()).thenReturn(true);
41
+        when(animator3.shouldAnimateProperty()).thenReturn(false);
42
+        uut = new TransitionAnimatorCreator() {
43
+            @Override
44
+            protected List<PropertyAnimatorCreator> getAnimators(Element from, Element to) {
45
+                return Arrays.asList(animator1, animator2, animator3);
46
+            }
47
+        };
48
+        Activity activity = newActivity();
49
+        e1 = createElement(activity); e2 = createElement(activity); e3 = createElement(activity); e4 = createElement(activity);
50
+        t1 = createTransition(e1, e2);
51
+        t2 = createTransition(e3, e4);
52
+    }
53
+
54
+    @Test
55
+    public void createAnimatorsForEachTransition() {
56
+        TransitionAnimatorCreator spy = spy(uut);
57
+        spy.create(
58
+                Arrays.asList(t1, t2),
59
+                map(Arrays.asList(e1, e3), Element::getElementId),
60
+                map(Arrays.asList(e2, e4), Element::getElementId)
61
+        );
62
+        verify(spy).create(t1, e1, e2);
63
+        verify(spy).create(t2, e3, e4);
64
+    }
65
+
66
+    @Test
67
+    public void create_animatorsAreCreated() {
68
+        List<Transition> transitions = Arrays.asList(t1, t2);
69
+        uut.create(
70
+                transitions,
71
+                map(Arrays.asList(e1, e3), Element::getElementId),
72
+                map(Arrays.asList(e2, e4), Element::getElementId)
73
+        );
74
+        verify(animator1, times(2)).shouldAnimateProperty();
75
+        verify(animator2, times(2)).shouldAnimateProperty();
76
+        verify(animator3, times(2)).shouldAnimateProperty();
77
+        for (Transition transition : transitions) {
78
+            verify(animator1).create(transition);
79
+            verify(animator2).create(transition);
80
+        }
81
+        verifyNoMoreInteractions(animator3);
82
+    }
83
+}

+ 32
- 0
lib/android/app/src/test/java/com/reactnativenavigation/views/element/TransitionTestUtils.java 查看文件

@@ -0,0 +1,32 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.app.Activity;
4
+
5
+import com.reactnativenavigation.parse.Transition;
6
+import com.reactnativenavigation.parse.params.Number;
7
+import com.reactnativenavigation.parse.params.Text;
8
+
9
+import java.util.UUID;
10
+
11
+public class TransitionTestUtils {
12
+    private static final int DURATION = 250;
13
+
14
+    static Transition createTransition(Element from, Element to) {
15
+        Transition transition = new Transition();
16
+        transition.duration = new Number(DURATION);
17
+        transition.fromId = new Text(from.getElementId());
18
+        transition.toId = new Text(to.getElementId());
19
+        return transition;
20
+    }
21
+
22
+    static Element createElement(Activity activity) {
23
+        return createElement(activity, UUID.randomUUID().toString());
24
+    }
25
+
26
+    static Element createElement(Activity activity, String id) {
27
+        Element e = new Element(activity);
28
+        e.setElementId(id);
29
+        return e;
30
+    }
31
+
32
+}

+ 110
- 0
lib/android/app/src/test/java/com/reactnativenavigation/views/element/TransitionValidatorTest.java 查看文件

@@ -0,0 +1,110 @@
1
+package com.reactnativenavigation.views.element;
2
+
3
+import android.app.Activity;
4
+
5
+import com.reactnativenavigation.BaseTest;
6
+import com.reactnativenavigation.parse.Transition;
7
+import com.reactnativenavigation.parse.params.NullNumber;
8
+import com.reactnativenavigation.parse.params.NullText;
9
+import com.reactnativenavigation.parse.params.Number;
10
+import com.reactnativenavigation.parse.params.Text;
11
+
12
+import org.junit.Test;
13
+
14
+import java.util.Collections;
15
+
16
+import static com.reactnativenavigation.utils.CollectionUtils.map;
17
+import static org.assertj.core.api.Java6Assertions.assertThat;
18
+
19
+public class TransitionValidatorTest extends BaseTest {
20
+    public static final String NONEXISTENT_ELEMENT = "nonexistentElement";
21
+    private TransitionValidator uut;
22
+    private Element from1;
23
+    private Element to1;
24
+    private Transition transition;
25
+    private Transition invalidFromElementTransition;
26
+    private Transition invalidToElementTransition;
27
+
28
+    @Override
29
+    public void beforeEach() {
30
+        uut = new TransitionValidator();
31
+        Activity activity = newActivity();
32
+        from1 = new Element(activity); from1.setElementId("e1");
33
+        to1 = new Element(activity); to1.setElementId("e2");
34
+
35
+        transition = createTransition(from1.getElementId(), to1.getElementId());
36
+        invalidFromElementTransition = createTransition(NONEXISTENT_ELEMENT, to1.getElementId());
37
+        invalidToElementTransition = createTransition(from1.getElementId(), NONEXISTENT_ELEMENT);
38
+    }
39
+
40
+    private Transition createTransition(String fromId, String toId) {
41
+        Transition transition = new Transition();
42
+        transition.duration = new Number(100);
43
+        transition.fromId = new Text(fromId);
44
+        transition.toId = new Text(toId);
45
+        return transition;
46
+    }
47
+
48
+    @Test
49
+    public void validate_falseIfNullToId() {
50
+        transition.toId = new NullText();
51
+        boolean result = uut.validate(
52
+                transition,
53
+                map(Collections.singletonList(from1), Element::getElementId),
54
+                map(Collections.singletonList(from1), Element::getElementId)
55
+        );
56
+        assertThat(result).isFalse();
57
+    }
58
+
59
+    @Test
60
+    public void validate_falseIfNullFromId() {
61
+        transition.fromId = new NullText();
62
+        boolean result = uut.validate(
63
+                transition,
64
+                map(Collections.singletonList(from1), Element::getElementId),
65
+                map(Collections.singletonList(from1), Element::getElementId)
66
+        );
67
+        assertThat(result).isFalse();
68
+    }
69
+
70
+    @Test
71
+    public void validate_falseIfFromElementRequiredByTransitionDontExist() {
72
+        boolean result = uut.validate(
73
+                invalidFromElementTransition,
74
+                map(Collections.singletonList(from1), Element::getElementId),
75
+                map(Collections.singletonList(to1), Element::getElementId)
76
+        );
77
+        assertThat(result).isFalse();
78
+    }
79
+
80
+    @Test
81
+    public void validate_falseIfToElementRequiredByTransitionDontExist() {
82
+        boolean result = uut.validate(
83
+                invalidToElementTransition,
84
+                map(Collections.singletonList(from1), Element::getElementId),
85
+                map(Collections.singletonList(to1), Element::getElementId)
86
+        );
87
+        assertThat(result).isFalse();
88
+    }
89
+
90
+    @Test
91
+    public void validate_trueIfElementsRequiredByTransitionExist() {
92
+        boolean result = uut.validate(
93
+                transition,
94
+                map(Collections.singletonList(from1), Element::getElementId),
95
+                map(Collections.singletonList(to1), Element::getElementId)
96
+        );
97
+        assertThat(result).isTrue();
98
+    }
99
+
100
+    @Test
101
+    public void validate_hasDuration() {
102
+        transition.duration = new NullNumber();
103
+        boolean result = uut.validate(
104
+                transition,
105
+                map(Collections.singletonList(from1), Element::getElementId),
106
+                map(Collections.singletonList(to1), Element::getElementId)
107
+        );
108
+        assertThat(result).isFalse();
109
+    }
110
+}

+ 12
- 3
lib/src/adapters/Element.tsx 查看文件

@@ -1,13 +1,14 @@
1 1
 import * as React from 'react';
2 2
 import * as PropTypes from 'prop-types';
3
-import { requireNativeComponent } from 'react-native';
3
+import { View, requireNativeComponent } from 'react-native';
4 4
 
5 5
 let RNNElement: React.ComponentType<any>;
6 6
 
7 7
 export class Element extends React.Component<{ elementId: any; resizeMode?: any; }, any> {
8 8
   static propTypes = {
9 9
     elementId: PropTypes.string.isRequired,
10
-    resizeMode: PropTypes.string
10
+    resizeMode: PropTypes.string,
11
+    ...View.propTypes
11 12
   };
12 13
 
13 14
   static defaultProps = {
@@ -21,4 +22,12 @@ export class Element extends React.Component<{ elementId: any; resizeMode?: any;
21 22
   }
22 23
 }
23 24
 
24
-RNNElement = requireNativeComponent('RNNElement', Element);
25
+RNNElement = requireNativeComponent(
26
+  'RNNElement',
27
+  Element,
28
+  {
29
+    nativeOnly: {
30
+      nativeID: true
31
+    }
32
+  }
33
+);

+ 8
- 7
package.json 查看文件

@@ -53,32 +53,33 @@
53 53
     "react-native": "*"
54 54
   },
55 55
   "dependencies": {
56
+    "hoist-non-react-statics": "2.x.x",
56 57
     "lodash": "4.x.x",
57 58
     "prop-types": "15.x.x",
58
-    "react-lifecycles-compat": "2.0.0",
59
-    "hoist-non-react-statics": "2.x.x"
59
+    "react-lifecycles-compat": "2.0.0"
60 60
   },
61 61
   "devDependencies": {
62
+    "@types/jest": "22.x.x",
63
+    "@types/lodash": "4.x.x",
62 64
     "@types/react": "16.x.x",
63 65
     "@types/react-native": "0.51.x",
64
-    "@types/lodash": "4.x.x",
65 66
     "@types/react-test-renderer": "16.x.x",
66
-    "@types/jest": "22.x.x",
67 67
     "detox": "7.x.x",
68
+    "handlebars": "4.x.x",
68 69
     "jest": "22.x.x",
69 70
     "react": "16.2.0",
70 71
     "react-native": "0.51.x",
72
+    "react-native-view-overflow": "0.0.3",
71 73
     "react-redux": "5.x.x",
72 74
     "react-test-renderer": "16.0.0-alpha.12",
73 75
     "redux": "3.x.x",
74 76
     "remx": "2.x.x",
75 77
     "semver": "5.x.x",
76 78
     "shell-utils": "1.x.x",
79
+    "ts-node": "5.x.x",
77 80
     "tslint": "5.x.x",
78
-    "typescript": "2.9.x",
79 81
     "typedoc": "0.x.x",
80
-    "handlebars": "4.x.x",
81
-    "ts-node": "5.x.x"
82
+    "typescript": "2.9.x"
82 83
   },
83 84
   "babel": {
84 85
     "env": {

+ 1
- 0
playground/android/app/build.gradle 查看文件

@@ -63,6 +63,7 @@ dependencies {
63 63
     //noinspection GradleDynamicVersion
64 64
     implementation 'com.facebook.react:react-native:+'
65 65
     implementation project(':react-native-navigation')
66
+    implementation project(':react-native-view-overflow')
66 67
 
67 68
     androidTestImplementation(project(':detox'))
68 69
     androidTestImplementation 'junit:junit:4.12'

+ 5
- 1
playground/android/app/src/main/java/com/reactnativenavigation/playground/MainApplication.java 查看文件

@@ -2,6 +2,7 @@ package com.reactnativenavigation.playground;
2 2
 
3 3
 import android.support.annotation.Nullable;
4 4
 
5
+import com.entria.views.RNViewOverflowPackage;
5 6
 import com.facebook.react.ReactNativeHost;
6 7
 import com.facebook.react.ReactPackage;
7 8
 import com.facebook.react.uimanager.UIImplementationProvider;
@@ -9,6 +10,7 @@ import com.reactnativenavigation.NavigationApplication;
9 10
 import com.reactnativenavigation.react.NavigationReactNativeHost;
10 11
 import com.reactnativenavigation.react.SyncUiImplementation;
11 12
 
13
+import java.util.ArrayList;
12 14
 import java.util.List;
13 15
 
14 16
 public class MainApplication extends NavigationApplication {
@@ -42,6 +44,8 @@ public class MainApplication extends NavigationApplication {
42 44
     @Nullable
43 45
     @Override
44 46
     public List<ReactPackage> createAdditionalReactPackages() {
45
-        return null;
47
+        List<ReactPackage> packages = new ArrayList<>();
48
+        packages.add(new RNViewOverflowPackage());
49
+        return packages;
46 50
     }
47 51
 }

+ 1
- 0
playground/android/build.gradle 查看文件

@@ -26,5 +26,6 @@ allprojects {
26 26
             url "$rootDir/../../node_modules/react-native/android"
27 27
         }
28 28
         maven { url 'https://jitpack.io' }
29
+        maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
29 30
     }
30 31
 }

+ 3
- 0
playground/android/settings.gradle 查看文件

@@ -6,3 +6,6 @@ project(':react-native-navigation').projectDir = new File(rootProject.projectDir
6 6
 
7 7
 include ':detox'
8 8
 project(':detox').projectDir = new File(rootProject.projectDir, '../../node_modules/detox/android/detox')
9
+
10
+include ':react-native-view-overflow'
11
+project(':react-native-view-overflow').projectDir = new File(rootProject.projectDir, '../../node_modules/react-native-view-overflow/android')

+ 1
- 0
playground/src/app.js 查看文件

@@ -155,6 +155,7 @@ function start() {
155 155
             {
156 156
               component: {
157 157
                 name: 'navigation.playground.WelcomeScreen'
158
+                // name: 'navigation.playground.CustomTransitionOrigin'
158 159
               }
159 160
             }
160 161
           ]

+ 25
- 5
playground/src/screens/CustomTransitionDestination.js 查看文件

@@ -2,6 +2,7 @@ const React = require('react');
2 2
 const { Component } = require('react');
3 3
 const { View, TouchableOpacity, Image, Text } = require('react-native');
4 4
 const { Navigation } = require('react-native-navigation');
5
+import ViewOverflow from 'react-native-view-overflow';
5 6
 
6 7
 class CustomTransitionDestination extends Component {
7 8
   constructor(props) {
@@ -14,7 +15,7 @@ class CustomTransitionDestination extends Component {
14 15
     return {
15 16
       topBar: {
16 17
         title: {
17
-          text: 'ye babyyyyyy',
18
+          text: 'Shared Element Transition',
18 19
           fontFamily: 'HelveticaNeue-Italic'
19 20
         },
20 21
         backButton: {
@@ -23,6 +24,17 @@ class CustomTransitionDestination extends Component {
23 24
         largeTitle: {
24 25
           visible: false
25 26
         }
27
+      },
28
+      animations: {
29
+        pop: {
30
+          content: {
31
+            alpha: {
32
+              from: 1,
33
+              to: 0,
34
+              duration: 250
35
+            }
36
+          }
37
+        }
26 38
       }
27 39
     };
28 40
   }
@@ -54,14 +66,22 @@ class CustomTransitionDestination extends Component {
54 66
           <Navigation.Element resizeMode={'contain'} elementId={'customDestinationImage'}>
55 67
             <Image resizeMode={'contain'} style={{ width: 300, height: 300 }} source={require('../../img/400.jpeg')} />
56 68
           </Navigation.Element>
57
-          <Navigation.Element elementId={'customDestinationImage2'}>
58
-            <Image style={{ width: 100, height: 100 }} source={require('../../img/2048.jpeg')} />
59
-          </Navigation.Element>
69
+
70
+          <View style={{ width: 120, height: 120, margin: 15, alignItems: 'center', justifyContent: 'center' }}>
71
+            <Navigation.Element elementId={'customDestinationImage2'} style={{ width: 100, height: 100, zIndex: 1 }}>
72
+              <Image resizeMode={'contain'} style={{ width: 100, height: 100 }} source={require('../../img/2048.jpeg')} />
73
+            </Navigation.Element>
74
+            <ViewOverflow>
75
+              <Navigation.Element elementId='image2bgDestination' style={{ width: 120, height: 120, marginTop: -110 }}>
76
+                <View style={{ width: 120, height: 120, backgroundColor: 'yellow' }} />
77
+              </Navigation.Element>
78
+            </ViewOverflow>
79
+          </View>
60 80
         </View>
61 81
 
62 82
         <TouchableOpacity testID={'shared_image2'} onPress={this.pop}>
63 83
           <Navigation.Element elementId={'title2'}>
64
-            <Text style={styles.h1}>{`Custom Transition Screen`}</Text>
84
+            <Text style={[{color: 'red', textAlign: 'center'}, styles.h1]}>{`Custom Transition Screen`}</Text>
65 85
           </Navigation.Element>
66 86
         </TouchableOpacity>
67 87
         <Navigation.Element elementId={'customDestinationParagraph'}>

+ 34
- 10
playground/src/screens/CustomTransitionOrigin.js 查看文件

@@ -2,7 +2,7 @@ const React = require('react');
2 2
 const { Component } = require('react');
3 3
 const { View, Text, Image, TouchableOpacity } = require('react-native');
4 4
 const { Navigation } = require('react-native-navigation');
5
-
5
+import ViewOverflow from 'react-native-view-overflow';
6 6
 class CustomTransitionOrigin extends Component {
7 7
   constructor(props) {
8 8
     super(props);
@@ -26,31 +26,41 @@ class CustomTransitionOrigin extends Component {
26 26
     return (
27 27
       <View style={styles.root}>
28 28
         <Navigation.Element elementId='title1'>
29
-          <Text style={styles.h1}>Custom Transition Screen</Text>
29
+          <Text style={[styles.h1, { color: 'black' }]}>Custom Transition Screen</Text>
30 30
         </Navigation.Element>
31
+
31 32
         <View style={{ flex: 1, justifyContent: 'flex-start' }}>
32 33
           <TouchableOpacity testID='shared_image1' activeOpacity={0.5} onPress={this.onClickNavigationIcon}>
33 34
             <Navigation.Element resizeMode='cover' elementId='image1'>
34 35
               <Image resizeMode='cover' style={styles.gyroImage} source={require('../../img/400.jpeg')} />
35 36
             </Navigation.Element>
36 37
           </TouchableOpacity>
37
-          <TouchableOpacity activeOpacity={0.5} onPress={this.onClickNavigationIcon}>
38
-            <Navigation.Element elementId='image2'>
39
-              <Image style={styles.gyroImage} source={require('../../img/2048.jpeg')} />
40
-            </Navigation.Element>
38
+
39
+          <TouchableOpacity style={{ width: 110 }} activeOpacity={0.5} onPress={this.onClickNavigationIcon}>
40
+            <View style={{ width: 100, height: 100, marginTop: 10, marginBottom: 30 }}>
41
+              <Navigation.Element elementId='image2' style={{ zIndex: 1 }}>
42
+                <Image style={styles.gyroImage} source={require('../../img/2048.jpeg')} />
43
+              </Navigation.Element>
44
+              <ViewOverflow>
45
+                <Navigation.Element elementId='image2bg' style={{ width: 100, height: 100, marginLeft: 10, marginTop: -90 }}>
46
+                  <View style={{ width: 100, height: 100, backgroundColor: 'purple' }} />
47
+                </Navigation.Element>
48
+              </ViewOverflow>
49
+            </View>
41 50
           </TouchableOpacity>
51
+
42 52
           <TouchableOpacity activeOpacity={0.5} onPress={this.onClickNavigationIcon}>
43 53
             <Navigation.Element elementId='image3'>
44 54
               <Image style={styles.gyroImage} source={require('../../img/Icon-87.png')} />
45 55
             </Navigation.Element>
46 56
           </TouchableOpacity>
57
+
47 58
           <TouchableOpacity activeOpacity={0.5} onPress={this.onClickNavigationIcon}>
48 59
             <Navigation.Element elementId='image4'>
49 60
               <Image style={styles.gyroImage} source={require('../../img/Icon-87.png')} />
50 61
             </Navigation.Element>
51 62
           </TouchableOpacity>
52 63
         </View>
53
-
54 64
       </View>
55 65
     );
56 66
   }
@@ -59,11 +69,26 @@ class CustomTransitionOrigin extends Component {
59 69
       component: {
60 70
         name: 'navigation.playground.CustomTransitionDestination',
61 71
         options: {
72
+          animations: {
73
+            push: {
74
+              waitForRender: true,
75
+              content: {
76
+                alpha: {
77
+                  from: 0,
78
+                  to: 1,
79
+                  duration: 250
80
+                }
81
+              }
82
+            }
83
+          },
62 84
           customTransition: {
63 85
             animations: [
64 86
               { type: 'sharedElement', fromId: 'title1', toId: 'title2', startDelay: 0, springVelocity: 0.2, duration: 0.5 },
65
-              { type: 'sharedElement', fromId: 'image1', toId: 'customDestinationImage', startDelay: 0, springVelocity: 0.9,
66
-              springDamping: 0.9, duration: 0.8, interactivePop: true },
87
+              {
88
+                type: 'sharedElement', fromId: 'image1', toId: 'customDestinationImage', startDelay: 0, springVelocity: 0.9,
89
+                springDamping: 0.9, duration: 0.8, interactivePop: true
90
+              },
91
+              { type: 'sharedElement', fromId: 'image2bg', toId: 'image2bgDestination', startDelay: 0, duration: 0.8 },
67 92
               { type: 'sharedElement', fromId: 'image2', toId: 'customDestinationImage2', startDelay: 0, duration: 0.8 },
68 93
               { fromId: 'image4', x: { to: 50 }, y: { to: 50 }, endAlpha: 0, startDelay: 0, duration: 0.8, springVelocity: 0.5 },
69 94
               { fromId: 'customDestinationParagraph', startY: 50, startAlpha: 0, endAlpha: 1, startDelay: 0, duration: 0.8 }
@@ -84,7 +109,6 @@ const styles = {
84 109
     backgroundColor: '#f5fcff'
85 110
   },
86 111
   h1: {
87
-
88 112
     fontSize: 24,
89 113
     textAlign: 'center',
90 114
     marginTop: 100

+ 2
- 2
playground/src/screens/OptionsScreen.js 查看文件

@@ -130,7 +130,7 @@ class OptionsScreen extends Component {
130 130
         <Button title='Top Bar Transparent' onPress={this.onClickTopBarTransparent} />
131 131
         <Button title='Top Bar Opaque' onPress={this.onClickTopBarOpaque} />
132 132
         <Button title='scrollView Screen' testID={testIDs.SCROLLVIEW_SCREEN_BUTTON} onPress={this.onClickScrollViewScreen} />
133
-        <Button title='Custom Transition' testID={testIDs.CUSTOM_TRANSITION_BUTTON} onPress={this.onClickCustomTranstition} />
133
+        <Button title='Custom Transition' testID={testIDs.CUSTOM_TRANSITION_BUTTON} onPress={this.onClickCustomTransition} />
134 134
         {Platform.OS === 'android' && <Button title='Hide fab' testID={testIDs.HIDE_FAB} onPress={this.onClickFab} />}
135 135
         <Button title='Show overlay' testID={testIDs.SHOW_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(true)} />
136 136
         <Button title='Show touch through overlay' testID={testIDs.SHOW_TOUCH_THROUGH_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(false)} />
@@ -207,7 +207,7 @@ class OptionsScreen extends Component {
207 207
     });
208 208
   }
209 209
 
210
-  onClickCustomTranstition = () => {
210
+  onClickCustomTransition = () => {
211 211
     Navigation.push(this.props.componentId, {
212 212
       component: {
213 213
         name: 'navigation.playground.CustomTransitionOrigin'