Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.Insets;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.ViewGroupCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;

import com.d4rk.androidtutorials.java.R;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomappbar.BottomAppBar;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.navigation.NavigationBarView;

public final class EdgeToEdgeDelegate {

Expand All @@ -21,37 +29,133 @@ private EdgeToEdgeDelegate() {

public static void apply(Activity activity, View view) {
enableEdgeToEdge(activity);
applyInsetsAsPadding(
applyInsetsInternal(
view,
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
true,
true
null,
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
);
}

public static void applyBottomBar(Activity activity, View container, View bottomNavigationView) {
enableEdgeToEdge(activity);
applyInsetsAsPadding(
applyInsetsInternal(
container,
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
true,
false
);
applyInsetsAsPadding(
bottomNavigationView,
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
false,
true
WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout()
);
}

private static void applyInsetsInternal(View root, @Nullable View explicitBottomBar, int insetTypes) {
if (root == null) {
return;
}

View topBar = null;
View bottomBar = explicitBottomBar;
if (root instanceof ViewGroup viewGroup) {
topBar = findTopBar(viewGroup);
if (bottomBar == null) {
bottomBar = findBottomBar(viewGroup);
}
applyInsetsToFloatingButtons(viewGroup, insetTypes);
}

if (topBar != null && topBar != root) {
applyInsetsAsPadding(topBar, insetTypes, true, false);
}
if (bottomBar != null && bottomBar != root) {
applyInsetsAsPadding(bottomBar, insetTypes, false, true);
}

boolean applyTop = topBar == null || topBar == root;
boolean applyBottom = bottomBar == null || bottomBar == root;
applyInsetsAsPadding(root, insetTypes, applyTop, applyBottom);
}

private static void enableEdgeToEdge(Activity activity) {
if (activity == null) {
return;
}
WindowCompat.enableEdgeToEdge(activity.getWindow());
}

@Nullable
private static View findTopBar(View view) {
if (!isVisible(view)) {
return null;
}
if (isTopBar(view)) {
return view;
}
if (view instanceof ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
View topBar = findTopBar(child);
if (topBar != null) {
return topBar;
}
}
}
return null;
}

@Nullable
private static View findBottomBar(View view) {
if (!isVisible(view)) {
return null;
}
if (isBottomBar(view)) {
return view;
}
if (view instanceof ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View child = viewGroup.getChildAt(i);
View bottomBar = findBottomBar(child);
if (bottomBar != null) {
return bottomBar;
}
}
}
return null;
}

private static void applyInsetsToFloatingButtons(View view, int insetTypes) {
if (!isVisible(view)) {
return;
}
if (view instanceof ExtendedFloatingActionButton || view instanceof FloatingActionButton) {
applyInsetsAsMargin(view, insetTypes, false, true);
return;
}
Comment on lines +122 to +129

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Apply FAB inset margins regardless of initial visibility

applyInsetsToFloatingButtons bails out when the view is not VISIBLE, so it never attaches the inset listener to FloatingActionButtons that start as INVISIBLE/GONE and are shown later. Those buttons keep their baseline margins and will overlap navigation gesture areas when they become visible, defeating the goal of this delegate. The inset listener should be attached even when the button is hidden so that margins are updated before it is displayed.

Useful? React with 👍 / 👎.

if (view instanceof ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
applyInsetsToFloatingButtons(viewGroup.getChildAt(i), insetTypes);
}
}
}

private static boolean isTopBar(View view) {
int id = view.getId();
return view instanceof AppBarLayout
|| view instanceof Toolbar
|| id == R.id.app_bar_layout
|| id == R.id.toolbar
|| id == R.id.top_app_bar;
}

private static boolean isBottomBar(View view) {
int id = view.getId();
return view instanceof NavigationBarView
|| view instanceof BottomAppBar
|| id == R.id.nav_view
|| id == R.id.bottom_nav
|| id == R.id.bottomBar;
}

private static boolean isVisible(View view) {
return view.getVisibility() == View.VISIBLE;
}

private static void applyInsetsAsPadding(View view, int insetTypes,
boolean applyTop,
boolean applyBottom) {
Expand Down Expand Up @@ -95,6 +199,43 @@ private static void applyInsetsAsPadding(View view, int insetTypes,
requestApplyInsetsWhenAttached(view);
}

private static void applyInsetsAsMargin(View view, int insetTypes,
boolean applyTop,
boolean applyBottom) {
if (view == null) {
return;
}
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if (!(layoutParams instanceof ViewGroup.MarginLayoutParams marginLayoutParams)) {
return;
}

InsetsMargin baseMargin = (InsetsMargin) view.getTag(R.id.tag_edge_to_edge_margin);
if (baseMargin == null) {
baseMargin = new InsetsMargin(
MarginLayoutParamsCompat.getMarginStart(marginLayoutParams),
marginLayoutParams.topMargin,
MarginLayoutParamsCompat.getMarginEnd(marginLayoutParams),
marginLayoutParams.bottomMargin
);
view.setTag(R.id.tag_edge_to_edge_margin, baseMargin);
}

InsetsMargin margin = baseMargin;
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(insetTypes);
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
MarginLayoutParamsCompat.setMarginStart(lp, margin.start + insets.left);
lp.topMargin = margin.top + (applyTop ? insets.top : 0);
MarginLayoutParamsCompat.setMarginEnd(lp, margin.end + insets.right);
lp.bottomMargin = margin.bottom + (applyBottom ? insets.bottom : 0);
v.setLayoutParams(lp);
return windowInsets;
});

requestApplyInsetsWhenAttached(view);
}

private static void requestApplyInsetsWhenAttached(@NonNull View view) {
if (ViewCompat.isAttachedToWindow(view)) { // FIXME: 'isAttachedToWindow(android.view.@org.jspecify.annotations.NonNull View)' is deprecated
ViewCompat.requestApplyInsets(view);
Expand All @@ -116,4 +257,7 @@ public void onViewDetachedFromWindow(@NonNull View v) {

private record InsetsPadding(int start, int top, int end, int bottom) {
}

private record InsetsMargin(int start, int top, int end, int bottom) {
}
}
5 changes: 2 additions & 3 deletions app/src/main/res/layout/activity_help.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="24dp"
android:layout_marginVertical="24dp"
android:fitsSystemWindows="true">
android:paddingHorizontal="24dp"
android:paddingVertical="24dp">

<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_view_faq"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/ids.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="tag_edge_to_edge_padding" type="id" />
<item name="tag_edge_to_edge_margin" type="id" />
</resources>
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
agp = "8.13.0"
firebaseCrashlyticsPlugin = "3.0.6"
googleServices = "4.4.3"
ossLicensesPlugin = "0.10.6"
ossLicensesPlugin = "0.10.9"
appcompat = "1.7.1"
appUpdate = "2.1.0"
billing = "8.0.0"
Expand Down
2 changes: 1 addition & 1 deletion settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.id == 'com.google.android.gms.oss-licenses-plugin') {
useModule('com.google.android.gms:oss-licenses-plugin:0.10.6')
useModule('com.google.android.gms:oss-licenses-plugin:0.10.9')
}
}
}
Expand Down