浏览代码

Fix Topbar custom component flicker across screens #3864 (#5110)

When a topBar.backgroundComponent is defined, check if a corresponding ReactView has already been created with matching componentName and componentId. If such view is found, reuse it instead of creating another view.

This change will fix a flickering in the TopBar background component when a screen was pushed into a stack with a background component.
Fabrizio Rizzonelli 5 年前
父节点
当前提交
99032e060d

+ 3
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/params/Text.java 查看文件

@@ -1,10 +1,13 @@
1 1
 package com.reactnativenavigation.parse.params;
2 2
 
3
+import android.support.annotation.NonNull;
4
+
3 5
 public class Text extends Param<String> {
4 6
     public Text(String value) {
5 7
         super(value);
6 8
     }
7 9
 
10
+    @NonNull
8 11
     @Override
9 12
     public String toString() {
10 13
         return hasValue() ? value : "No Value";

+ 19
- 8
lib/android/app/src/main/java/com/reactnativenavigation/presentation/StackPresenter.java 查看文件

@@ -45,10 +45,7 @@ import java.util.LinkedHashMap;
45 45
 import java.util.List;
46 46
 import java.util.Map;
47 47
 
48
-import static com.reactnativenavigation.utils.CollectionUtils.filter;
49
-import static com.reactnativenavigation.utils.CollectionUtils.forEach;
50
-import static com.reactnativenavigation.utils.CollectionUtils.keyBy;
51
-import static com.reactnativenavigation.utils.CollectionUtils.merge;
48
+import static com.reactnativenavigation.utils.CollectionUtils.*;
52 49
 import static com.reactnativenavigation.utils.ObjectUtils.perform;
53 50
 
54 51
 public class StackPresenter {
@@ -205,8 +202,9 @@ public class StackPresenter {
205 202
         topBar.setBackgroundColor(options.background.color.get(Color.WHITE));
206 203
 
207 204
         if (options.background.component.hasValue()) {
208
-            if (backgroundControllers.containsKey(component)) {
209
-                topBar.setBackgroundComponent(backgroundControllers.get(component).getView());
205
+            View createdComponent = findBackgroundComponent(options.background.component);
206
+            if (createdComponent != null) {
207
+                topBar.setBackgroundComponent(createdComponent);
210 208
             } else {
211 209
                 TopBarBackgroundViewController controller = new TopBarBackgroundViewController(activity, topBarBackgroundViewCreator);
212 210
                 controller.setWaitForRender(options.background.waitForRender);
@@ -215,6 +213,8 @@ public class StackPresenter {
215 213
                 controller.getView().setLayoutParams(new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
216 214
                 topBar.setBackgroundComponent(controller.getView());
217 215
             }
216
+        } else {
217
+            topBar.clearBackgroundComponent();
218 218
         }
219 219
 
220 220
         if (options.testId.hasValue()) topBar.setTestId(options.testId.get());
@@ -233,6 +233,17 @@ public class StackPresenter {
233 233
         }
234 234
     }
235 235
 
236
+    @Nullable
237
+    private View findBackgroundComponent(com.reactnativenavigation.parse.Component component) {
238
+        for (TopBarBackgroundViewController controller : backgroundControllers.values()) {
239
+            if (ObjectUtils.equalsNotNull(controller.getComponent().name.get(null), component.name.get(null)) &&
240
+                ObjectUtils.equalsNotNull(controller.getComponent().componentId.get(null), component.componentId.get(null))) {
241
+                return controller.getView();
242
+            }
243
+        }
244
+        return null;
245
+    }
246
+
236 247
     private void setInitialTopBarVisibility(TopBarOptions options) {
237 248
         if (options.visible.isFalse()) {
238 249
             topBar.hide();
@@ -268,7 +279,7 @@ public class StackPresenter {
268 279
             componentRightButtons.put(child, keyBy(rightButtonControllers, TitleBarButtonController::getButtonInstanceId));
269 280
             topBar.setRightButtons(rightButtonControllers);
270 281
         } else {
271
-            topBar.setRightButtons(null);
282
+            topBar.clearRightButtons();
272 283
         }
273 284
 
274 285
         if (leftButtons != null) {
@@ -276,7 +287,7 @@ public class StackPresenter {
276 287
             componentLeftButtons.put(child, keyBy(leftButtonControllers, TitleBarButtonController::getButtonInstanceId));
277 288
             topBar.setLeftButtons(leftButtonControllers);
278 289
         } else {
279
-            topBar.setLeftButtons(null);
290
+            topBar.clearLeftButtons();
280 291
         }
281 292
 
282 293
         if (options.buttons.back.visible.isTrue() && !options.buttons.hasLeftButtons()) {

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

@@ -17,4 +17,8 @@ public class ObjectUtils {
17 17
     public static boolean notNull(Object o) {
18 18
         return o != null;
19 19
     }
20
+
21
+    public static <T> boolean equalsNotNull(@Nullable T a, @Nullable T b) {
22
+        return a != null && a.equals(b);
23
+    }
20 24
 }

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

@@ -132,18 +132,6 @@ public class StackController extends ParentController<StackLayout> {
132 132
         );
133 133
     }
134 134
 
135
-    @Override
136
-    public void destroy() {
137
-        topBarController.clear();
138
-        super.destroy();
139
-    }
140
-
141
-    @Override
142
-    public void clearOptions() {
143
-        super.clearOptions();
144
-        topBarController.clear();
145
-    }
146
-
147 135
     @Override
148 136
     public void onChildDestroyed(Component child) {
149 137
         super.onChildDestroyed(child);

+ 4
- 0
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarBackgroundViewController.java 查看文件

@@ -45,4 +45,8 @@ public class TopBarBackgroundViewController extends ViewController<TopBarBackgro
45 45
     public void setComponent(Component component) {
46 46
         this.component = component;
47 47
     }
48
+
49
+    public Component getComponent() {
50
+        return component;
51
+    }
48 52
 }

+ 0
- 6
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/topbar/TopBarController.java 查看文件

@@ -24,12 +24,6 @@ public class TopBarController {
24 24
         return new TopBar(context, stackLayout);
25 25
     }
26 26
 
27
-    public void clear() {
28
-        if (topBar != null) {
29
-            topBar.clear();
30
-        }
31
-    }
32
-
33 27
     public TopBar getView() {
34 28
         return topBar;
35 29
     }

+ 11
- 2
lib/android/app/src/main/java/com/reactnativenavigation/views/topbar/TopBar.java 查看文件

@@ -36,6 +36,7 @@ import com.reactnativenavigation.views.StackLayout;
36 36
 import com.reactnativenavigation.views.titlebar.TitleBar;
37 37
 import com.reactnativenavigation.views.toptabs.TopTabs;
38 38
 
39
+import java.util.Collections;
39 40
 import java.util.List;
40 41
 
41 42
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -174,6 +175,7 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
174 175
     }
175 176
 
176 177
     public void setBackgroundComponent(View component) {
178
+        if (this.component == component || component.getParent() != null) return;
177 179
         this.component = component;
178 180
         root.addView(component, 0);
179 181
     }
@@ -208,10 +210,18 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
208 210
         titleBar.setLeftButtons(leftButtons);
209 211
     }
210 212
 
213
+    public void clearLeftButtons() {
214
+        titleBar.setLeftButtons(Collections.emptyList());
215
+    }
216
+
211 217
     public void setRightButtons(List<TitleBarButtonController> rightButtons) {
212 218
         titleBar.setRightButtons(rightButtons);
213 219
     }
214 220
 
221
+    public void clearRightButtons() {
222
+        titleBar.setRightButtons(Collections.emptyList());
223
+    }
224
+
215 225
     public void setElevation(Double elevation) {
216 226
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && getElevation() != elevation.floatValue()) {
217 227
             this.elevation = UiUtils.dpToPx(getContext(), elevation.floatValue());
@@ -271,12 +281,11 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
271 281
         animator.hide(options, onAnimationEnd);
272 282
     }
273 283
 
274
-    public void clear() {
284
+    public void clearBackgroundComponent() {
275 285
         if (component != null) {
276 286
             root.removeView(component);
277 287
             component = null;
278 288
         }
279
-        titleBar.clear();
280 289
     }
281 290
 
282 291
     public void clearTopTabs() {

+ 14
- 0
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/StackPresenterTest.java 查看文件

@@ -36,6 +36,7 @@ import com.reactnativenavigation.views.topbar.TopBar;
36 36
 import org.json.JSONObject;
37 37
 import org.junit.Test;
38 38
 import org.mockito.ArgumentCaptor;
39
+import org.mockito.Mockito;
39 40
 
40 41
 import java.util.ArrayList;
41 42
 import java.util.Collection;
@@ -354,6 +355,19 @@ public class StackPresenterTest extends BaseTest {
354 355
         assertThat(leftCaptor.getValue().get(0)).isNotEqualTo(leftButton);
355 356
     }
356 357
 
358
+    @Test
359
+    public void applyTopBarOptions_backgroundComponentIsCreatedOnceIfNameAndIdAreEqual() {
360
+        Options o = new Options();
361
+        o.topBar.background.component.name = new Text("comp");
362
+        o.topBar.background.component.componentId = new Text("compId");
363
+
364
+        uut.applyChildOptions(o, Mockito.mock(com.reactnativenavigation.views.Component.class));
365
+        assertThat(uut.getBackgroundComponents().size()).isOne();
366
+
367
+        uut.applyChildOptions(o, Mockito.mock(com.reactnativenavigation.views.Component.class));
368
+        assertThat(uut.getBackgroundComponents().size()).isOne();
369
+    }
370
+
357 371
     @Test
358 372
     public void mergeChildOptions_buttonColorIsResolvedFromAppliedOptions() {
359 373
         Options appliedOptions = new Options();

+ 0
- 62
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/TopBarControllerTest.java 查看文件

@@ -1,62 +0,0 @@
1
-package com.reactnativenavigation.viewcontrollers;
2
-
3
-import android.app.Activity;
4
-import android.content.Context;
5
-import android.support.annotation.NonNull;
6
-
7
-import com.reactnativenavigation.BaseTest;
8
-import com.reactnativenavigation.viewcontrollers.topbar.TopBarController;
9
-import com.reactnativenavigation.views.StackLayout;
10
-import com.reactnativenavigation.views.titlebar.TitleBar;
11
-import com.reactnativenavigation.views.topbar.TopBar;
12
-
13
-import org.junit.Test;
14
-import org.mockito.Mockito;
15
-
16
-import static org.mockito.Mockito.mock;
17
-import static org.mockito.Mockito.spy;
18
-import static org.mockito.Mockito.times;
19
-import static org.mockito.Mockito.verify;
20
-
21
-public class TopBarControllerTest extends BaseTest {
22
-
23
-    private TopBarController uut;
24
-
25
-    @Override
26
-    public void beforeEach() {
27
-        uut = new TopBarController();
28
-    }
29
-
30
-    @Test
31
-    public void clear() {
32
-        final TitleBar[] titleBar = new TitleBar[1];
33
-        uut = new TopBarController() {
34
-            @NonNull
35
-            @Override
36
-            protected TopBar createTopBar(Context context, StackLayout stackLayout) {
37
-                return new TopBar(context, stackLayout) {
38
-                    @Override
39
-                    protected TitleBar createTitleBar(Context context) {
40
-                        titleBar[0] = spy(super.createTitleBar(context));
41
-                        return titleBar[0];
42
-                    }
43
-                };
44
-            }
45
-        };
46
-        Activity activity = newActivity();
47
-        uut.createView(activity, Mockito.mock(StackLayout.class));
48
-        uut.clear();
49
-        verify(titleBar[0], times(1)).clear();
50
-    }
51
-
52
-    @Test
53
-    public void destroy() {
54
-        uut.createView(newActivity(), mock(StackLayout.class));
55
-        uut.clear();
56
-    }
57
-
58
-    @Test
59
-    public void destroy_canBeCalledBeforeViewIsCreated() {
60
-        uut.clear();
61
-    }
62
-}

+ 0
- 6
lib/android/app/src/test/java/com/reactnativenavigation/viewcontrollers/stack/StackControllerTest.java 查看文件

@@ -1036,12 +1036,6 @@ public class StackControllerTest extends BaseTest {
1036 1036
         verify(presenter).applyChildOptions(any(), eq(component));
1037 1037
     }
1038 1038
 
1039
-    @Test
1040
-    public void destroy() {
1041
-        uut.destroy();
1042
-        verify(topBarController, times(1)).clear();
1043
-    }
1044
-
1045 1039
     private void assertContainsOnlyId(String... ids) {
1046 1040
         assertThat(uut.size()).isEqualTo(ids.length);
1047 1041
         assertThat(uut.getChildControllers()).extracting((Extractor<ViewController, String>) ViewController::getId).containsOnly(ids);