Daniel Zlotin 6 years ago
parent
commit
9d18904db7

+ 63
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/BaseAnimator.java View File

@@ -0,0 +1,63 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+import android.animation.AnimatorSet;
5
+import android.animation.ObjectAnimator;
6
+import android.content.Context;
7
+import android.support.annotation.NonNull;
8
+import android.support.annotation.RestrictTo;
9
+import android.view.View;
10
+import android.view.animation.AccelerateInterpolator;
11
+import android.view.animation.DecelerateInterpolator;
12
+
13
+import com.reactnativenavigation.parse.AnimationsOptions;
14
+import com.reactnativenavigation.utils.UiUtils;
15
+
16
+class BaseAnimator {
17
+
18
+    AnimationsOptions options = new AnimationsOptions();
19
+
20
+    private static final int DURATION = 300;
21
+    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
22
+    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
23
+
24
+    private float translationY;
25
+
26
+    BaseAnimator(Context context) {
27
+        translationY = UiUtils.getWindowHeight(context);
28
+    }
29
+
30
+    @NonNull
31
+    AnimatorSet getDefaultPushAnimation(View view) {
32
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
33
+        alpha.setInterpolator(DECELERATE_INTERPOLATOR);
34
+
35
+        AnimatorSet set = new AnimatorSet();
36
+        ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0);
37
+        translationY.setInterpolator(DECELERATE_INTERPOLATOR);
38
+        translationY.setDuration(DURATION);
39
+        alpha.setDuration(DURATION);
40
+        set.playTogether(translationY, alpha);
41
+        return set;
42
+    }
43
+
44
+
45
+    @NonNull
46
+    AnimatorSet getDefaultPopAnimation(View view) {
47
+        AnimatorSet set = new AnimatorSet();
48
+
49
+        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
50
+        alpha.setInterpolator(ACCELERATE_INTERPOLATOR);
51
+
52
+        ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY);
53
+        translationY.setInterpolator(ACCELERATE_INTERPOLATOR);
54
+        translationY.setDuration(DURATION);
55
+        alpha.setDuration(DURATION);
56
+        set.playTogether(translationY, alpha);
57
+        return set;
58
+    }
59
+
60
+    public void setOptions(AnimationsOptions options) {
61
+        this.options = options;
62
+    }
63
+}

+ 40
- 0
lib/android/app/src/main/java/com/reactnativenavigation/anim/ModalAnimator.java View File

@@ -0,0 +1,40 @@
1
+package com.reactnativenavigation.anim;
2
+
3
+
4
+import android.animation.AnimatorListenerAdapter;
5
+import android.animation.AnimatorSet;
6
+import android.content.Context;
7
+import android.view.View;
8
+
9
+import com.reactnativenavigation.parse.AnimationOptions;
10
+import com.reactnativenavigation.parse.AnimationsOptions;
11
+
12
+public class ModalAnimator extends BaseAnimator {
13
+
14
+    public ModalAnimator(Context context, AnimationsOptions options) {
15
+        super(context);
16
+        this.options = options;
17
+    }
18
+
19
+    public void animateShow(View contentView, AnimatorListenerAdapter listener) {
20
+        AnimatorSet animatorSet;
21
+        if (options.showModal.hasValue()) {
22
+            animatorSet = options.showModal.getAnimation(contentView);
23
+        } else {
24
+            animatorSet = getDefaultPushAnimation(contentView);
25
+        }
26
+        animatorSet.addListener(listener);
27
+        animatorSet.start();
28
+    }
29
+
30
+    public void animateDismiss(View contentView, AnimatorListenerAdapter listener) {
31
+        AnimatorSet animatorSet;
32
+        if (options.dismissModal.hasValue()) {
33
+            animatorSet = options.dismissModal.getAnimation(contentView);
34
+        } else {
35
+            animatorSet = getDefaultPopAnimation(contentView);
36
+        }
37
+        animatorSet.addListener(listener);
38
+        animatorSet.start();
39
+    }
40
+}

+ 2
- 42
lib/android/app/src/main/java/com/reactnativenavigation/anim/NavigationAnimator.java View File

@@ -15,17 +15,10 @@ import com.reactnativenavigation.parse.AnimationsOptions;
15 15
 import com.reactnativenavigation.utils.UiUtils;
16 16
 
17 17
 @SuppressWarnings("ResourceType")
18
-public class NavigationAnimator {
19
-
20
-    private AnimationsOptions options = new AnimationsOptions();
21
-
22
-    private static final int DURATION = 300;
23
-    private static final DecelerateInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
24
-    private static final AccelerateInterpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();
25
-    private float translationY;
18
+public class NavigationAnimator extends BaseAnimator {
26 19
 
27 20
     public NavigationAnimator(Context context) {
28
-        translationY = UiUtils.getWindowHeight(context);
21
+        super(context);
29 22
     }
30 23
 
31 24
     public void animatePush(final View view, @Nullable final AnimationListener animationListener) {
@@ -52,20 +45,6 @@ public class NavigationAnimator {
52 45
         set.start();
53 46
     }
54 47
 
55
-    @NonNull
56
-    private AnimatorSet getDefaultPushAnimation(View view) {
57
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
58
-        alpha.setInterpolator(DECELERATE_INTERPOLATOR);
59
-
60
-        AnimatorSet set = new AnimatorSet();
61
-        ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, this.translationY, 0);
62
-        translationY.setInterpolator(DECELERATE_INTERPOLATOR);
63
-        translationY.setDuration(DURATION);
64
-        alpha.setDuration(DURATION);
65
-        set.playTogether(translationY, alpha);
66
-        return set;
67
-    }
68
-
69 48
     public void animatePop(View view, @Nullable final AnimationListener animationListener) {
70 49
         AnimatorSet set;
71 50
         if (options.pop.hasValue()) {
@@ -83,23 +62,4 @@ public class NavigationAnimator {
83 62
         });
84 63
         set.start();
85 64
     }
86
-
87
-    @NonNull
88
-    private AnimatorSet getDefaultPopAnimation(View view) {
89
-        AnimatorSet set = new AnimatorSet();
90
-
91
-        ObjectAnimator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
92
-        alpha.setInterpolator(ACCELERATE_INTERPOLATOR);
93
-
94
-        ObjectAnimator translationY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, 0, this.translationY);
95
-        translationY.setInterpolator(ACCELERATE_INTERPOLATOR);
96
-        translationY.setDuration(DURATION);
97
-        alpha.setDuration(DURATION);
98
-        set.playTogether(translationY, alpha);
99
-        return set;
100
-    }
101
-
102
-    public void setOptions(AnimationsOptions options) {
103
-        this.options = options;
104
-    }
105 65
 }

+ 1
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/AnimationOptions.java View File

@@ -3,6 +3,7 @@ package com.reactnativenavigation.parse;
3 3
 
4 4
 import android.animation.Animator;
5 5
 import android.animation.AnimatorSet;
6
+import android.util.Log;
6 7
 import android.util.Property;
7 8
 import android.view.View;
8 9
 

+ 2
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/AnimationsOptions.java View File

@@ -1,6 +1,8 @@
1 1
 package com.reactnativenavigation.parse;
2 2
 
3 3
 
4
+import android.util.Log;
5
+
4 6
 import org.json.JSONObject;
5 7
 
6 8
 public class AnimationsOptions {

+ 1
- 1
lib/android/app/src/main/java/com/reactnativenavigation/parse/Options.java View File

@@ -17,7 +17,7 @@ public class Options implements DEFAULT_VALUES {
17 17
     @NonNull
18 18
     public static Options parse(TypefaceLoader typefaceManager, JSONObject json, @NonNull Options defaultOptions) {
19 19
         Options result = new Options();
20
-        if (json == null) return result;
20
+        if (json == null) return result.withDefaultOptions(defaultOptions);
21 21
 
22 22
         result.orientationOptions = OrientationOptions.parse(json);
23 23
         result.topBarOptions = TopBarOptions.parse(typefaceManager, json.optJSONObject("topBar"));

+ 42
- 34
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/ModalStack.java View File

@@ -14,7 +14,7 @@ import java.util.List;
14 14
 
15 15
 class ModalStack implements ModalListener {
16 16
 
17
-	private List<Modal> modals = new ArrayList<>();
17
+    private List<Modal> modals = new ArrayList<>();
18 18
     private ModalCreator creator;
19 19
     private ModalListener modalListener;
20 20
 
@@ -26,25 +26,20 @@ class ModalStack implements ModalListener {
26 26
     void showModal(final ViewController viewController, Promise promise) {
27 27
         Modal modal = creator.create(viewController, this);
28 28
         modals.add(modal);
29
-		modal.show();
29
+        modal.show();
30 30
         promise.resolve(viewController.getId());
31
-	}
32
-
33
-	void dismissModal(final String componentId, Promise promise) {
34
-		Modal modal = findModalByComponentId(componentId);
35
-		if (modal != null) {
36
-			modal.dismiss(promise);
37
-		} else {
38
-			Navigator.rejectPromise(promise);
39
-		}
40
-	}
41
-
42
-	void dismissAll(Promise promise) {
43
-		for (Modal modal : modals) {
44
-			modal.dismiss(size() == 1 ? promise : new NoOpPromise());
45
-		}
46
-		modals.clear();
47
-	}
31
+    }
32
+
33
+    void dismissModal(final String componentId, Promise promise) {
34
+        applyOnModal(componentId, (modal) -> modal.dismiss(promise), () -> Navigator.rejectPromise(promise));
35
+    }
36
+
37
+    void dismissAll(Promise promise) {
38
+        for (Modal modal : modals) {
39
+            modal.dismiss(size() == 1 ? promise : new NoOpPromise());
40
+        }
41
+        modals.clear();
42
+    }
48 43
 
49 44
     boolean isEmpty() {
50 45
         return modals.isEmpty();
@@ -54,17 +49,17 @@ class ModalStack implements ModalListener {
54 49
         return modals.size();
55 50
     }
56 51
 
57
-	@Nullable
52
+    @Nullable
58 53
     Modal findModalByComponentId(String componentId) {
59
-		for (Modal modal : modals) {
60
-			if (modal.containsDeepComponentId(componentId)) {
61
-				return modal;
62
-			}
63
-		}
64
-		return null;
65
-	}
66
-
67
-	@Nullable
54
+        for (Modal modal : modals) {
55
+            if (modal.containsDeepComponentId(componentId)) {
56
+                return modal;
57
+            }
58
+        }
59
+        return null;
60
+    }
61
+
62
+    @Nullable
68 63
     ViewController findControllerById(String id) {
69 64
         Modal modal = findModalByComponentId(id);
70 65
         return modal != null ? modal.viewController.findControllerById(id) : null;
@@ -74,7 +69,7 @@ class ModalStack implements ModalListener {
74 69
     public void onModalDismiss(Modal modal) {
75 70
         if (peek() == modal) {
76 71
             modals.remove(modal);
77
-            performOnModal(peek(), peek -> peek.viewController.onViewAppeared());
72
+            applyOnModal(peek(), peek -> peek.viewController.onViewAppeared(), null);
78 73
         } else {
79 74
             modals.remove(modal);
80 75
         }
@@ -90,11 +85,24 @@ class ModalStack implements ModalListener {
90 85
         return isEmpty() ? null : modals.get(modals.size() - 1);
91 86
     }
92 87
 
93
-    private void performOnModal(@Nullable Modal modal, Task<Modal> task) {
94
-        if (modal != null) task.run(modal);
95
-    }
96
-
97 88
     public boolean handleBack() {
98 89
         return !modals.isEmpty() && peek().handleBack();
99 90
     }
91
+
92
+    private void applyOnModal(String componentId, Task<Modal> accept, Runnable reject) {
93
+        Modal modal = findModalByComponentId(componentId);
94
+        if (modal != null) {
95
+            if (accept != null) accept.run(modal);
96
+        } else {
97
+            if (reject != null) reject.run();
98
+        }
99
+    }
100
+
101
+    private void applyOnModal(Modal modal, Task<Modal> accept, Runnable reject) {
102
+        if (modal != null) {
103
+            if (accept != null) accept.run(modal);
104
+        } else {
105
+            if (reject != null) reject.run();
106
+        }
107
+    }
100 108
 }

+ 19
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/modal/Modal.java View File

@@ -1,5 +1,7 @@
1 1
 package com.reactnativenavigation.viewcontrollers.modal;
2 2
 
3
+import android.animation.Animator;
4
+import android.animation.AnimatorListenerAdapter;
3 5
 import android.app.Dialog;
4 6
 import android.content.DialogInterface;
5 7
 import android.support.annotation.Nullable;
@@ -8,6 +10,7 @@ import android.view.View;
8 10
 
9 11
 import com.facebook.react.bridge.Promise;
10 12
 import com.reactnativenavigation.R;
13
+import com.reactnativenavigation.anim.ModalAnimator;
11 14
 import com.reactnativenavigation.viewcontrollers.ViewController;
12 15
 
13 16
 import static android.view.View.MeasureSpec.EXACTLY;
@@ -19,6 +22,8 @@ public class Modal implements DialogInterface.OnKeyListener, DialogInterface.OnD
19 22
     private ModalListener modalListener;
20 23
     @Nullable private Promise dismissPromise;
21 24
 
25
+    private ModalAnimator animator;
26
+
22 27
     public Modal(final ViewController viewController, ModalListener modalListener) {
23 28
         this.viewController = viewController;
24 29
         dialog = new Dialog(viewController.getActivity(), R.style.Modal);
@@ -26,17 +31,29 @@ public class Modal implements DialogInterface.OnKeyListener, DialogInterface.OnD
26 31
         dialog.setOnKeyListener(this);
27 32
         dialog.setOnDismissListener(this);
28 33
         dialog.setOnShowListener(this);
34
+        animator = new ModalAnimator(viewController.getActivity(), viewController.options.animationsOptions);
29 35
     }
30 36
 
31 37
     public void show() {
32 38
         preMeasureView();
33
-        dialog.setContentView(viewController.getView());
39
+        final View contentView = viewController.getView();
34 40
         dialog.show();
41
+        animator.animateShow(contentView, new AnimatorListenerAdapter() {
42
+            @Override
43
+            public void onAnimationStart(Animator animation) {
44
+                dialog.setContentView(contentView);
45
+            }
46
+        });
35 47
     }
36 48
 
37 49
     public void dismiss(Promise promise) {
38 50
         dismissPromise = promise;
39
-        dialog.dismiss();
51
+        animator.animateDismiss(viewController.getView(), new AnimatorListenerAdapter() {
52
+            @Override
53
+            public void onAnimationEnd(Animator animation) {
54
+                dialog.dismiss();
55
+            }
56
+        });
40 57
     }
41 58
 
42 59
     public boolean containsDeepComponentId(String componentId) {

+ 3
- 2
lib/android/app/src/main/res/values/styles.xml View File

@@ -3,10 +3,11 @@
3 3
 
4 4
     <style name="Modal" parent="Theme.AppCompat.Light.NoActionBar">
5 5
         <item name="android:windowAnimationStyle">@style/modalAnimations</item>
6
+        <item name="android:windowBackground">@android:color/transparent</item>
6 7
     </style>
7 8
 
8 9
     <style name="modalAnimations">
9
-        <item name="android:windowEnterAnimation">@anim/slide_up</item>
10
-        <item name="android:windowExitAnimation">@anim/slide_down</item>
10
+        <item name="android:windowEnterAnimation">@null</item>
11
+        <item name="android:windowExitAnimation">@null</item>
11 12
     </style>
12 13
 </resources>

+ 6
- 2
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/ModalStackTest.java View File

@@ -103,8 +103,12 @@ public class ModalStackTest extends BaseTest {
103 103
     public void onDismiss() throws Exception {
104 104
         uut.showModal(viewController, new MockPromise());
105 105
         uut.showModal(new SimpleViewController(newActivity(), "otherComponent", new Options()), new MockPromise());
106
-        uut.dismissAll(new MockPromise());
107
-        verify(uut, times(2)).onModalDismiss(any());
106
+        uut.dismissAll(new MockPromise() {
107
+            @Override
108
+            public void resolve(@Nullable Object value) {
109
+                verify(uut, times(2)).onModalDismiss(any());
110
+            }
111
+        });
108 112
     }
109 113
 
110 114
     @Test