@capgo/capacitor-native-navigation 8.0.24 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -13
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigationPlugin.java +525 -38
- package/dist/docs.json +74 -0
- package/dist/esm/components.js +6 -1
- package/dist/esm/components.js.map +1 -1
- package/dist/esm/definitions.d.ts +38 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.js +3 -3
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +9 -4
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +9 -4
- package/dist/plugin.js.map +1 -1
- package/docs/native-liquid-glass-android.webp +0 -0
- package/docs/native-liquid-glass-ios.webp +0 -0
- package/ios/Sources/NativeNavigationPlugin/NativeNavigationPlugin.swift +49 -4
- package/ios/Tests/NativeNavigationPluginTests/NativeNavigationTests.swift +8 -0
- package/package.json +4 -3
|
@@ -18,16 +18,20 @@ import android.graphics.drawable.Drawable;
|
|
|
18
18
|
import android.graphics.drawable.GradientDrawable;
|
|
19
19
|
import android.graphics.drawable.StateListDrawable;
|
|
20
20
|
import android.os.Build;
|
|
21
|
+
import android.view.ContextThemeWrapper;
|
|
21
22
|
import android.view.Gravity;
|
|
22
23
|
import android.view.Menu;
|
|
23
24
|
import android.view.MenuItem;
|
|
24
25
|
import android.view.View;
|
|
25
26
|
import android.view.ViewGroup;
|
|
26
27
|
import android.view.ViewOutlineProvider;
|
|
28
|
+
import android.view.ViewTreeObserver;
|
|
27
29
|
import android.view.Window;
|
|
28
30
|
import android.view.WindowInsets;
|
|
29
31
|
import android.widget.FrameLayout;
|
|
30
32
|
import android.widget.ImageView;
|
|
33
|
+
import android.widget.TextView;
|
|
34
|
+
import androidx.annotation.RequiresApi;
|
|
31
35
|
import androidx.appcompat.content.res.AppCompatResources;
|
|
32
36
|
import androidx.appcompat.widget.Toolbar;
|
|
33
37
|
import androidx.core.graphics.PathParser;
|
|
@@ -60,16 +64,32 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
60
64
|
private static final int DEFAULT_NAVBAR_DP = 56;
|
|
61
65
|
// Android uses Material bottom navigation metrics; web/iOS keep their own platform-native tab bar heights.
|
|
62
66
|
private static final int DEFAULT_TABBAR_DP = 80;
|
|
67
|
+
private static final int LIQUID_GLASS_TABBAR_DP = 64;
|
|
68
|
+
private static final int LIQUID_GLASS_TABBAR_MAX_WIDTH_DP = 300;
|
|
69
|
+
private static final int LIQUID_GLASS_TABBAR_SIDE_MARGIN_DP = 30;
|
|
70
|
+
private static final int LIQUID_GLASS_TABBAR_CONTENT_TOP_PADDING_DP = 8;
|
|
71
|
+
private static final int LIQUID_GLASS_TABBAR_CONTENT_TRANSLATION_Y_DP = 6;
|
|
72
|
+
private static final int LIQUID_GLASS_TABBAR_LABEL_TRANSLATION_Y_DP = 3;
|
|
73
|
+
private static final int LIQUID_GLASS_TABBAR_BADGE_VERTICAL_OFFSET_DP = 2;
|
|
63
74
|
private static final int TABBAR_ICON_DP = 24;
|
|
64
75
|
private static final int TABBAR_ITEM_VERTICAL_PADDING_DP = 8;
|
|
76
|
+
private static final int LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP = 4;
|
|
65
77
|
private static final int TABBAR_INDICATOR_DP = 32;
|
|
78
|
+
private static final int LIQUID_GLASS_TABBAR_INDICATOR_DP = 28;
|
|
66
79
|
private static final int TABBAR_INDICATOR_LABEL_PADDING_DP = 4;
|
|
80
|
+
private static final int LIQUID_GLASS_TABBAR_INDICATOR_LABEL_PADDING_DP = 6;
|
|
81
|
+
private static final int TABBAR_FLOATING_BOTTOM_GAP_DP = 10;
|
|
82
|
+
private static final int LIQUID_GLASS_TABBAR_FLOATING_BOTTOM_GAP_DP = 24;
|
|
67
83
|
private static final int DEFAULT_TRANSITION_MS = 350;
|
|
68
84
|
private static final int MENU_ITEM_BASE = 10_000;
|
|
69
85
|
|
|
70
86
|
private final NativeNavigation implementation = new NativeNavigation();
|
|
71
87
|
private FrameLayout navbarContainer;
|
|
72
88
|
private FrameLayout tabbarContainer;
|
|
89
|
+
private GlassBackdropView navbarGlassBackdrop;
|
|
90
|
+
private View navbarGlassSurface;
|
|
91
|
+
private GlassBackdropView tabbarGlassBackdrop;
|
|
92
|
+
private View tabbarGlassSurface;
|
|
73
93
|
private Toolbar toolbar;
|
|
74
94
|
private BottomNavigationView tabbar;
|
|
75
95
|
private ImageView transitionSnapshot;
|
|
@@ -81,10 +101,19 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
81
101
|
private int activeTransitionMs = DEFAULT_TRANSITION_MS;
|
|
82
102
|
private int tintColor = Color.rgb(0, 122, 255);
|
|
83
103
|
private int inactiveTintColor = Color.rgb(120, 126, 137);
|
|
104
|
+
private GlassOptions defaultGlassOptions = GlassOptions.defaults();
|
|
105
|
+
private GlassOptions navbarGlassOptions = GlassOptions.defaults();
|
|
106
|
+
private GlassOptions tabbarGlassOptions = GlassOptions.defaults();
|
|
107
|
+
private JSObject navbarGlassConfig;
|
|
108
|
+
private JSObject tabbarGlassConfig;
|
|
109
|
+
private int navbarBackgroundColor = Color.argb(225, 255, 255, 255);
|
|
110
|
+
private int tabbarBackgroundColor = Color.argb(235, 255, 255, 255);
|
|
84
111
|
private String activeTransitionId;
|
|
85
112
|
private String activeTransitionDirection = "forward";
|
|
86
113
|
private RectF activeZoomSourceFrame;
|
|
87
114
|
private float activeZoomCornerRadius = 0f;
|
|
115
|
+
private Drawable activeTransitionRootBackground;
|
|
116
|
+
private boolean activeTransitionRootBackgroundCaptured = false;
|
|
88
117
|
private final Map<Integer, String> menuActionIds = new HashMap<>();
|
|
89
118
|
private final Map<Integer, String> menuActionTitles = new HashMap<>();
|
|
90
119
|
private final Map<Integer, String> menuActionPlacements = new HashMap<>();
|
|
@@ -104,6 +133,9 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
104
133
|
runOnUiThread(() -> {
|
|
105
134
|
enabled = call.getBoolean("enabled", true);
|
|
106
135
|
contentInsetMode = call.getString("contentInsetMode", contentInsetMode);
|
|
136
|
+
defaultGlassOptions = GlassOptions.from(call.getObject("glass", null), defaultGlassOptions);
|
|
137
|
+
navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
|
|
138
|
+
tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
|
|
107
139
|
Double duration = call.getDouble("animationDuration");
|
|
108
140
|
if (duration != null) {
|
|
109
141
|
defaultTransitionMs = Math.max(0, duration.intValue());
|
|
@@ -119,6 +151,12 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
119
151
|
tabbarContainer.setVisibility(View.GONE);
|
|
120
152
|
}
|
|
121
153
|
}
|
|
154
|
+
if (enabled) {
|
|
155
|
+
reapplyVisibleChromeBackgrounds();
|
|
156
|
+
if (tabbar != null) {
|
|
157
|
+
applyTabbarMetrics(tabbar);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
122
160
|
updateInsetsAndNotify();
|
|
123
161
|
call.resolve(insetsResult());
|
|
124
162
|
});
|
|
@@ -166,6 +204,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
166
204
|
|
|
167
205
|
addToolbarItems(nativeToolbar, call.getArray("rightItems", new JSArray()), "right");
|
|
168
206
|
JSObject colors = call.getObject("colors", new JSObject());
|
|
207
|
+
navbarGlassConfig = call.getObject("glass", null);
|
|
208
|
+
navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
|
|
169
209
|
applyToolbarColors(nativeToolbar, colors);
|
|
170
210
|
navbarContainer.setVisibility(View.VISIBLE);
|
|
171
211
|
layoutChrome();
|
|
@@ -214,6 +254,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
214
254
|
JSONArray tabs = call.getArray("tabs", new JSArray());
|
|
215
255
|
String selectedId = call.getString("selectedId", null);
|
|
216
256
|
JSObject colors = call.getObject("colors", new JSObject());
|
|
257
|
+
tabbarGlassConfig = call.getObject("glass", null);
|
|
258
|
+
tabbarGlassOptions = GlassOptions.from(tabbarGlassConfig, defaultGlassOptions);
|
|
217
259
|
Integer badgeBackground = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", null);
|
|
218
260
|
Integer badgeText = colorOption(call, colors, "badgeTextColor", "badgeText", null);
|
|
219
261
|
for (int index = 0; index < tabs.length(); index++) {
|
|
@@ -233,6 +275,9 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
233
275
|
if (tab.has("badge")) {
|
|
234
276
|
nativeTabbar.removeBadge(itemId);
|
|
235
277
|
BadgeDrawable badge = nativeTabbar.getOrCreateBadge(itemId);
|
|
278
|
+
if (isLiquidGlassTabbar()) {
|
|
279
|
+
badge.setVerticalOffset(dp(LIQUID_GLASS_TABBAR_BADGE_VERTICAL_OFFSET_DP));
|
|
280
|
+
}
|
|
236
281
|
if (badgeBackground != null) {
|
|
237
282
|
badge.setBackgroundColor(badgeBackground);
|
|
238
283
|
}
|
|
@@ -295,8 +340,11 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
295
340
|
|
|
296
341
|
if (transitionSnapshot != null) {
|
|
297
342
|
root.removeView(transitionSnapshot);
|
|
343
|
+
restoreTransitionRootBackground();
|
|
298
344
|
}
|
|
299
345
|
|
|
346
|
+
int transitionSurface = transitionSurfaceColor(webView);
|
|
347
|
+
prepareTransitionRootBackground(root, transitionSurface);
|
|
300
348
|
Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888);
|
|
301
349
|
webView.draw(new Canvas(bitmap));
|
|
302
350
|
if (zoomSourceRect != null) {
|
|
@@ -305,6 +353,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
305
353
|
}
|
|
306
354
|
transitionSnapshot = new ImageView(getContext());
|
|
307
355
|
transitionSnapshot.setImageBitmap(bitmap);
|
|
356
|
+
transitionSnapshot.setBackgroundColor(transitionSurface);
|
|
308
357
|
transitionSnapshot.setScaleType(ImageView.ScaleType.FIT_XY);
|
|
309
358
|
FrameLayout.LayoutParams params =
|
|
310
359
|
activeZoomSourceFrame == null
|
|
@@ -372,8 +421,9 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
372
421
|
snapshotEndTranslation = -width * 0.3f;
|
|
373
422
|
}
|
|
374
423
|
|
|
424
|
+
boolean stationaryTransition = isStationaryTransition(direction);
|
|
375
425
|
webView.setTranslationX(startTranslation);
|
|
376
|
-
webView.setAlpha(
|
|
426
|
+
webView.setAlpha(stationaryTransition ? 1f : 0.01f);
|
|
377
427
|
View snapshot = transitionSnapshot;
|
|
378
428
|
JSObject event = transitionEvent(transitionId, direction, durationMs);
|
|
379
429
|
Runnable finish = () -> {
|
|
@@ -381,6 +431,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
381
431
|
if (root != null && transitionSnapshot != null) {
|
|
382
432
|
root.removeView(transitionSnapshot);
|
|
383
433
|
}
|
|
434
|
+
restoreTransitionRootBackground();
|
|
384
435
|
transitionSnapshot = null;
|
|
385
436
|
activeTransitionId = null;
|
|
386
437
|
activeZoomSourceFrame = null;
|
|
@@ -400,7 +451,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
400
451
|
snapshot
|
|
401
452
|
.animate()
|
|
402
453
|
.translationX(snapshotEndTranslation)
|
|
403
|
-
.alpha(
|
|
454
|
+
.alpha(stationaryTransition ? 0f : 0.75f)
|
|
404
455
|
.setDuration(durationMs)
|
|
405
456
|
.withEndAction(finish)
|
|
406
457
|
.start();
|
|
@@ -437,6 +488,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
437
488
|
if (root != null && transitionSnapshot != null) {
|
|
438
489
|
root.removeView(transitionSnapshot);
|
|
439
490
|
}
|
|
491
|
+
restoreTransitionRootBackground();
|
|
440
492
|
transitionSnapshot = null;
|
|
441
493
|
activeTransitionId = null;
|
|
442
494
|
activeZoomSourceFrame = null;
|
|
@@ -595,6 +647,10 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
595
647
|
FrameLayout root = contentRoot();
|
|
596
648
|
navbarContainer = new FrameLayout(getContext());
|
|
597
649
|
navbarContainer.setElevation(dp(8));
|
|
650
|
+
navbarGlassBackdrop = new GlassBackdropView(getContext());
|
|
651
|
+
navbarGlassSurface = new View(getContext());
|
|
652
|
+
navbarGlassBackdrop.setVisibility(View.GONE);
|
|
653
|
+
navbarGlassSurface.setVisibility(View.GONE);
|
|
598
654
|
toolbar = new Toolbar(getContext());
|
|
599
655
|
toolbar.setPopupTheme(androidx.appcompat.R.style.ThemeOverlay_AppCompat_Light);
|
|
600
656
|
toolbar.setOnMenuItemClickListener((item) -> {
|
|
@@ -607,6 +663,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
607
663
|
return true;
|
|
608
664
|
});
|
|
609
665
|
|
|
666
|
+
navbarContainer.addView(navbarGlassBackdrop);
|
|
667
|
+
navbarContainer.addView(navbarGlassSurface);
|
|
610
668
|
navbarContainer.addView(toolbar);
|
|
611
669
|
if (root != null) {
|
|
612
670
|
root.addView(navbarContainer);
|
|
@@ -626,26 +684,34 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
626
684
|
FrameLayout root = contentRoot();
|
|
627
685
|
tabbarContainer = new FrameLayout(getContext());
|
|
628
686
|
tabbarContainer.setElevation(dp(12));
|
|
687
|
+
tabbarContainer.setClipChildren(false);
|
|
688
|
+
tabbarContainer.setClipToPadding(false);
|
|
689
|
+
tabbarGlassBackdrop = new GlassBackdropView(getContext());
|
|
690
|
+
tabbarGlassSurface = new View(getContext());
|
|
691
|
+
tabbarGlassBackdrop.setVisibility(View.GONE);
|
|
692
|
+
tabbarGlassSurface.setVisibility(View.GONE);
|
|
629
693
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
public void getOutline(View view, Outline outline) {
|
|
635
|
-
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), view.getHeight() / 2f);
|
|
636
|
-
}
|
|
694
|
+
ViewOutlineProvider tabbarPillOutlineProvider = new ViewOutlineProvider() {
|
|
695
|
+
@Override
|
|
696
|
+
public void getOutline(View view, Outline outline) {
|
|
697
|
+
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), view.getHeight() / 2f);
|
|
637
698
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
699
|
+
};
|
|
700
|
+
tabbarContainer.setClipToOutline(false);
|
|
701
|
+
tabbarContainer.setOutlineProvider(tabbarPillOutlineProvider);
|
|
702
|
+
tabbarGlassBackdrop.setClipToOutline(true);
|
|
703
|
+
tabbarGlassBackdrop.setOutlineProvider(tabbarPillOutlineProvider);
|
|
704
|
+
tabbarGlassSurface.setClipToOutline(true);
|
|
705
|
+
tabbarGlassSurface.setOutlineProvider(tabbarPillOutlineProvider);
|
|
706
|
+
}
|
|
707
|
+
tabbar = new BottomNavigationView(
|
|
708
|
+
new ContextThemeWrapper(getContext(), com.google.android.material.R.style.Theme_MaterialComponents_DayNight)
|
|
709
|
+
);
|
|
642
710
|
tabbar.setElevation(0);
|
|
643
|
-
tabbar.
|
|
711
|
+
tabbar.setClipChildren(false);
|
|
712
|
+
tabbar.setClipToPadding(false);
|
|
644
713
|
tabbar.setItemIconSize(dp(TABBAR_ICON_DP));
|
|
645
|
-
tabbar
|
|
646
|
-
tabbar.setItemPaddingBottom(dp(TABBAR_ITEM_VERTICAL_PADDING_DP));
|
|
647
|
-
tabbar.setItemActiveIndicatorHeight(dp(TABBAR_INDICATOR_DP));
|
|
648
|
-
tabbar.setActiveIndicatorLabelPadding(dp(TABBAR_INDICATOR_LABEL_PADDING_DP));
|
|
714
|
+
applyTabbarMetrics(tabbar);
|
|
649
715
|
tabbar.setBackgroundColor(Color.TRANSPARENT);
|
|
650
716
|
tabbar.setOnItemSelectedListener((item) -> {
|
|
651
717
|
int itemId = item.getItemId();
|
|
@@ -659,14 +725,13 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
659
725
|
notifyListeners("tabSelect", event);
|
|
660
726
|
return true;
|
|
661
727
|
});
|
|
728
|
+
tabbarContainer.addView(tabbarGlassBackdrop);
|
|
729
|
+
tabbarContainer.addView(tabbarGlassSurface);
|
|
662
730
|
tabbarContainer.addView(tabbar);
|
|
663
731
|
if (root != null) {
|
|
664
732
|
root.addView(tabbarContainer);
|
|
665
733
|
} else {
|
|
666
|
-
getActivity().addContentView(
|
|
667
|
-
tabbarContainer,
|
|
668
|
-
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(DEFAULT_TABBAR_DP))
|
|
669
|
-
);
|
|
734
|
+
getActivity().addContentView(tabbarContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, tabbarHeight()));
|
|
670
735
|
}
|
|
671
736
|
return tabbar;
|
|
672
737
|
}
|
|
@@ -1026,6 +1091,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1026
1091
|
: Color.rgb(20, 24, 32);
|
|
1027
1092
|
int tint = parseColor(colors.getString("tint", null), tintFallback);
|
|
1028
1093
|
int background = parseColor(colors.getString("background", null), backgroundFallback);
|
|
1094
|
+
navbarBackgroundColor = background;
|
|
1029
1095
|
int foreground = parseColor(colors.getString("foreground", null), foregroundFallback);
|
|
1030
1096
|
tintColor = tint;
|
|
1031
1097
|
nativeToolbar.setTitleTextColor(foreground);
|
|
@@ -1036,9 +1102,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1036
1102
|
tintedIcon.setTint(tint);
|
|
1037
1103
|
nativeToolbar.setNavigationIcon(tintedIcon);
|
|
1038
1104
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
}
|
|
1105
|
+
nativeToolbar.setBackgroundColor(Color.TRANSPARENT);
|
|
1106
|
+
applyChromeBackground(navbarContainer, navbarGlassBackdrop, navbarGlassSurface, background, navbarGlassOptions, 0f);
|
|
1042
1107
|
for (int index = 0; index < nativeToolbar.getMenu().size(); index++) {
|
|
1043
1108
|
Drawable icon = nativeToolbar.getMenu().getItem(index).getIcon();
|
|
1044
1109
|
if (icon != null) {
|
|
@@ -1057,6 +1122,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1057
1122
|
int tint = parseColor(colors.getString("tint", null), tintFallback);
|
|
1058
1123
|
int inactiveTint = parseColor(colors.getString("inactiveTint", null), inactiveFallback);
|
|
1059
1124
|
int background = parseColor(colors.getString("background", null), backgroundFallback);
|
|
1125
|
+
tabbarBackgroundColor = background;
|
|
1060
1126
|
tintColor = tint;
|
|
1061
1127
|
inactiveTintColor = inactiveTint;
|
|
1062
1128
|
int[][] states = new int[][] { new int[] { android.R.attr.state_checked }, new int[] {} };
|
|
@@ -1070,12 +1136,89 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1070
1136
|
Integer ripple = colorOption(call, colors, "rippleColor", "ripple", null);
|
|
1071
1137
|
nativeTabbar.setItemRippleColor(ripple == null ? null : ColorStateList.valueOf(ripple));
|
|
1072
1138
|
nativeTabbar.setBackgroundColor(Color.TRANSPARENT);
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1139
|
+
applyTabbarMetrics(nativeTabbar);
|
|
1140
|
+
applyChromeBackground(
|
|
1141
|
+
tabbarContainer,
|
|
1142
|
+
tabbarGlassBackdrop,
|
|
1143
|
+
tabbarGlassSurface,
|
|
1144
|
+
background,
|
|
1145
|
+
tabbarGlassOptions,
|
|
1146
|
+
tabbarCornerRadius()
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
private void reapplyVisibleChromeBackgrounds() {
|
|
1151
|
+
if (navbarContainer != null && navbarContainer.getVisibility() == View.VISIBLE) {
|
|
1152
|
+
applyChromeBackground(navbarContainer, navbarGlassBackdrop, navbarGlassSurface, navbarBackgroundColor, navbarGlassOptions, 0f);
|
|
1078
1153
|
}
|
|
1154
|
+
if (tabbarContainer != null && tabbarContainer.getVisibility() == View.VISIBLE) {
|
|
1155
|
+
applyChromeBackground(
|
|
1156
|
+
tabbarContainer,
|
|
1157
|
+
tabbarGlassBackdrop,
|
|
1158
|
+
tabbarGlassSurface,
|
|
1159
|
+
tabbarBackgroundColor,
|
|
1160
|
+
tabbarGlassOptions,
|
|
1161
|
+
tabbarCornerRadius()
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
private void applyChromeBackground(
|
|
1167
|
+
ViewGroup container,
|
|
1168
|
+
GlassBackdropView backdrop,
|
|
1169
|
+
View surface,
|
|
1170
|
+
int background,
|
|
1171
|
+
GlassOptions glassOptions,
|
|
1172
|
+
float cornerRadius
|
|
1173
|
+
) {
|
|
1174
|
+
if (container == null) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
GlassOptions resolvedGlassOptions = glassOptions == null ? GlassOptions.defaults() : glassOptions;
|
|
1178
|
+
if (!resolvedGlassOptions.isLiquidGlass()) {
|
|
1179
|
+
hideGlassBackground(backdrop, surface);
|
|
1180
|
+
container.setBackground(chromeBackgroundDrawable(background, cornerRadius));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
container.setBackgroundColor(Color.TRANSPARENT);
|
|
1185
|
+
int surfaceColor = glassSurfaceColor(background, resolvedGlassOptions);
|
|
1186
|
+
if (surface != null) {
|
|
1187
|
+
surface.setBackground(chromeBackgroundDrawable(surfaceColor, cornerRadius));
|
|
1188
|
+
surface.setVisibility(View.VISIBLE);
|
|
1189
|
+
}
|
|
1190
|
+
if (backdrop != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
1191
|
+
View webView = getBridge() == null ? null : getBridge().getWebView();
|
|
1192
|
+
backdrop.configure(webView, dp(resolvedGlassOptions.blurRadiusDp), surfaceColor);
|
|
1193
|
+
backdrop.setVisibility(View.VISIBLE);
|
|
1194
|
+
} else if (backdrop != null) {
|
|
1195
|
+
backdrop.clearEffect();
|
|
1196
|
+
backdrop.setVisibility(View.GONE);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
private void hideGlassBackground(GlassBackdropView backdrop, View surface) {
|
|
1201
|
+
if (backdrop != null) {
|
|
1202
|
+
backdrop.clearEffect();
|
|
1203
|
+
backdrop.setVisibility(View.GONE);
|
|
1204
|
+
}
|
|
1205
|
+
if (surface != null) {
|
|
1206
|
+
surface.setBackground(null);
|
|
1207
|
+
surface.setVisibility(View.GONE);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
private Drawable chromeBackgroundDrawable(int color, float cornerRadius) {
|
|
1212
|
+
GradientDrawable drawable = new GradientDrawable();
|
|
1213
|
+
drawable.setColor(color);
|
|
1214
|
+
if (cornerRadius > 0f) {
|
|
1215
|
+
drawable.setCornerRadius(cornerRadius);
|
|
1216
|
+
}
|
|
1217
|
+
return drawable;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
private int glassSurfaceColor(int background, GlassOptions glassOptions) {
|
|
1221
|
+
return withAlpha(background, Math.round(Color.alpha(background) * (float) glassOptions.surfaceAlpha));
|
|
1079
1222
|
}
|
|
1080
1223
|
|
|
1081
1224
|
private int parseColor(String value, int fallback) {
|
|
@@ -1141,6 +1284,91 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1141
1284
|
return mode == Configuration.UI_MODE_NIGHT_YES;
|
|
1142
1285
|
}
|
|
1143
1286
|
|
|
1287
|
+
private void fillContainer(View view) {
|
|
1288
|
+
if (view != null) {
|
|
1289
|
+
view.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
private boolean isLiquidGlassTabbar() {
|
|
1294
|
+
return tabbarGlassOptions != null && tabbarGlassOptions.isLiquidGlass();
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
private int tabbarHeight() {
|
|
1298
|
+
return dp(isLiquidGlassTabbar() ? LIQUID_GLASS_TABBAR_DP : DEFAULT_TABBAR_DP);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
private float tabbarCornerRadius() {
|
|
1302
|
+
return tabbarHeight() / 2f;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
private int tabbarWidth(int rootWidth) {
|
|
1306
|
+
if (isLiquidGlassTabbar()) {
|
|
1307
|
+
return Math.min(Math.max(0, rootWidth - dp(LIQUID_GLASS_TABBAR_SIDE_MARGIN_DP * 2)), dp(LIQUID_GLASS_TABBAR_MAX_WIDTH_DP));
|
|
1308
|
+
}
|
|
1309
|
+
return Math.min(Math.max(0, rootWidth - dp(48)), dp(420));
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
private int floatingTabbarBottomMargin(int navigationInset) {
|
|
1313
|
+
if (isLiquidGlassTabbar()) {
|
|
1314
|
+
return Math.max(dp(LIQUID_GLASS_TABBAR_FLOATING_BOTTOM_GAP_DP), navigationInset);
|
|
1315
|
+
}
|
|
1316
|
+
return navigationInset + dp(TABBAR_FLOATING_BOTTOM_GAP_DP);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
private void applyTabbarMetrics(BottomNavigationView nativeTabbar) {
|
|
1320
|
+
boolean liquidGlass = isLiquidGlassTabbar();
|
|
1321
|
+
nativeTabbar.setMinimumHeight(tabbarHeight());
|
|
1322
|
+
nativeTabbar.setPadding(0, liquidGlass ? dp(LIQUID_GLASS_TABBAR_CONTENT_TOP_PADDING_DP) : 0, 0, 0);
|
|
1323
|
+
nativeTabbar.setItemPaddingTop(dp(liquidGlass ? LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP : TABBAR_ITEM_VERTICAL_PADDING_DP));
|
|
1324
|
+
nativeTabbar.setItemPaddingBottom(dp(liquidGlass ? LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP : TABBAR_ITEM_VERTICAL_PADDING_DP));
|
|
1325
|
+
nativeTabbar.setItemActiveIndicatorHeight(dp(liquidGlass ? LIQUID_GLASS_TABBAR_INDICATOR_DP : TABBAR_INDICATOR_DP));
|
|
1326
|
+
nativeTabbar.setActiveIndicatorLabelPadding(
|
|
1327
|
+
dp(liquidGlass ? LIQUID_GLASS_TABBAR_INDICATOR_LABEL_PADDING_DP : TABBAR_INDICATOR_LABEL_PADDING_DP)
|
|
1328
|
+
);
|
|
1329
|
+
alignTabbarMenu(nativeTabbar, liquidGlass);
|
|
1330
|
+
nativeTabbar.post(() -> alignTabbarMenu(nativeTabbar, isLiquidGlassTabbar()));
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
private void alignTabbarMenu(BottomNavigationView nativeTabbar, boolean liquidGlass) {
|
|
1334
|
+
nativeTabbar.setClipChildren(false);
|
|
1335
|
+
nativeTabbar.setClipToPadding(false);
|
|
1336
|
+
if (nativeTabbar.getChildCount() == 0) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
View menuView = nativeTabbar.getChildAt(0);
|
|
1340
|
+
ViewGroup.LayoutParams currentParams = menuView.getLayoutParams();
|
|
1341
|
+
FrameLayout.LayoutParams menuParams =
|
|
1342
|
+
currentParams instanceof FrameLayout.LayoutParams
|
|
1343
|
+
? (FrameLayout.LayoutParams) currentParams
|
|
1344
|
+
: new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
|
1345
|
+
menuParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
|
|
1346
|
+
menuParams.height = liquidGlass ? ViewGroup.LayoutParams.MATCH_PARENT : ViewGroup.LayoutParams.WRAP_CONTENT;
|
|
1347
|
+
menuParams.gravity = liquidGlass ? Gravity.CENTER : Gravity.TOP;
|
|
1348
|
+
menuView.setLayoutParams(menuParams);
|
|
1349
|
+
menuView.setTranslationY(liquidGlass ? dp(LIQUID_GLASS_TABBAR_CONTENT_TRANSLATION_Y_DP) : 0f);
|
|
1350
|
+
if (menuView instanceof ViewGroup) {
|
|
1351
|
+
ViewGroup menuGroup = (ViewGroup) menuView;
|
|
1352
|
+
menuGroup.setClipChildren(false);
|
|
1353
|
+
menuGroup.setClipToPadding(false);
|
|
1354
|
+
translateTabbarLabels(menuGroup, liquidGlass ? dp(LIQUID_GLASS_TABBAR_LABEL_TRANSLATION_Y_DP) : 0f);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
private void translateTabbarLabels(View view, float translationY) {
|
|
1359
|
+
if (view instanceof TextView) {
|
|
1360
|
+
view.setTranslationY(translationY);
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
if (!(view instanceof ViewGroup)) {
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
ViewGroup group = (ViewGroup) view;
|
|
1367
|
+
for (int index = 0; index < group.getChildCount(); index++) {
|
|
1368
|
+
translateTabbarLabels(group.getChildAt(index), translationY);
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1144
1372
|
private void layoutChrome() {
|
|
1145
1373
|
FrameLayout root = contentRoot();
|
|
1146
1374
|
if (root == null) {
|
|
@@ -1149,8 +1377,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1149
1377
|
int status = statusBarInset();
|
|
1150
1378
|
int bottom = navigationBarInset();
|
|
1151
1379
|
int navbarHeight = navbarVisible ? status + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1152
|
-
int tabbarHeight =
|
|
1153
|
-
int tabbarBottomMargin = tabbarVisible ? bottom
|
|
1380
|
+
int tabbarHeight = tabbarHeight();
|
|
1381
|
+
int tabbarBottomMargin = tabbarVisible ? floatingTabbarBottomMargin(bottom) : bottom;
|
|
1154
1382
|
|
|
1155
1383
|
if (navbarContainer != null) {
|
|
1156
1384
|
FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(
|
|
@@ -1159,6 +1387,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1159
1387
|
Gravity.TOP
|
|
1160
1388
|
);
|
|
1161
1389
|
navbarContainer.setLayoutParams(containerParams);
|
|
1390
|
+
fillContainer(navbarGlassBackdrop);
|
|
1391
|
+
fillContainer(navbarGlassSurface);
|
|
1162
1392
|
FrameLayout.LayoutParams toolbarParams = new FrameLayout.LayoutParams(
|
|
1163
1393
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1164
1394
|
dp(DEFAULT_NAVBAR_DP),
|
|
@@ -1170,7 +1400,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1170
1400
|
|
|
1171
1401
|
if (tabbarContainer != null) {
|
|
1172
1402
|
int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
|
|
1173
|
-
int tabbarWidth =
|
|
1403
|
+
int tabbarWidth = tabbarWidth(rootWidth);
|
|
1174
1404
|
FrameLayout.LayoutParams tabbarContainerParams = new FrameLayout.LayoutParams(
|
|
1175
1405
|
tabbarWidth,
|
|
1176
1406
|
tabbarHeight,
|
|
@@ -1178,18 +1408,23 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1178
1408
|
);
|
|
1179
1409
|
tabbarContainerParams.bottomMargin = tabbarBottomMargin;
|
|
1180
1410
|
tabbarContainer.setLayoutParams(tabbarContainerParams);
|
|
1411
|
+
fillContainer(tabbarGlassBackdrop);
|
|
1412
|
+
fillContainer(tabbarGlassSurface);
|
|
1181
1413
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
1182
1414
|
tabbarContainer.invalidateOutline();
|
|
1415
|
+
tabbarGlassBackdrop.invalidateOutline();
|
|
1416
|
+
tabbarGlassSurface.invalidateOutline();
|
|
1183
1417
|
}
|
|
1184
1418
|
}
|
|
1185
1419
|
|
|
1186
1420
|
if (tabbar != null) {
|
|
1421
|
+
applyTabbarMetrics(tabbar);
|
|
1187
1422
|
FrameLayout.LayoutParams tabbarParams = new FrameLayout.LayoutParams(
|
|
1188
1423
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1189
1424
|
ViewGroup.LayoutParams.MATCH_PARENT
|
|
1190
1425
|
);
|
|
1191
1426
|
tabbar.setLayoutParams(tabbarParams);
|
|
1192
|
-
tabbar.setPadding(0, 0, 0, 0);
|
|
1427
|
+
tabbar.setPadding(0, isLiquidGlassTabbar() ? dp(LIQUID_GLASS_TABBAR_CONTENT_TOP_PADDING_DP) : 0, 0, 0);
|
|
1193
1428
|
}
|
|
1194
1429
|
|
|
1195
1430
|
bringChromeToFront();
|
|
@@ -1245,14 +1480,15 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1245
1480
|
}
|
|
1246
1481
|
|
|
1247
1482
|
private JSObject currentInsets() {
|
|
1248
|
-
int
|
|
1249
|
-
int
|
|
1483
|
+
int navbarHeight = navbarVisible ? statusBarInset() + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1484
|
+
int top = navbarVisible ? navbarHeight : 0;
|
|
1485
|
+
int bottom = tabbarVisible ? floatingTabbarBottomMargin(navigationBarInset()) + tabbarHeight() : 0;
|
|
1250
1486
|
JSObject insets = new JSObject();
|
|
1251
1487
|
insets.put("top", top);
|
|
1252
1488
|
insets.put("right", 0);
|
|
1253
1489
|
insets.put("bottom", bottom);
|
|
1254
1490
|
insets.put("left", 0);
|
|
1255
|
-
insets.put("navbarHeight",
|
|
1491
|
+
insets.put("navbarHeight", navbarHeight);
|
|
1256
1492
|
insets.put("tabbarHeight", bottom);
|
|
1257
1493
|
return insets;
|
|
1258
1494
|
}
|
|
@@ -1271,6 +1507,52 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1271
1507
|
return event;
|
|
1272
1508
|
}
|
|
1273
1509
|
|
|
1510
|
+
private boolean isStationaryTransition(String direction) {
|
|
1511
|
+
return "tab".equals(direction) || "root".equals(direction) || "none".equals(direction);
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
private void prepareTransitionRootBackground(FrameLayout root, int surfaceColor) {
|
|
1515
|
+
activeTransitionRootBackground = root.getBackground();
|
|
1516
|
+
activeTransitionRootBackgroundCaptured = true;
|
|
1517
|
+
if (needsTransitionSurface(activeTransitionRootBackground)) {
|
|
1518
|
+
root.setBackgroundColor(surfaceColor);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
private void restoreTransitionRootBackground() {
|
|
1523
|
+
if (!activeTransitionRootBackgroundCaptured) {
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
FrameLayout root = contentRoot();
|
|
1527
|
+
if (root != null) {
|
|
1528
|
+
root.setBackground(activeTransitionRootBackground);
|
|
1529
|
+
}
|
|
1530
|
+
activeTransitionRootBackground = null;
|
|
1531
|
+
activeTransitionRootBackgroundCaptured = false;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
private boolean needsTransitionSurface(Drawable background) {
|
|
1535
|
+
if (background == null) {
|
|
1536
|
+
return true;
|
|
1537
|
+
}
|
|
1538
|
+
if (background instanceof ColorDrawable) {
|
|
1539
|
+
return Color.alpha(((ColorDrawable) background).getColor()) < 255;
|
|
1540
|
+
}
|
|
1541
|
+
return false;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
private int transitionSurfaceColor(View webView) {
|
|
1545
|
+
Drawable background = webView.getBackground();
|
|
1546
|
+
if (background instanceof ColorDrawable) {
|
|
1547
|
+
int color = ((ColorDrawable) background).getColor();
|
|
1548
|
+
if (Color.alpha(color) > 0) {
|
|
1549
|
+
return withAlpha(color, 255);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
int fallback = isNightMode() ? Color.rgb(18, 18, 18) : Color.WHITE;
|
|
1553
|
+
return dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", fallback);
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1274
1556
|
private FrameLayout contentRoot() {
|
|
1275
1557
|
Activity activity = getActivity();
|
|
1276
1558
|
return activity == null ? null : activity.findViewById(android.R.id.content);
|
|
@@ -1307,7 +1589,10 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1307
1589
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1308
1590
|
WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
|
|
1309
1591
|
if (insets != null) {
|
|
1310
|
-
|
|
1592
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
1593
|
+
return insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
|
|
1594
|
+
}
|
|
1595
|
+
return insets.getSystemWindowInsetBottom();
|
|
1311
1596
|
}
|
|
1312
1597
|
}
|
|
1313
1598
|
return systemDimension("navigation_bar_height");
|
|
@@ -1322,6 +1607,208 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1322
1607
|
return Math.round(value * getContext().getResources().getDisplayMetrics().density);
|
|
1323
1608
|
}
|
|
1324
1609
|
|
|
1610
|
+
private float dp(double value) {
|
|
1611
|
+
return (float) (value * getContext().getResources().getDisplayMetrics().density);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
private static final class GlassOptions {
|
|
1615
|
+
|
|
1616
|
+
private static final String EFFECT_NONE = "none";
|
|
1617
|
+
private static final String EFFECT_LIQUID_GLASS = "liquidGlass";
|
|
1618
|
+
private static final double DEFAULT_BLUR_RADIUS_DP = 18d;
|
|
1619
|
+
private static final double DEFAULT_SURFACE_ALPHA = 0.62d;
|
|
1620
|
+
|
|
1621
|
+
final String effect;
|
|
1622
|
+
final double blurRadiusDp;
|
|
1623
|
+
final double surfaceAlpha;
|
|
1624
|
+
|
|
1625
|
+
GlassOptions(String effect, double blurRadiusDp, double surfaceAlpha) {
|
|
1626
|
+
this.effect = effect;
|
|
1627
|
+
this.blurRadiusDp = Math.max(0d, blurRadiusDp);
|
|
1628
|
+
this.surfaceAlpha = Math.max(0d, Math.min(1d, surfaceAlpha));
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
static GlassOptions defaults() {
|
|
1632
|
+
return new GlassOptions(EFFECT_NONE, DEFAULT_BLUR_RADIUS_DP, DEFAULT_SURFACE_ALPHA);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
static GlassOptions from(JSObject raw, GlassOptions fallback) {
|
|
1636
|
+
GlassOptions base = fallback == null ? defaults() : fallback;
|
|
1637
|
+
if (raw == null) {
|
|
1638
|
+
return base;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
String effect = raw.optString("effect", base.effect);
|
|
1642
|
+
if (!EFFECT_NONE.equals(effect) && !EFFECT_LIQUID_GLASS.equals(effect)) {
|
|
1643
|
+
effect = base.effect;
|
|
1644
|
+
}
|
|
1645
|
+
double blurRadiusDp = raw.has("blurRadius") ? raw.optDouble("blurRadius", base.blurRadiusDp) : base.blurRadiusDp;
|
|
1646
|
+
double surfaceAlpha = raw.has("surfaceAlpha") ? raw.optDouble("surfaceAlpha", base.surfaceAlpha) : base.surfaceAlpha;
|
|
1647
|
+
return new GlassOptions(effect, blurRadiusDp, surfaceAlpha);
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
boolean isLiquidGlass() {
|
|
1651
|
+
return EFFECT_LIQUID_GLASS.equals(effect);
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
private static final class GlassBackdropView extends View {
|
|
1656
|
+
|
|
1657
|
+
private static final long SOURCE_CONTENT_REFRESH_INTERVAL_MS = 250L;
|
|
1658
|
+
|
|
1659
|
+
private final Paint fallbackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
1660
|
+
private final int[] sourceLocation = new int[2];
|
|
1661
|
+
private final int[] viewLocation = new int[2];
|
|
1662
|
+
private final ViewTreeObserver.OnScrollChangedListener sourceScrollListener = this::markDirty;
|
|
1663
|
+
private final ViewTreeObserver.OnPreDrawListener sourcePreDrawListener = () -> {
|
|
1664
|
+
markDirtyFromSourcePreDraw();
|
|
1665
|
+
return true;
|
|
1666
|
+
};
|
|
1667
|
+
private final View.OnLayoutChangeListener sourceLayoutListener = (
|
|
1668
|
+
view,
|
|
1669
|
+
left,
|
|
1670
|
+
top,
|
|
1671
|
+
right,
|
|
1672
|
+
bottom,
|
|
1673
|
+
oldLeft,
|
|
1674
|
+
oldTop,
|
|
1675
|
+
oldRight,
|
|
1676
|
+
oldBottom
|
|
1677
|
+
) -> markDirty();
|
|
1678
|
+
private View source;
|
|
1679
|
+
private int fallbackColor = Color.TRANSPARENT;
|
|
1680
|
+
private boolean dirty;
|
|
1681
|
+
private boolean redrawPending;
|
|
1682
|
+
private long lastSourcePreDrawRefreshMs;
|
|
1683
|
+
|
|
1684
|
+
GlassBackdropView(android.content.Context context) {
|
|
1685
|
+
super(context);
|
|
1686
|
+
setWillNotDraw(false);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
void configure(View source, float blurRadiusPx, int fallbackColor) {
|
|
1690
|
+
this.fallbackColor = fallbackColor;
|
|
1691
|
+
attachSource(source);
|
|
1692
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
1693
|
+
Api31RenderEffects.setBlur(this, blurRadiusPx);
|
|
1694
|
+
}
|
|
1695
|
+
markDirty();
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
void clearEffect() {
|
|
1699
|
+
attachSource(null);
|
|
1700
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
1701
|
+
Api31RenderEffects.clear(this);
|
|
1702
|
+
}
|
|
1703
|
+
markDirty();
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
private void attachSource(View nextSource) {
|
|
1707
|
+
if (source == nextSource) {
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
if (source != null) {
|
|
1711
|
+
source.removeOnLayoutChangeListener(sourceLayoutListener);
|
|
1712
|
+
ViewTreeObserver observer = source.getViewTreeObserver();
|
|
1713
|
+
if (observer.isAlive()) {
|
|
1714
|
+
observer.removeOnScrollChangedListener(sourceScrollListener);
|
|
1715
|
+
observer.removeOnPreDrawListener(sourcePreDrawListener);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
source = nextSource;
|
|
1719
|
+
if (source != null) {
|
|
1720
|
+
source.addOnLayoutChangeListener(sourceLayoutListener);
|
|
1721
|
+
ViewTreeObserver observer = source.getViewTreeObserver();
|
|
1722
|
+
if (observer.isAlive()) {
|
|
1723
|
+
observer.addOnScrollChangedListener(sourceScrollListener);
|
|
1724
|
+
observer.addOnPreDrawListener(sourcePreDrawListener);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
markDirty();
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
private void markDirty() {
|
|
1731
|
+
dirty = true;
|
|
1732
|
+
scheduleRedrawIfVisible();
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
private void markDirtyFromSourcePreDraw() {
|
|
1736
|
+
dirty = true;
|
|
1737
|
+
long now = System.currentTimeMillis();
|
|
1738
|
+
if (now - lastSourcePreDrawRefreshMs < SOURCE_CONTENT_REFRESH_INTERVAL_MS) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
lastSourcePreDrawRefreshMs = now;
|
|
1742
|
+
scheduleRedrawIfVisible();
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
private void scheduleRedrawIfVisible() {
|
|
1746
|
+
if (redrawPending || getVisibility() != View.VISIBLE || !isShown()) {
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
redrawPending = true;
|
|
1750
|
+
postInvalidateOnAnimation();
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
@Override
|
|
1754
|
+
protected void onDraw(Canvas canvas) {
|
|
1755
|
+
super.onDraw(canvas);
|
|
1756
|
+
View currentSource = source;
|
|
1757
|
+
if (currentSource == null || currentSource.getWidth() <= 0 || currentSource.getHeight() <= 0) {
|
|
1758
|
+
fallbackPaint.setColor(fallbackColor);
|
|
1759
|
+
canvas.drawRect(0, 0, getWidth(), getHeight(), fallbackPaint);
|
|
1760
|
+
} else {
|
|
1761
|
+
currentSource.getLocationOnScreen(sourceLocation);
|
|
1762
|
+
getLocationOnScreen(viewLocation);
|
|
1763
|
+
canvas.save();
|
|
1764
|
+
canvas.translate(sourceLocation[0] - viewLocation[0], sourceLocation[1] - viewLocation[1]);
|
|
1765
|
+
currentSource.draw(canvas);
|
|
1766
|
+
canvas.restore();
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
dirty = false;
|
|
1770
|
+
redrawPending = false;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
@Override
|
|
1774
|
+
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
|
1775
|
+
super.onSizeChanged(width, height, oldWidth, oldHeight);
|
|
1776
|
+
if (width != oldWidth || height != oldHeight) {
|
|
1777
|
+
markDirty();
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
@Override
|
|
1782
|
+
protected void onVisibilityChanged(View changedView, int visibility) {
|
|
1783
|
+
super.onVisibilityChanged(changedView, visibility);
|
|
1784
|
+
if (visibility != View.VISIBLE) {
|
|
1785
|
+
redrawPending = false;
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
if (dirty) {
|
|
1789
|
+
scheduleRedrawIfVisible();
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
@RequiresApi(Build.VERSION_CODES.S)
|
|
1795
|
+
private static final class Api31RenderEffects {
|
|
1796
|
+
|
|
1797
|
+
static void setBlur(View view, float blurRadiusPx) {
|
|
1798
|
+
if (blurRadiusPx <= 0f) {
|
|
1799
|
+
view.setRenderEffect(null);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
view.setRenderEffect(
|
|
1803
|
+
android.graphics.RenderEffect.createBlurEffect(blurRadiusPx, blurRadiusPx, android.graphics.Shader.TileMode.CLAMP)
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
static void clear(View view) {
|
|
1808
|
+
view.setRenderEffect(null);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1325
1812
|
private void runOnUiThread(Runnable runnable) {
|
|
1326
1813
|
Activity activity = getActivity();
|
|
1327
1814
|
if (activity == null) {
|