55import android .view .ViewGroup ;
66
77import androidx .annotation .NonNull ;
8+ import androidx .annotation .Nullable ;
9+ import androidx .appcompat .widget .Toolbar ;
810import androidx .core .graphics .Insets ;
11+ import androidx .core .view .MarginLayoutParamsCompat ;
912import androidx .core .view .ViewCompat ;
1013import androidx .core .view .ViewGroupCompat ;
1114import androidx .core .view .WindowCompat ;
1215import androidx .core .view .WindowInsetsCompat ;
1316
1417import 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 ;
1523
1624public final class EdgeToEdgeDelegate {
1725
@@ -21,37 +29,133 @@ private EdgeToEdgeDelegate() {
2129
2230 public static void apply (Activity activity , View view ) {
2331 enableEdgeToEdge (activity );
24- applyInsetsAsPadding (
32+ applyInsetsInternal (
2533 view ,
26- WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (),
27- true ,
28- true
34+ null ,
35+ WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout ()
2936 );
3037 }
3138
3239 public static void applyBottomBar (Activity activity , View container , View bottomNavigationView ) {
3340 enableEdgeToEdge (activity );
34- applyInsetsAsPadding (
41+ applyInsetsInternal (
3542 container ,
36- WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (),
37- true ,
38- false
39- );
40- applyInsetsAsPadding (
4143 bottomNavigationView ,
42- WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout (),
43- false ,
44- true
44+ WindowInsetsCompat .Type .systemBars () | WindowInsetsCompat .Type .displayCutout ()
4545 );
4646 }
4747
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+
4875 private static void enableEdgeToEdge (Activity activity ) {
4976 if (activity == null ) {
5077 return ;
5178 }
5279 WindowCompat .enableEdgeToEdge (activity .getWindow ());
5380 }
5481
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+
55159 private static void applyInsetsAsPadding (View view , int insetTypes ,
56160 boolean applyTop ,
57161 boolean applyBottom ) {
@@ -95,6 +199,43 @@ private static void applyInsetsAsPadding(View view, int insetTypes,
95199 requestApplyInsetsWhenAttached (view );
96200 }
97201
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+
98239 private static void requestApplyInsetsWhenAttached (@ NonNull View view ) {
99240 if (ViewCompat .isAttachedToWindow (view )) { // FIXME: 'isAttachedToWindow(android.view.@org.jspecify.annotations.NonNull View)' is deprecated
100241 ViewCompat .requestApplyInsets (view );
@@ -116,4 +257,7 @@ public void onViewDetachedFromWindow(@NonNull View v) {
116257
117258 private record InsetsPadding (int start , int top , int end , int bottom ) {
118259 }
260+
261+ private record InsetsMargin (int start , int top , int end , int bottom ) {
262+ }
119263}
0 commit comments