5
5
import android .view .ViewGroup ;
6
6
7
7
import androidx .annotation .NonNull ;
8
+ import androidx .annotation .Nullable ;
9
+ import androidx .appcompat .widget .Toolbar ;
8
10
import androidx .core .graphics .Insets ;
11
+ import androidx .core .view .MarginLayoutParamsCompat ;
9
12
import androidx .core .view .ViewCompat ;
10
13
import androidx .core .view .ViewGroupCompat ;
11
14
import androidx .core .view .WindowCompat ;
12
15
import androidx .core .view .WindowInsetsCompat ;
13
16
14
17
import com .d4rk .androidtutorials .java .R ;
18
+ import com .google .android .material .appbar .AppBarLayout ;
19
+ import com .google .android .material .bottomappbar .BottomAppBar ;
20
+ import com .google .android .material .floatingactionbutton .ExtendedFloatingActionButton ;
21
+ import com .google .android .material .floatingactionbutton .FloatingActionButton ;
22
+ import com .google .android .material .navigation .NavigationBarView ;
15
23
16
24
public final class EdgeToEdgeDelegate {
17
25
@@ -21,37 +29,133 @@ private EdgeToEdgeDelegate() {
21
29
22
30
public static void apply (Activity activity , View view ) {
23
31
enableEdgeToEdge (activity );
24
- applyInsetsAsPadding (
32
+ applyInsetsInternal (
25
33
view ,
26
- WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (),
27
- true ,
28
- true
34
+ null ,
35
+ WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout ()
29
36
);
30
37
}
31
38
32
39
public static void applyBottomBar (Activity activity , View container , View bottomNavigationView ) {
33
40
enableEdgeToEdge (activity );
34
- applyInsetsAsPadding (
41
+ applyInsetsInternal (
35
42
container ,
36
- WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (),
37
- true ,
38
- false
39
- );
40
- applyInsetsAsPadding (
41
43
bottomNavigationView ,
42
- WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (),
43
- false ,
44
- true
44
+ WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout ()
45
45
);
46
46
}
47
47
48
+ private static void applyInsetsInternal (View root , @ Nullable View explicitBottomBar , int insetTypes ) {
49
+ if (root == null ) {
50
+ return ;
51
+ }
52
+
53
+ View topBar = null ;
54
+ View bottomBar = explicitBottomBar ;
55
+ if (root instanceof ViewGroup viewGroup ) {
56
+ topBar = findTopBar (viewGroup );
57
+ if (bottomBar == null ) {
58
+ bottomBar = findBottomBar (viewGroup );
59
+ }
60
+ applyInsetsToFloatingButtons (viewGroup , insetTypes );
61
+ }
62
+
63
+ if (topBar != null && topBar != root ) {
64
+ applyInsetsAsPadding (topBar , insetTypes , true , false );
65
+ }
66
+ if (bottomBar != null && bottomBar != root ) {
67
+ applyInsetsAsPadding (bottomBar , insetTypes , false , true );
68
+ }
69
+
70
+ boolean applyTop = topBar == null || topBar == root ;
71
+ boolean applyBottom = bottomBar == null || bottomBar == root ;
72
+ applyInsetsAsPadding (root , insetTypes , applyTop , applyBottom );
73
+ }
74
+
48
75
private static void enableEdgeToEdge (Activity activity ) {
49
76
if (activity == null ) {
50
77
return ;
51
78
}
52
79
WindowCompat .enableEdgeToEdge (activity .getWindow ());
53
80
}
54
81
82
+ @ Nullable
83
+ private static View findTopBar (View view ) {
84
+ if (!isVisible (view )) {
85
+ return null ;
86
+ }
87
+ if (isTopBar (view )) {
88
+ return view ;
89
+ }
90
+ if (view instanceof ViewGroup viewGroup ) {
91
+ for (int i = 0 ; i < viewGroup .getChildCount (); i ++) {
92
+ View child = viewGroup .getChildAt (i );
93
+ View topBar = findTopBar (child );
94
+ if (topBar != null ) {
95
+ return topBar ;
96
+ }
97
+ }
98
+ }
99
+ return null ;
100
+ }
101
+
102
+ @ Nullable
103
+ private static View findBottomBar (View view ) {
104
+ if (!isVisible (view )) {
105
+ return null ;
106
+ }
107
+ if (isBottomBar (view )) {
108
+ return view ;
109
+ }
110
+ if (view instanceof ViewGroup viewGroup ) {
111
+ for (int i = 0 ; i < viewGroup .getChildCount (); i ++) {
112
+ View child = viewGroup .getChildAt (i );
113
+ View bottomBar = findBottomBar (child );
114
+ if (bottomBar != null ) {
115
+ return bottomBar ;
116
+ }
117
+ }
118
+ }
119
+ return null ;
120
+ }
121
+
122
+ private static void applyInsetsToFloatingButtons (View view , int insetTypes ) {
123
+ if (!isVisible (view )) {
124
+ return ;
125
+ }
126
+ if (view instanceof ExtendedFloatingActionButton || view instanceof FloatingActionButton ) {
127
+ applyInsetsAsMargin (view , insetTypes , false , true );
128
+ return ;
129
+ }
130
+ if (view instanceof ViewGroup viewGroup ) {
131
+ for (int i = 0 ; i < viewGroup .getChildCount (); i ++) {
132
+ applyInsetsToFloatingButtons (viewGroup .getChildAt (i ), insetTypes );
133
+ }
134
+ }
135
+ }
136
+
137
+ private static boolean isTopBar (View view ) {
138
+ int id = view .getId ();
139
+ return view instanceof AppBarLayout
140
+ || view instanceof Toolbar
141
+ || id == R .id .app_bar_layout
142
+ || id == R .id .toolbar
143
+ || id == R .id .top_app_bar ;
144
+ }
145
+
146
+ private static boolean isBottomBar (View view ) {
147
+ int id = view .getId ();
148
+ return view instanceof NavigationBarView
149
+ || view instanceof BottomAppBar
150
+ || id == R .id .nav_view
151
+ || id == R .id .bottom_nav
152
+ || id == R .id .bottomBar ;
153
+ }
154
+
155
+ private static boolean isVisible (View view ) {
156
+ return view .getVisibility () == View .VISIBLE ;
157
+ }
158
+
55
159
private static void applyInsetsAsPadding (View view , int insetTypes ,
56
160
boolean applyTop ,
57
161
boolean applyBottom ) {
@@ -95,6 +199,43 @@ private static void applyInsetsAsPadding(View view, int insetTypes,
95
199
requestApplyInsetsWhenAttached (view );
96
200
}
97
201
202
+ private static void applyInsetsAsMargin (View view , int insetTypes ,
203
+ boolean applyTop ,
204
+ boolean applyBottom ) {
205
+ if (view == null ) {
206
+ return ;
207
+ }
208
+ ViewGroup .LayoutParams layoutParams = view .getLayoutParams ();
209
+ if (!(layoutParams instanceof ViewGroup .MarginLayoutParams marginLayoutParams )) {
210
+ return ;
211
+ }
212
+
213
+ InsetsMargin baseMargin = (InsetsMargin ) view .getTag (R .id .tag_edge_to_edge_margin );
214
+ if (baseMargin == null ) {
215
+ baseMargin = new InsetsMargin (
216
+ MarginLayoutParamsCompat .getMarginStart (marginLayoutParams ),
217
+ marginLayoutParams .topMargin ,
218
+ MarginLayoutParamsCompat .getMarginEnd (marginLayoutParams ),
219
+ marginLayoutParams .bottomMargin
220
+ );
221
+ view .setTag (R .id .tag_edge_to_edge_margin , baseMargin );
222
+ }
223
+
224
+ InsetsMargin margin = baseMargin ;
225
+ ViewCompat .setOnApplyWindowInsetsListener (view , (v , windowInsets ) -> {
226
+ Insets insets = windowInsets .getInsets (insetTypes );
227
+ ViewGroup .MarginLayoutParams lp = (ViewGroup .MarginLayoutParams ) v .getLayoutParams ();
228
+ MarginLayoutParamsCompat .setMarginStart (lp , margin .start + insets .left );
229
+ lp .topMargin = margin .top + (applyTop ? insets .top : 0 );
230
+ MarginLayoutParamsCompat .setMarginEnd (lp , margin .end + insets .right );
231
+ lp .bottomMargin = margin .bottom + (applyBottom ? insets .bottom : 0 );
232
+ v .setLayoutParams (lp );
233
+ return windowInsets ;
234
+ });
235
+
236
+ requestApplyInsetsWhenAttached (view );
237
+ }
238
+
98
239
private static void requestApplyInsetsWhenAttached (@ NonNull View view ) {
99
240
if (ViewCompat .isAttachedToWindow (view )) { // FIXME: 'isAttachedToWindow(android.view.@org.jspecify.annotations.NonNull View)' is deprecated
100
241
ViewCompat .requestApplyInsets (view );
@@ -116,4 +257,7 @@ public void onViewDetachedFromWindow(@NonNull View v) {
116
257
117
258
private record InsetsPadding (int start , int top , int end , int bottom ) {
118
259
}
260
+
261
+ private record InsetsMargin (int start , int top , int end , int bottom ) {
262
+ }
119
263
}
0 commit comments