@capgo/capacitor-native-navigation 8.1.1 → 8.2.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 +35 -0
- package/android/src/main/java/app/capgo/nativenavigation/NativeNavigationPlugin.java +454 -575
- package/dist/docs.json +112 -0
- package/dist/esm/definitions.d.ts +65 -0
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +1 -0
- package/dist/esm/web.js +16 -3
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +16 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +16 -3
- package/dist/plugin.js.map +1 -1
- package/docs/demo-curved-tabbar.png +0 -0
- package/docs/native-screenshots/android-curved-tabbar.png +0 -0
- package/ios/Sources/NativeNavigationPlugin/NativeNavigationPlugin.swift +612 -37
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
package app.capgo.nativenavigation;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
|
-
import android.content.
|
|
4
|
+
import android.content.Context;
|
|
5
5
|
import android.content.res.Configuration;
|
|
6
6
|
import android.content.res.Resources;
|
|
7
7
|
import android.graphics.Bitmap;
|
|
@@ -12,26 +12,25 @@ import android.graphics.Paint;
|
|
|
12
12
|
import android.graphics.Path;
|
|
13
13
|
import android.graphics.Rect;
|
|
14
14
|
import android.graphics.RectF;
|
|
15
|
+
import android.graphics.Typeface;
|
|
15
16
|
import android.graphics.drawable.BitmapDrawable;
|
|
16
17
|
import android.graphics.drawable.ColorDrawable;
|
|
17
18
|
import android.graphics.drawable.Drawable;
|
|
18
19
|
import android.graphics.drawable.GradientDrawable;
|
|
19
|
-
import android.graphics.drawable.StateListDrawable;
|
|
20
20
|
import android.os.Build;
|
|
21
|
-
import android.
|
|
21
|
+
import android.util.TypedValue;
|
|
22
22
|
import android.view.Gravity;
|
|
23
23
|
import android.view.Menu;
|
|
24
24
|
import android.view.MenuItem;
|
|
25
25
|
import android.view.View;
|
|
26
26
|
import android.view.ViewGroup;
|
|
27
27
|
import android.view.ViewOutlineProvider;
|
|
28
|
-
import android.view.ViewTreeObserver;
|
|
29
28
|
import android.view.Window;
|
|
30
29
|
import android.view.WindowInsets;
|
|
31
30
|
import android.widget.FrameLayout;
|
|
32
31
|
import android.widget.ImageView;
|
|
32
|
+
import android.widget.LinearLayout;
|
|
33
33
|
import android.widget.TextView;
|
|
34
|
-
import androidx.annotation.RequiresApi;
|
|
35
34
|
import androidx.appcompat.content.res.AppCompatResources;
|
|
36
35
|
import androidx.appcompat.widget.Toolbar;
|
|
37
36
|
import androidx.core.graphics.PathParser;
|
|
@@ -41,9 +40,6 @@ import com.getcapacitor.Plugin;
|
|
|
41
40
|
import com.getcapacitor.PluginCall;
|
|
42
41
|
import com.getcapacitor.PluginMethod;
|
|
43
42
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
44
|
-
import com.google.android.material.badge.BadgeDrawable;
|
|
45
|
-
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
46
|
-
import com.google.android.material.navigation.NavigationBarView;
|
|
47
43
|
import java.io.StringReader;
|
|
48
44
|
import java.net.URLDecoder;
|
|
49
45
|
import java.util.ArrayDeque;
|
|
@@ -62,36 +58,15 @@ import org.xmlpull.v1.XmlPullParserFactory;
|
|
|
62
58
|
public class NativeNavigationPlugin extends Plugin {
|
|
63
59
|
|
|
64
60
|
private static final int DEFAULT_NAVBAR_DP = 56;
|
|
65
|
-
|
|
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;
|
|
74
|
-
private static final int TABBAR_ICON_DP = 24;
|
|
75
|
-
private static final int TABBAR_ITEM_VERTICAL_PADDING_DP = 8;
|
|
76
|
-
private static final int LIQUID_GLASS_TABBAR_ITEM_VERTICAL_PADDING_DP = 4;
|
|
77
|
-
private static final int TABBAR_INDICATOR_DP = 32;
|
|
78
|
-
private static final int LIQUID_GLASS_TABBAR_INDICATOR_DP = 28;
|
|
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;
|
|
61
|
+
private static final int DEFAULT_TABBAR_DP = 64;
|
|
83
62
|
private static final int DEFAULT_TRANSITION_MS = 350;
|
|
84
63
|
private static final int MENU_ITEM_BASE = 10_000;
|
|
85
64
|
|
|
86
65
|
private final NativeNavigation implementation = new NativeNavigation();
|
|
87
66
|
private FrameLayout navbarContainer;
|
|
88
67
|
private FrameLayout tabbarContainer;
|
|
89
|
-
private GlassBackdropView navbarGlassBackdrop;
|
|
90
|
-
private View navbarGlassSurface;
|
|
91
|
-
private GlassBackdropView tabbarGlassBackdrop;
|
|
92
|
-
private View tabbarGlassSurface;
|
|
93
68
|
private Toolbar toolbar;
|
|
94
|
-
private
|
|
69
|
+
private NativeTabbarLayout tabbar;
|
|
95
70
|
private ImageView transitionSnapshot;
|
|
96
71
|
private boolean enabled = true;
|
|
97
72
|
private boolean navbarVisible = false;
|
|
@@ -101,13 +76,10 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
101
76
|
private int activeTransitionMs = DEFAULT_TRANSITION_MS;
|
|
102
77
|
private int tintColor = Color.rgb(0, 122, 255);
|
|
103
78
|
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
79
|
private int tabbarBackgroundColor = Color.argb(235, 255, 255, 255);
|
|
80
|
+
private int badgeBackgroundColor = Color.rgb(255, 59, 48);
|
|
81
|
+
private int badgeTextColor = Color.WHITE;
|
|
82
|
+
private TabbarStyle tabbarStyle = TabbarStyle.defaults(Color.rgb(0, 122, 255));
|
|
111
83
|
private String activeTransitionId;
|
|
112
84
|
private String activeTransitionDirection = "forward";
|
|
113
85
|
private RectF activeZoomSourceFrame;
|
|
@@ -117,8 +89,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
117
89
|
private final Map<Integer, String> menuActionIds = new HashMap<>();
|
|
118
90
|
private final Map<Integer, String> menuActionTitles = new HashMap<>();
|
|
119
91
|
private final Map<Integer, String> menuActionPlacements = new HashMap<>();
|
|
120
|
-
private final
|
|
121
|
-
private
|
|
92
|
+
private final List<NativeTabItem> tabItems = new ArrayList<>();
|
|
93
|
+
private int selectedTabIndex = 0;
|
|
122
94
|
|
|
123
95
|
@Override
|
|
124
96
|
public void load() {
|
|
@@ -133,9 +105,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
133
105
|
runOnUiThread(() -> {
|
|
134
106
|
enabled = call.getBoolean("enabled", true);
|
|
135
107
|
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);
|
|
139
108
|
Double duration = call.getDouble("animationDuration");
|
|
140
109
|
if (duration != null) {
|
|
141
110
|
defaultTransitionMs = Math.max(0, duration.intValue());
|
|
@@ -151,12 +120,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
151
120
|
tabbarContainer.setVisibility(View.GONE);
|
|
152
121
|
}
|
|
153
122
|
}
|
|
154
|
-
if (enabled) {
|
|
155
|
-
reapplyVisibleChromeBackgrounds();
|
|
156
|
-
if (tabbar != null) {
|
|
157
|
-
applyTabbarMetrics(tabbar);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
123
|
updateInsetsAndNotify();
|
|
161
124
|
call.resolve(insetsResult());
|
|
162
125
|
});
|
|
@@ -204,8 +167,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
204
167
|
|
|
205
168
|
addToolbarItems(nativeToolbar, call.getArray("rightItems", new JSArray()), "right");
|
|
206
169
|
JSObject colors = call.getObject("colors", new JSObject());
|
|
207
|
-
navbarGlassConfig = call.getObject("glass", null);
|
|
208
|
-
navbarGlassOptions = GlassOptions.from(navbarGlassConfig, defaultGlassOptions);
|
|
209
170
|
applyToolbarColors(nativeToolbar, colors);
|
|
210
171
|
navbarContainer.setVisibility(View.VISIBLE);
|
|
211
172
|
layoutChrome();
|
|
@@ -238,77 +199,44 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
238
199
|
return;
|
|
239
200
|
}
|
|
240
201
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
nativeTabbar.getMenu().clear();
|
|
246
|
-
tabIds.clear();
|
|
247
|
-
tabTitles.clear();
|
|
202
|
+
NativeTabbarLayout nativeTabbar = ensureTabbar();
|
|
203
|
+
nativeTabbar.removeAllViews();
|
|
204
|
+
tabItems.clear();
|
|
205
|
+
selectedTabIndex = -1;
|
|
248
206
|
|
|
249
207
|
boolean labels = call.getBoolean("labels", true);
|
|
250
208
|
boolean icons = call.getBoolean("icons", true);
|
|
251
209
|
String labelVisibilityMode = call.getString("labelVisibilityMode", labels ? "labeled" : "unlabeled");
|
|
252
|
-
nativeTabbar.setLabelVisibilityMode(labelVisibilityMode(labelVisibilityMode));
|
|
253
|
-
|
|
254
210
|
JSONArray tabs = call.getArray("tabs", new JSArray());
|
|
255
211
|
String selectedId = call.getString("selectedId", null);
|
|
256
212
|
JSObject colors = call.getObject("colors", new JSObject());
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
Integer badgeText = colorOption(call, colors, "badgeTextColor", "badgeText", null);
|
|
213
|
+
badgeBackgroundColor = colorOption(call, colors, "badgeBackgroundColor", "badgeBackground", Color.rgb(255, 59, 48));
|
|
214
|
+
badgeTextColor = colorOption(call, colors, "badgeTextColor", "badgeText", Color.WHITE);
|
|
215
|
+
|
|
261
216
|
for (int index = 0; index < tabs.length(); index++) {
|
|
262
217
|
JSONObject tab = tabs.optJSONObject(index);
|
|
263
218
|
if (tab == null) {
|
|
264
219
|
continue;
|
|
265
220
|
}
|
|
266
|
-
int itemId = MENU_ITEM_BASE + index;
|
|
267
221
|
String id = tab.optString("id", "tab-" + index);
|
|
268
222
|
String title = tab.optString("title", "");
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
item.setIcon(icon);
|
|
274
|
-
}
|
|
275
|
-
if (tab.has("badge")) {
|
|
276
|
-
nativeTabbar.removeBadge(itemId);
|
|
277
|
-
BadgeDrawable badge = nativeTabbar.getOrCreateBadge(itemId);
|
|
278
|
-
if (isLiquidGlassTabbar()) {
|
|
279
|
-
badge.setVerticalOffset(dp(LIQUID_GLASS_TABBAR_BADGE_VERTICAL_OFFSET_DP));
|
|
280
|
-
}
|
|
281
|
-
if (badgeBackground != null) {
|
|
282
|
-
badge.setBackgroundColor(badgeBackground);
|
|
283
|
-
}
|
|
284
|
-
if (badgeText != null) {
|
|
285
|
-
badge.setBadgeTextColor(badgeText);
|
|
286
|
-
}
|
|
287
|
-
Object badgeValue = tab.opt("badge");
|
|
288
|
-
if (badgeValue instanceof Number) {
|
|
289
|
-
badge.setNumber(((Number) badgeValue).intValue());
|
|
290
|
-
} else {
|
|
291
|
-
try {
|
|
292
|
-
badge.setNumber(Integer.parseInt(String.valueOf(badgeValue)));
|
|
293
|
-
} catch (NumberFormatException ignored) {
|
|
294
|
-
badge.setVisible(true);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
} else {
|
|
298
|
-
nativeTabbar.removeBadge(itemId);
|
|
299
|
-
}
|
|
300
|
-
tabIds.put(itemId, id);
|
|
301
|
-
tabTitles.put(itemId, title);
|
|
223
|
+
Drawable icon = icons ? iconFrom(tab.optJSONObject("icon")) : new ColorDrawable(Color.TRANSPARENT);
|
|
224
|
+
Drawable selectedIcon = icons ? iconFrom(tab.optJSONObject("selectedIcon")) : null;
|
|
225
|
+
String badge = tab.has("badge") ? String.valueOf(tab.opt("badge")) : null;
|
|
226
|
+
tabItems.add(new NativeTabItem(id, title, icon, selectedIcon, badge, tab.optBoolean("enabled", true)));
|
|
302
227
|
if (id.equals(selectedId)) {
|
|
303
|
-
|
|
228
|
+
selectedTabIndex = index;
|
|
304
229
|
}
|
|
305
230
|
}
|
|
306
231
|
|
|
307
|
-
if (
|
|
308
|
-
|
|
232
|
+
if (!tabItems.isEmpty() && (selectedTabIndex < 0 || selectedTabIndex >= tabItems.size())) {
|
|
233
|
+
selectedTabIndex = 0;
|
|
309
234
|
}
|
|
310
235
|
|
|
311
|
-
applyTabbarColors(
|
|
236
|
+
applyTabbarColors(call, colors);
|
|
237
|
+
tabbarStyle = makeTabbarStyle(call.getObject("style", new JSObject()));
|
|
238
|
+
nativeTabbar.setTabbarStyle(tabbarStyle, tabbarBackgroundColor, centerTabIndex());
|
|
239
|
+
renderTabbarItems(labelVisibilityMode, icons);
|
|
312
240
|
if (tabbarContainer != null) {
|
|
313
241
|
tabbarContainer.setVisibility(View.VISIBLE);
|
|
314
242
|
}
|
|
@@ -647,10 +575,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
647
575
|
FrameLayout root = contentRoot();
|
|
648
576
|
navbarContainer = new FrameLayout(getContext());
|
|
649
577
|
navbarContainer.setElevation(dp(8));
|
|
650
|
-
navbarGlassBackdrop = new GlassBackdropView(getContext());
|
|
651
|
-
navbarGlassSurface = new View(getContext());
|
|
652
|
-
navbarGlassBackdrop.setVisibility(View.GONE);
|
|
653
|
-
navbarGlassSurface.setVisibility(View.GONE);
|
|
654
578
|
toolbar = new Toolbar(getContext());
|
|
655
579
|
toolbar.setPopupTheme(androidx.appcompat.R.style.ThemeOverlay_AppCompat_Light);
|
|
656
580
|
toolbar.setOnMenuItemClickListener((item) -> {
|
|
@@ -663,8 +587,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
663
587
|
return true;
|
|
664
588
|
});
|
|
665
589
|
|
|
666
|
-
navbarContainer.addView(navbarGlassBackdrop);
|
|
667
|
-
navbarContainer.addView(navbarGlassSurface);
|
|
668
590
|
navbarContainer.addView(toolbar);
|
|
669
591
|
if (root != null) {
|
|
670
592
|
root.addView(navbarContainer);
|
|
@@ -677,90 +599,157 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
677
599
|
return toolbar;
|
|
678
600
|
}
|
|
679
601
|
|
|
680
|
-
private
|
|
602
|
+
private NativeTabbarLayout ensureTabbar() {
|
|
681
603
|
if (tabbar != null) {
|
|
682
604
|
return tabbar;
|
|
683
605
|
}
|
|
684
606
|
FrameLayout root = contentRoot();
|
|
685
607
|
tabbarContainer = new FrameLayout(getContext());
|
|
686
|
-
tabbarContainer.setElevation(dp(12));
|
|
687
608
|
tabbarContainer.setClipChildren(false);
|
|
688
609
|
tabbarContainer.setClipToPadding(false);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
tabbarGlassSurface.setVisibility(View.GONE);
|
|
693
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
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);
|
|
698
|
-
}
|
|
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
|
-
);
|
|
710
|
-
tabbar.setElevation(0);
|
|
610
|
+
tabbarContainer.setElevation(dp(12));
|
|
611
|
+
|
|
612
|
+
tabbar = new NativeTabbarLayout(getContext());
|
|
711
613
|
tabbar.setClipChildren(false);
|
|
712
614
|
tabbar.setClipToPadding(false);
|
|
713
|
-
tabbar.setItemIconSize(dp(TABBAR_ICON_DP));
|
|
714
|
-
applyTabbarMetrics(tabbar);
|
|
715
615
|
tabbar.setBackgroundColor(Color.TRANSPARENT);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
721
|
-
JSObject event = new JSObject();
|
|
722
|
-
event.put("id", tabIds.get(itemId));
|
|
723
|
-
event.put("index", itemId - MENU_ITEM_BASE);
|
|
724
|
-
event.put("title", tabTitles.get(itemId));
|
|
725
|
-
notifyListeners("tabSelect", event);
|
|
726
|
-
return true;
|
|
727
|
-
});
|
|
728
|
-
tabbarContainer.addView(tabbarGlassBackdrop);
|
|
729
|
-
tabbarContainer.addView(tabbarGlassSurface);
|
|
730
|
-
tabbarContainer.addView(tabbar);
|
|
616
|
+
tabbarContainer.addView(
|
|
617
|
+
tabbar,
|
|
618
|
+
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
619
|
+
);
|
|
731
620
|
if (root != null) {
|
|
732
621
|
root.addView(tabbarContainer);
|
|
733
622
|
} else {
|
|
734
|
-
getActivity().addContentView(
|
|
623
|
+
getActivity().addContentView(
|
|
624
|
+
tabbarContainer,
|
|
625
|
+
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(DEFAULT_TABBAR_DP))
|
|
626
|
+
);
|
|
735
627
|
}
|
|
736
628
|
return tabbar;
|
|
737
629
|
}
|
|
738
630
|
|
|
739
|
-
private
|
|
740
|
-
if (
|
|
741
|
-
return
|
|
631
|
+
private void renderTabbarItems(String labelVisibilityMode, boolean icons) {
|
|
632
|
+
if (tabbar == null) {
|
|
633
|
+
return;
|
|
742
634
|
}
|
|
743
|
-
|
|
744
|
-
|
|
635
|
+
tabbar.removeAllViews();
|
|
636
|
+
int centerIndex = centerTabIndex();
|
|
637
|
+
tabbar.setTabbarStyle(tabbarStyle, tabbarBackgroundColor, centerIndex);
|
|
638
|
+
for (int index = 0; index < tabItems.size(); index++) {
|
|
639
|
+
final int itemIndex = index;
|
|
640
|
+
NativeTabItem item = tabItems.get(index);
|
|
641
|
+
boolean selected = itemIndex == selectedTabIndex;
|
|
642
|
+
boolean showLabel = shouldShowTabLabel(labelVisibilityMode, selected);
|
|
643
|
+
FrameLayout button = makeTabButton(item, selected, showLabel, icons, itemIndex == centerIndex);
|
|
644
|
+
button.setSelected(selected);
|
|
645
|
+
button.setEnabled(item.enabled);
|
|
646
|
+
button.setAlpha(item.enabled ? 1f : 0.38f);
|
|
647
|
+
button.setOnClickListener((view) -> {
|
|
648
|
+
if (!item.enabled) {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
selectedTabIndex = itemIndex;
|
|
652
|
+
renderTabbarItems(labelVisibilityMode, icons);
|
|
653
|
+
JSObject event = new JSObject();
|
|
654
|
+
event.put("id", item.id);
|
|
655
|
+
event.put("index", itemIndex);
|
|
656
|
+
event.put("title", item.title);
|
|
657
|
+
notifyListeners("tabSelect", event);
|
|
658
|
+
});
|
|
659
|
+
tabbar.addView(button, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
745
660
|
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private boolean shouldShowTabLabel(String mode, boolean selected) {
|
|
746
664
|
if ("unlabeled".equals(mode)) {
|
|
747
|
-
return
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
if ("selected".equals(mode)) {
|
|
668
|
+
return selected;
|
|
748
669
|
}
|
|
749
|
-
return
|
|
670
|
+
return true;
|
|
750
671
|
}
|
|
751
672
|
|
|
752
|
-
private
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
673
|
+
private FrameLayout makeTabButton(NativeTabItem item, boolean selected, boolean showLabel, boolean icons, boolean center) {
|
|
674
|
+
FrameLayout button = new FrameLayout(getContext());
|
|
675
|
+
button.setClipChildren(false);
|
|
676
|
+
button.setClipToPadding(false);
|
|
677
|
+
button.setForeground(selectableItemBackground());
|
|
678
|
+
if (center) {
|
|
679
|
+
GradientDrawable centerBackground = new GradientDrawable();
|
|
680
|
+
centerBackground.setShape(GradientDrawable.OVAL);
|
|
681
|
+
centerBackground.setColor(tabbarStyle.centerButtonColor);
|
|
682
|
+
button.setBackground(centerBackground);
|
|
683
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
684
|
+
button.setElevation(dp(10));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (!center && selected) {
|
|
689
|
+
GradientDrawable selectedBackground = new GradientDrawable();
|
|
690
|
+
selectedBackground.setShape(GradientDrawable.OVAL);
|
|
691
|
+
selectedBackground.setColor(withAlpha(tintColor, 34));
|
|
692
|
+
View selectedCircle = new View(getContext());
|
|
693
|
+
selectedCircle.setBackground(selectedBackground);
|
|
694
|
+
button.addView(selectedCircle, new FrameLayout.LayoutParams(dp(58), dp(58), Gravity.CENTER));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
LinearLayout content = new LinearLayout(getContext());
|
|
698
|
+
content.setOrientation(LinearLayout.VERTICAL);
|
|
699
|
+
content.setGravity(Gravity.CENTER);
|
|
700
|
+
content.setPadding(dp(4), dp(4), dp(4), dp(4));
|
|
701
|
+
button.addView(content, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
|
702
|
+
|
|
703
|
+
Drawable currentIcon = selected && item.selectedIcon != null ? item.selectedIcon : item.icon;
|
|
704
|
+
boolean labelVisible = center ? showLabel && (currentIcon == null || !icons) : showLabel;
|
|
705
|
+
int itemColor = center ? tabbarStyle.centerButtonIconColor : (selected ? tintColor : inactiveTintColor);
|
|
706
|
+
if (icons && currentIcon != null) {
|
|
707
|
+
Drawable icon = currentIcon.mutate();
|
|
708
|
+
icon.setTint(itemColor);
|
|
709
|
+
ImageView image = new ImageView(getContext());
|
|
710
|
+
image.setImageDrawable(icon);
|
|
711
|
+
image.setColorFilter(itemColor);
|
|
712
|
+
int imageSize = center ? dp(32) : dp(24);
|
|
713
|
+
LinearLayout.LayoutParams imageParams = new LinearLayout.LayoutParams(imageSize, imageSize);
|
|
714
|
+
imageParams.bottomMargin = labelVisible ? dp(2) : 0;
|
|
715
|
+
content.addView(image, imageParams);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
if (labelVisible) {
|
|
719
|
+
TextView label = new TextView(getContext());
|
|
720
|
+
label.setText(item.title);
|
|
721
|
+
label.setTextColor(itemColor);
|
|
722
|
+
label.setTextSize(12);
|
|
723
|
+
label.setTypeface(Typeface.DEFAULT, selected ? Typeface.BOLD : Typeface.NORMAL);
|
|
724
|
+
label.setGravity(Gravity.CENTER);
|
|
725
|
+
label.setSingleLine(true);
|
|
726
|
+
content.addView(label, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dp(18)));
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (item.badge != null && !item.badge.isEmpty() && !"0".equals(item.badge)) {
|
|
730
|
+
TextView badge = new TextView(getContext());
|
|
731
|
+
badge.setText(item.badge);
|
|
732
|
+
badge.setTextColor(badgeTextColor);
|
|
733
|
+
badge.setTextSize(11);
|
|
734
|
+
badge.setTypeface(Typeface.DEFAULT_BOLD);
|
|
735
|
+
badge.setGravity(Gravity.CENTER);
|
|
736
|
+
GradientDrawable badgeBackground = new GradientDrawable();
|
|
737
|
+
badgeBackground.setColor(badgeBackgroundColor);
|
|
738
|
+
badgeBackground.setCornerRadius(dp(9));
|
|
739
|
+
badge.setBackground(badgeBackground);
|
|
740
|
+
int badgeWidth = Math.max(dp(18), item.badge.length() * dp(7) + dp(10));
|
|
741
|
+
FrameLayout.LayoutParams badgeParams = new FrameLayout.LayoutParams(
|
|
742
|
+
badgeWidth,
|
|
743
|
+
dp(18),
|
|
744
|
+
Gravity.TOP | Gravity.CENTER_HORIZONTAL
|
|
745
|
+
);
|
|
746
|
+
badgeParams.topMargin = center ? 0 : dp(4);
|
|
747
|
+
badge.setTranslationX(center ? dp(18) : dp(16));
|
|
748
|
+
button.addView(badge, badgeParams);
|
|
762
749
|
}
|
|
763
|
-
|
|
750
|
+
|
|
751
|
+
button.setContentDescription(item.title);
|
|
752
|
+
return button;
|
|
764
753
|
}
|
|
765
754
|
|
|
766
755
|
private Drawable iconFrom(JSONObject descriptor) {
|
|
@@ -913,6 +902,235 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
913
902
|
}
|
|
914
903
|
}
|
|
915
904
|
|
|
905
|
+
private static final class NativeTabItem {
|
|
906
|
+
|
|
907
|
+
final String id;
|
|
908
|
+
final String title;
|
|
909
|
+
final Drawable icon;
|
|
910
|
+
final Drawable selectedIcon;
|
|
911
|
+
final String badge;
|
|
912
|
+
final boolean enabled;
|
|
913
|
+
|
|
914
|
+
NativeTabItem(String id, String title, Drawable icon, Drawable selectedIcon, String badge, boolean enabled) {
|
|
915
|
+
this.id = id;
|
|
916
|
+
this.title = title;
|
|
917
|
+
this.icon = icon;
|
|
918
|
+
this.selectedIcon = selectedIcon;
|
|
919
|
+
this.badge = badge;
|
|
920
|
+
this.enabled = enabled;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
private static final class TabbarStyle {
|
|
925
|
+
|
|
926
|
+
final String shape;
|
|
927
|
+
final int height;
|
|
928
|
+
final int horizontalMargin;
|
|
929
|
+
final int maxWidth;
|
|
930
|
+
final int bottomGap;
|
|
931
|
+
final int cornerRadius;
|
|
932
|
+
final String centerItemId;
|
|
933
|
+
final int centerButtonDiameter;
|
|
934
|
+
final int centerButtonLift;
|
|
935
|
+
final int centerButtonColor;
|
|
936
|
+
final int centerButtonIconColor;
|
|
937
|
+
|
|
938
|
+
TabbarStyle(
|
|
939
|
+
String shape,
|
|
940
|
+
int height,
|
|
941
|
+
int horizontalMargin,
|
|
942
|
+
int maxWidth,
|
|
943
|
+
int bottomGap,
|
|
944
|
+
int cornerRadius,
|
|
945
|
+
String centerItemId,
|
|
946
|
+
int centerButtonDiameter,
|
|
947
|
+
int centerButtonLift,
|
|
948
|
+
int centerButtonColor,
|
|
949
|
+
int centerButtonIconColor
|
|
950
|
+
) {
|
|
951
|
+
this.shape = shape;
|
|
952
|
+
this.height = height;
|
|
953
|
+
this.horizontalMargin = horizontalMargin;
|
|
954
|
+
this.maxWidth = maxWidth;
|
|
955
|
+
this.bottomGap = bottomGap;
|
|
956
|
+
this.cornerRadius = cornerRadius;
|
|
957
|
+
this.centerItemId = centerItemId;
|
|
958
|
+
this.centerButtonDiameter = centerButtonDiameter;
|
|
959
|
+
this.centerButtonLift = centerButtonLift;
|
|
960
|
+
this.centerButtonColor = centerButtonColor;
|
|
961
|
+
this.centerButtonIconColor = centerButtonIconColor;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
static TabbarStyle defaults(int tintColor) {
|
|
965
|
+
return new TabbarStyle("floating", 64, 24, 430, 10, 32, null, 76, 38, tintColor, Color.WHITE);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
boolean isCurve() {
|
|
969
|
+
return "curve".equals(shape);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
int barTop() {
|
|
973
|
+
return isCurve() ? centerButtonLift : 0;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
int totalHeight() {
|
|
977
|
+
return height + barTop();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
private static final class NativeTabbarLayout extends FrameLayout {
|
|
982
|
+
|
|
983
|
+
private final Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
984
|
+
private TabbarStyle style = TabbarStyle.defaults(Color.rgb(0, 122, 255));
|
|
985
|
+
private int backgroundColor = Color.argb(235, 255, 255, 255);
|
|
986
|
+
private int centerIndex = -1;
|
|
987
|
+
|
|
988
|
+
NativeTabbarLayout(Context context) {
|
|
989
|
+
super(context);
|
|
990
|
+
setWillNotDraw(false);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
void setTabbarStyle(TabbarStyle style, int backgroundColor, int centerIndex) {
|
|
994
|
+
this.style = style;
|
|
995
|
+
this.backgroundColor = backgroundColor;
|
|
996
|
+
this.centerIndex = centerIndex;
|
|
997
|
+
requestLayout();
|
|
998
|
+
invalidate();
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
@Override
|
|
1002
|
+
protected void onDraw(Canvas canvas) {
|
|
1003
|
+
super.onDraw(canvas);
|
|
1004
|
+
backgroundPaint.setColor(backgroundColor);
|
|
1005
|
+
canvas.drawPath(backgroundPath(getWidth(), getHeight()), backgroundPaint);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
@Override
|
|
1009
|
+
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
1010
|
+
int width = MeasureSpec.getSize(widthMeasureSpec);
|
|
1011
|
+
int height = MeasureSpec.getSize(heightMeasureSpec);
|
|
1012
|
+
if (hasCenterButton()) {
|
|
1013
|
+
int barHeight = dp(style.height);
|
|
1014
|
+
int barTop = dp(style.barTop());
|
|
1015
|
+
int centerGap = Math.min(dp(style.centerButtonDiameter + 22), Math.round(width * 0.46f));
|
|
1016
|
+
int leftWidth = Math.max(0, Math.round(width / 2f - centerGap / 2f));
|
|
1017
|
+
int rightX = Math.min(width, Math.round(width / 2f + centerGap / 2f));
|
|
1018
|
+
measureRange(0, centerIndex, leftWidth, barHeight);
|
|
1019
|
+
measureChildExact(getChildAt(centerIndex), dp(style.centerButtonDiameter), dp(style.centerButtonDiameter));
|
|
1020
|
+
measureRange(centerIndex + 1, getChildCount(), width - rightX, barHeight);
|
|
1021
|
+
setMeasuredDimension(width, Math.max(height, barTop + barHeight));
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
int count = Math.max(getChildCount(), 1);
|
|
1026
|
+
int childWidth = width / count;
|
|
1027
|
+
for (int index = 0; index < getChildCount(); index++) {
|
|
1028
|
+
measureChildExact(getChildAt(index), childWidth, height);
|
|
1029
|
+
}
|
|
1030
|
+
setMeasuredDimension(width, height);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
@Override
|
|
1034
|
+
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
1035
|
+
int width = right - left;
|
|
1036
|
+
int height = bottom - top;
|
|
1037
|
+
if (hasCenterButton()) {
|
|
1038
|
+
int barTop = dp(style.barTop());
|
|
1039
|
+
int barHeight = dp(style.height);
|
|
1040
|
+
int buttonDiameter = dp(style.centerButtonDiameter);
|
|
1041
|
+
int centerGap = Math.min(dp(style.centerButtonDiameter + 22), Math.round(width * 0.46f));
|
|
1042
|
+
int leftWidth = Math.max(0, Math.round(width / 2f - centerGap / 2f));
|
|
1043
|
+
int rightX = Math.min(width, Math.round(width / 2f + centerGap / 2f));
|
|
1044
|
+
View center = getChildAt(centerIndex);
|
|
1045
|
+
center.layout((width - buttonDiameter) / 2, 0, (width + buttonDiameter) / 2, buttonDiameter);
|
|
1046
|
+
layoutRange(0, centerIndex, 0, barTop, leftWidth, barHeight);
|
|
1047
|
+
layoutRange(centerIndex + 1, getChildCount(), rightX, barTop, width - rightX, barHeight);
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
layoutRange(0, getChildCount(), 0, 0, width, height);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private boolean hasCenterButton() {
|
|
1055
|
+
return style.isCurve() && centerIndex >= 0 && centerIndex < getChildCount();
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
private void measureRange(int start, int end, int width, int height) {
|
|
1059
|
+
int count = Math.max(0, end - start);
|
|
1060
|
+
if (count == 0) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
int childWidth = Math.max(0, width / count);
|
|
1064
|
+
for (int index = start; index < end; index++) {
|
|
1065
|
+
measureChildExact(getChildAt(index), childWidth, height);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
private void layoutRange(int start, int end, int left, int top, int width, int height) {
|
|
1070
|
+
int count = Math.max(0, end - start);
|
|
1071
|
+
if (count == 0) {
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
int childWidth = Math.max(0, width / count);
|
|
1075
|
+
for (int index = start; index < end; index++) {
|
|
1076
|
+
int childLeft = left + (index - start) * childWidth;
|
|
1077
|
+
getChildAt(index).layout(childLeft, top, childLeft + childWidth, top + height);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
private void measureChildExact(View child, int width, int height) {
|
|
1082
|
+
child.measure(
|
|
1083
|
+
MeasureSpec.makeMeasureSpec(Math.max(0, width), MeasureSpec.EXACTLY),
|
|
1084
|
+
MeasureSpec.makeMeasureSpec(Math.max(0, height), MeasureSpec.EXACTLY)
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
private Path backgroundPath(int width, int height) {
|
|
1089
|
+
Path path = new Path();
|
|
1090
|
+
if (!style.isCurve()) {
|
|
1091
|
+
float radius = dp(style.cornerRadius);
|
|
1092
|
+
path.addRoundRect(new RectF(0, 0, width, height), radius, radius, Path.Direction.CW);
|
|
1093
|
+
return path;
|
|
1094
|
+
}
|
|
1095
|
+
float barTop = dp(style.barTop());
|
|
1096
|
+
float barHeight = dp(style.height);
|
|
1097
|
+
float cornerRadius = Math.min(dp(style.cornerRadius), barHeight / 2f);
|
|
1098
|
+
float centerX = width / 2f;
|
|
1099
|
+
float notchRadius = dp(style.centerButtonDiameter) / 2f + dp(7);
|
|
1100
|
+
float notchDepth = Math.min(barHeight * 0.78f, notchRadius);
|
|
1101
|
+
float leftShoulder = Math.max(cornerRadius, centerX - notchRadius);
|
|
1102
|
+
float rightShoulder = Math.min(width - cornerRadius, centerX + notchRadius);
|
|
1103
|
+
float control = notchDepth * 0.55228475f;
|
|
1104
|
+
RectF barRect = new RectF(0, barTop, width, barTop + barHeight);
|
|
1105
|
+
|
|
1106
|
+
path.moveTo(barRect.left + cornerRadius, barRect.top);
|
|
1107
|
+
path.lineTo(leftShoulder, barRect.top);
|
|
1108
|
+
path.cubicTo(
|
|
1109
|
+
leftShoulder,
|
|
1110
|
+
barRect.top + control,
|
|
1111
|
+
centerX - control,
|
|
1112
|
+
barRect.top + notchDepth,
|
|
1113
|
+
centerX,
|
|
1114
|
+
barRect.top + notchDepth
|
|
1115
|
+
);
|
|
1116
|
+
path.cubicTo(centerX + control, barRect.top + notchDepth, rightShoulder, barRect.top + control, rightShoulder, barRect.top);
|
|
1117
|
+
path.lineTo(barRect.right - cornerRadius, barRect.top);
|
|
1118
|
+
path.quadTo(barRect.right, barRect.top, barRect.right, barRect.top + cornerRadius);
|
|
1119
|
+
path.lineTo(barRect.right, barRect.bottom - cornerRadius);
|
|
1120
|
+
path.quadTo(barRect.right, barRect.bottom, barRect.right - cornerRadius, barRect.bottom);
|
|
1121
|
+
path.lineTo(barRect.left + cornerRadius, barRect.bottom);
|
|
1122
|
+
path.quadTo(barRect.left, barRect.bottom, barRect.left, barRect.bottom - cornerRadius);
|
|
1123
|
+
path.lineTo(barRect.left, barRect.top + cornerRadius);
|
|
1124
|
+
path.quadTo(barRect.left, barRect.top, barRect.left + cornerRadius, barRect.top);
|
|
1125
|
+
path.close();
|
|
1126
|
+
return path;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
private int dp(int value) {
|
|
1130
|
+
return Math.round(value * getResources().getDisplayMetrics().density);
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
916
1134
|
private static final class SvgIconRenderer {
|
|
917
1135
|
|
|
918
1136
|
private static final Pattern NUMBER_PATTERN = Pattern.compile("[-+]?(?:\\d*\\.\\d+|\\d+\\.?)(?:[eE][-+]?\\d+)?");
|
|
@@ -1091,7 +1309,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1091
1309
|
: Color.rgb(20, 24, 32);
|
|
1092
1310
|
int tint = parseColor(colors.getString("tint", null), tintFallback);
|
|
1093
1311
|
int background = parseColor(colors.getString("background", null), backgroundFallback);
|
|
1094
|
-
navbarBackgroundColor = background;
|
|
1095
1312
|
int foreground = parseColor(colors.getString("foreground", null), foregroundFallback);
|
|
1096
1313
|
tintColor = tint;
|
|
1097
1314
|
nativeToolbar.setTitleTextColor(foreground);
|
|
@@ -1102,8 +1319,9 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1102
1319
|
tintedIcon.setTint(tint);
|
|
1103
1320
|
nativeToolbar.setNavigationIcon(tintedIcon);
|
|
1104
1321
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1322
|
+
if (navbarContainer != null) {
|
|
1323
|
+
navbarContainer.setBackgroundColor(background);
|
|
1324
|
+
}
|
|
1107
1325
|
for (int index = 0; index < nativeToolbar.getMenu().size(); index++) {
|
|
1108
1326
|
Drawable icon = nativeToolbar.getMenu().getItem(index).getIcon();
|
|
1109
1327
|
if (icon != null) {
|
|
@@ -1112,113 +1330,67 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1112
1330
|
}
|
|
1113
1331
|
}
|
|
1114
1332
|
|
|
1115
|
-
private
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
int
|
|
1119
|
-
int
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
int
|
|
1123
|
-
int
|
|
1124
|
-
int
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
applyTabbarMetrics(nativeTabbar);
|
|
1140
|
-
applyChromeBackground(
|
|
1141
|
-
tabbarContainer,
|
|
1142
|
-
tabbarGlassBackdrop,
|
|
1143
|
-
tabbarGlassSurface,
|
|
1144
|
-
background,
|
|
1145
|
-
tabbarGlassOptions,
|
|
1146
|
-
tabbarCornerRadius()
|
|
1333
|
+
private TabbarStyle makeTabbarStyle(JSObject rawStyle) {
|
|
1334
|
+
String requestedShape = rawStyle.getString("shape", "floating");
|
|
1335
|
+
boolean curve = "curve".equalsIgnoreCase(requestedShape);
|
|
1336
|
+
int centerButtonDiameter = Math.max(styleDimension(rawStyle, "centerButtonDiameter", 76), 44);
|
|
1337
|
+
int height = Math.max(styleDimension(rawStyle, "height", curve ? 76 : DEFAULT_TABBAR_DP), 44);
|
|
1338
|
+
int centerButtonLift = Math.max(styleDimension(rawStyle, "centerButtonLift", centerButtonDiameter / 2), 0);
|
|
1339
|
+
int bottomGap = Math.max(styleDimension(rawStyle, "bottomGap", curve ? 0 : 10), 0);
|
|
1340
|
+
int horizontalMargin = Math.max(styleDimension(rawStyle, "horizontalMargin", curve ? 0 : 24), 0);
|
|
1341
|
+
int maxWidth = Math.max(styleDimension(rawStyle, "maxWidth", curve ? 0 : 430), 0);
|
|
1342
|
+
int cornerRadius = Math.max(styleDimension(rawStyle, "cornerRadius", curve ? 24 : height / 2), 0);
|
|
1343
|
+
int centerButtonColor = parseColor(rawStyle.getString("centerButtonColor", null), tintColor);
|
|
1344
|
+
int centerButtonIconColor = parseColor(rawStyle.getString("centerButtonIconColor", null), Color.WHITE);
|
|
1345
|
+
return new TabbarStyle(
|
|
1346
|
+
curve ? "curve" : "floating",
|
|
1347
|
+
height,
|
|
1348
|
+
horizontalMargin,
|
|
1349
|
+
maxWidth,
|
|
1350
|
+
bottomGap,
|
|
1351
|
+
cornerRadius,
|
|
1352
|
+
rawStyle.getString("centerItemId", null),
|
|
1353
|
+
centerButtonDiameter,
|
|
1354
|
+
centerButtonLift,
|
|
1355
|
+
centerButtonColor,
|
|
1356
|
+
centerButtonIconColor
|
|
1147
1357
|
);
|
|
1148
1358
|
}
|
|
1149
1359
|
|
|
1150
|
-
private
|
|
1151
|
-
if (
|
|
1152
|
-
|
|
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);
|
|
1360
|
+
private int styleDimension(JSObject rawStyle, String key, int fallback) {
|
|
1361
|
+
if (rawStyle == null || !rawStyle.has(key)) {
|
|
1362
|
+
return fallback;
|
|
1197
1363
|
}
|
|
1364
|
+
return (int) Math.round(rawStyle.optDouble(key, fallback));
|
|
1198
1365
|
}
|
|
1199
1366
|
|
|
1200
|
-
private
|
|
1201
|
-
if (
|
|
1202
|
-
|
|
1203
|
-
backdrop.setVisibility(View.GONE);
|
|
1367
|
+
private int centerTabIndex() {
|
|
1368
|
+
if (!tabbarStyle.isCurve() || tabItems.isEmpty()) {
|
|
1369
|
+
return -1;
|
|
1204
1370
|
}
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1207
|
-
|
|
1371
|
+
if (tabbarStyle.centerItemId != null) {
|
|
1372
|
+
for (int index = 0; index < tabItems.size(); index++) {
|
|
1373
|
+
if (tabbarStyle.centerItemId.equals(tabItems.get(index).id)) {
|
|
1374
|
+
return index;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1208
1377
|
}
|
|
1378
|
+
return tabItems.size() / 2;
|
|
1209
1379
|
}
|
|
1210
1380
|
|
|
1211
|
-
private
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1381
|
+
private void applyTabbarColors(PluginCall call, JSObject colors) {
|
|
1382
|
+
boolean dynamic = Boolean.TRUE.equals(colors.getBool("dynamic"));
|
|
1383
|
+
int tintFallback = dynamic ? dynamicColor("system_accent1_600", tintColor) : tintColor;
|
|
1384
|
+
int inactiveFallback = dynamic ? dynamicColor("system_neutral2_600", inactiveTintColor) : inactiveTintColor;
|
|
1385
|
+
int backgroundFallback = dynamic
|
|
1386
|
+
? withAlpha(dynamicColor(isNightMode() ? "system_neutral1_900" : "system_neutral1_50", Color.WHITE), 245)
|
|
1387
|
+
: Color.argb(235, 255, 255, 255);
|
|
1388
|
+
tintColor = parseColor(colors.getString("tint", null), tintFallback);
|
|
1389
|
+
inactiveTintColor = parseColor(colors.getString("inactiveTint", null), inactiveFallback);
|
|
1390
|
+
tabbarBackgroundColor = parseColor(colors.getString("background", null), backgroundFallback);
|
|
1391
|
+
if (tabbar != null) {
|
|
1392
|
+
tabbar.setTabbarStyle(tabbarStyle, tabbarBackgroundColor, centerTabIndex());
|
|
1216
1393
|
}
|
|
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));
|
|
1222
1394
|
}
|
|
1223
1395
|
|
|
1224
1396
|
private int parseColor(String value, int fallback) {
|
|
@@ -1279,96 +1451,17 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1279
1451
|
return Color.argb(Math.max(0, Math.min(255, alpha)), Color.red(color), Color.green(color), Color.blue(color));
|
|
1280
1452
|
}
|
|
1281
1453
|
|
|
1454
|
+
private Drawable selectableItemBackground() {
|
|
1455
|
+
TypedValue outValue = new TypedValue();
|
|
1456
|
+
getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true);
|
|
1457
|
+
return AppCompatResources.getDrawable(getContext(), outValue.resourceId);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1282
1460
|
private boolean isNightMode() {
|
|
1283
1461
|
int mode = getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
|
1284
1462
|
return mode == Configuration.UI_MODE_NIGHT_YES;
|
|
1285
1463
|
}
|
|
1286
1464
|
|
|
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
|
-
|
|
1372
1465
|
private void layoutChrome() {
|
|
1373
1466
|
FrameLayout root = contentRoot();
|
|
1374
1467
|
if (root == null) {
|
|
@@ -1377,8 +1470,8 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1377
1470
|
int status = statusBarInset();
|
|
1378
1471
|
int bottom = navigationBarInset();
|
|
1379
1472
|
int navbarHeight = navbarVisible ? status + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1380
|
-
int tabbarHeight =
|
|
1381
|
-
int tabbarBottomMargin = tabbarVisible ?
|
|
1473
|
+
int tabbarHeight = dp(tabbarStyle.totalHeight());
|
|
1474
|
+
int tabbarBottomMargin = tabbarVisible ? bottom + dp(tabbarStyle.bottomGap) : bottom;
|
|
1382
1475
|
|
|
1383
1476
|
if (navbarContainer != null) {
|
|
1384
1477
|
FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(
|
|
@@ -1387,8 +1480,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1387
1480
|
Gravity.TOP
|
|
1388
1481
|
);
|
|
1389
1482
|
navbarContainer.setLayoutParams(containerParams);
|
|
1390
|
-
fillContainer(navbarGlassBackdrop);
|
|
1391
|
-
fillContainer(navbarGlassSurface);
|
|
1392
1483
|
FrameLayout.LayoutParams toolbarParams = new FrameLayout.LayoutParams(
|
|
1393
1484
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1394
1485
|
dp(DEFAULT_NAVBAR_DP),
|
|
@@ -1400,7 +1491,9 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1400
1491
|
|
|
1401
1492
|
if (tabbarContainer != null) {
|
|
1402
1493
|
int rootWidth = root.getWidth() > 0 ? root.getWidth() : Resources.getSystem().getDisplayMetrics().widthPixels;
|
|
1403
|
-
int
|
|
1494
|
+
int availableWidth = Math.max(0, rootWidth - dp(tabbarStyle.horizontalMargin) * 2);
|
|
1495
|
+
int maxWidth = tabbarStyle.maxWidth > 0 ? dp(tabbarStyle.maxWidth) : availableWidth;
|
|
1496
|
+
int tabbarWidth = Math.min(availableWidth, maxWidth);
|
|
1404
1497
|
FrameLayout.LayoutParams tabbarContainerParams = new FrameLayout.LayoutParams(
|
|
1405
1498
|
tabbarWidth,
|
|
1406
1499
|
tabbarHeight,
|
|
@@ -1408,23 +1501,15 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1408
1501
|
);
|
|
1409
1502
|
tabbarContainerParams.bottomMargin = tabbarBottomMargin;
|
|
1410
1503
|
tabbarContainer.setLayoutParams(tabbarContainerParams);
|
|
1411
|
-
fillContainer(tabbarGlassBackdrop);
|
|
1412
|
-
fillContainer(tabbarGlassSurface);
|
|
1413
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
1414
|
-
tabbarContainer.invalidateOutline();
|
|
1415
|
-
tabbarGlassBackdrop.invalidateOutline();
|
|
1416
|
-
tabbarGlassSurface.invalidateOutline();
|
|
1417
|
-
}
|
|
1418
1504
|
}
|
|
1419
1505
|
|
|
1420
1506
|
if (tabbar != null) {
|
|
1421
|
-
applyTabbarMetrics(tabbar);
|
|
1422
1507
|
FrameLayout.LayoutParams tabbarParams = new FrameLayout.LayoutParams(
|
|
1423
1508
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
1424
1509
|
ViewGroup.LayoutParams.MATCH_PARENT
|
|
1425
1510
|
);
|
|
1426
1511
|
tabbar.setLayoutParams(tabbarParams);
|
|
1427
|
-
tabbar.setPadding(0,
|
|
1512
|
+
tabbar.setPadding(0, 0, 0, 0);
|
|
1428
1513
|
}
|
|
1429
1514
|
|
|
1430
1515
|
bringChromeToFront();
|
|
@@ -1480,15 +1565,14 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1480
1565
|
}
|
|
1481
1566
|
|
|
1482
1567
|
private JSObject currentInsets() {
|
|
1483
|
-
int
|
|
1484
|
-
int
|
|
1485
|
-
int bottom = tabbarVisible ? floatingTabbarBottomMargin(navigationBarInset()) + tabbarHeight() : 0;
|
|
1568
|
+
int top = navbarVisible ? statusBarInset() + dp(DEFAULT_NAVBAR_DP) : 0;
|
|
1569
|
+
int bottom = tabbarVisible ? navigationBarInset() + dp(tabbarStyle.totalHeight()) + dp(tabbarStyle.bottomGap) : 0;
|
|
1486
1570
|
JSObject insets = new JSObject();
|
|
1487
1571
|
insets.put("top", top);
|
|
1488
1572
|
insets.put("right", 0);
|
|
1489
1573
|
insets.put("bottom", bottom);
|
|
1490
1574
|
insets.put("left", 0);
|
|
1491
|
-
insets.put("navbarHeight",
|
|
1575
|
+
insets.put("navbarHeight", top);
|
|
1492
1576
|
insets.put("tabbarHeight", bottom);
|
|
1493
1577
|
return insets;
|
|
1494
1578
|
}
|
|
@@ -1589,10 +1673,7 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1589
1673
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
1590
1674
|
WindowInsets insets = getActivity().getWindow().getDecorView().getRootWindowInsets();
|
|
1591
1675
|
if (insets != null) {
|
|
1592
|
-
|
|
1593
|
-
return insets.getInsets(WindowInsets.Type.navigationBars()).bottom;
|
|
1594
|
-
}
|
|
1595
|
-
return insets.getSystemWindowInsetBottom();
|
|
1676
|
+
return insets.getStableInsetBottom();
|
|
1596
1677
|
}
|
|
1597
1678
|
}
|
|
1598
1679
|
return systemDimension("navigation_bar_height");
|
|
@@ -1607,208 +1688,6 @@ public class NativeNavigationPlugin extends Plugin {
|
|
|
1607
1688
|
return Math.round(value * getContext().getResources().getDisplayMetrics().density);
|
|
1608
1689
|
}
|
|
1609
1690
|
|
|
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
|
-
|
|
1812
1691
|
private void runOnUiThread(Runnable runnable) {
|
|
1813
1692
|
Activity activity = getActivity();
|
|
1814
1693
|
if (activity == null) {
|