Skip to content
Open
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
@@ -1,4 +1,4 @@
// Copyright 2025 Google LLC
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,8 @@
import android.os.Bundle;
import android.view.View;

import android.view.ViewGroup;

import androidx.activity.EdgeToEdge;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
Expand All @@ -33,18 +35,67 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
EdgeToEdge.enable(this);
}

@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
setupEdgeToEdgeInsets();
}

@Override
public void setContentView(View view) {
super.setContentView(view);
setupEdgeToEdgeInsets();
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(view, params);
setupEdgeToEdgeInsets();
}

@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
super.addContentView(view, params);
setupEdgeToEdgeInsets();
}

private void setupEdgeToEdgeInsets() {
View root = findViewById(android.R.id.content);
if (root == null) return;
View topBar = root.findViewById(com.example.common_ui.R.id.top_bar);
if (topBar != null) {
android.util.TypedValue typedValue = new android.util.TypedValue();
int baseHeight;
if (getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) {
baseHeight = android.util.TypedValue.complexToDimensionPixelSize(typedValue.data, getResources().getDisplayMetrics());
} else {
baseHeight = (int) (56 * getResources().getDisplayMetrics().density);
}
ViewCompat.setOnApplyWindowInsetsListener(topBar, (view, insets) -> {
Insets statusBar = insets.getInsets(WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.displayCutout());
view.setPadding(statusBar.left, statusBar.top, statusBar.right, 0);
view.getLayoutParams().height = baseHeight + statusBar.top;
view.requestLayout();
return insets;
});
}

View mapContainer = root.findViewById(com.example.common_ui.R.id.map_container);
View bottomTarget = mapContainer != null ? mapContainer : root;
ViewCompat.setOnApplyWindowInsetsListener(bottomTarget, (view, insets) -> {
Insets navBars = insets.getInsets(WindowInsetsCompat.Type.navigationBars() | WindowInsetsCompat.Type.displayCutout());
int topInsets = (topBar == null) ? insets.getInsets(WindowInsetsCompat.Type.statusBars()).top : 0;
view.setPadding(navBars.left, topInsets, navBars.right, navBars.bottom);
return insets;
});
}

/**
* Applies insets to the container view to properly handle window insets.
*
* @param container the container view to apply insets to
*/
protected static void applyInsets(View container) {
ViewCompat.setOnApplyWindowInsetsListener(container,
(view, insets) -> {
Insets innerPadding = insets.getInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
view.setPadding(innerPadding.left, innerPadding.top, innerPadding.right, innerPadding.bottom);
return insets;
}
);
// Handled automatically in SamplesBaseActivity
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018 Google LLC
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,7 +36,7 @@ class OnMapAndViewReadyListener(
) : OnGlobalLayoutListener,
OnMapReadyCallback {

private val mapView: View? = mapFragment.view
private var mapView: View? = null

private var isViewReady = false
private var isMapReady = false
Expand All @@ -52,16 +52,6 @@ class OnMapAndViewReadyListener(
}

private fun registerListeners() {
// View layout.
mapView?.let {
if (it.width != 0 && it.height != 0) {
// View has already completed layout.
isViewReady = true
} else {
// Map has not undergone layout, register a View observer.
it.viewTreeObserver.addOnGlobalLayoutListener(this)
}
}
// GoogleMap. Note if the GoogleMap is already ready it will still fire the callback later.
mapFragment.getMapAsync(this)
}
Expand All @@ -70,6 +60,22 @@ class OnMapAndViewReadyListener(
// NOTE: The GoogleMap API specifies the listener is removed just prior to invocation.
map = googleMap
isMapReady = true

// View layout.
mapView = mapFragment.view
val view = mapView
if (view != null) {
if (view.width != 0 && view.height != 0) {
// View has already completed layout.
isViewReady = true
} else {
// Map has not undergone layout, register a View observer.
view.viewTreeObserver.addOnGlobalLayoutListener(this)
}
} else {
isViewReady = true
}

fireCallbackIfReady()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2025 Google LLC
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -15,9 +15,9 @@ package com.example.kotlindemos

import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

Expand All @@ -27,27 +27,81 @@ open class SamplesBaseActivity : AppCompatActivity() {
enableEdgeToEdge()
}

override fun setContentView(layoutResID: Int) {
super.setContentView(layoutResID)
setupEdgeToEdgeInsets()
}

override fun setContentView(view: View?) {
super.setContentView(view)
setupEdgeToEdgeInsets()
}

override fun setContentView(view: View?, params: ViewGroup.LayoutParams?) {
super.setContentView(view, params)
setupEdgeToEdgeInsets()
}

override fun addContentView(view: View?, params: ViewGroup.LayoutParams?) {
super.addContentView(view, params)
setupEdgeToEdgeInsets()
}

private fun setupEdgeToEdgeInsets() {
val root = findViewById<View>(android.R.id.content) ?: return
val topBar = root.findViewById<View>(com.example.common_ui.R.id.top_bar)
if (topBar != null) {
val typedValue = android.util.TypedValue()
val baseHeight = if (theme.resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) {
android.util.TypedValue.complexToDimensionPixelSize(typedValue.data, resources.displayMetrics)
} else {
(56 * resources.displayMetrics.density).toInt()
}
ViewCompat.setOnApplyWindowInsetsListener(topBar) { view, insets ->
val statusBar = insets.getInsets(
WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.displayCutout()
)
view.setPadding(
statusBar.left,
statusBar.top,
statusBar.right,
0
)
view.layoutParams.height = baseHeight + statusBar.top
view.requestLayout()
insets
}
}

val mapContainer = root.findViewById<View>(com.example.common_ui.R.id.map_container)
val bottomTarget = mapContainer ?: root
ViewCompat.setOnApplyWindowInsetsListener(bottomTarget) { view, insets ->
val navBars = insets.getInsets(
WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.displayCutout()
)
val topInsets = if (topBar == null) {
insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
} else {
0
}
view.setPadding(
navBars.left,
topInsets,
navBars.right,
navBars.bottom
)
insets
}
}

companion object {
/**
* Applies insets to the container view to properly handle window insets.
*
* @param container the container view to apply insets to
*/
fun applyInsets(container: View) {
ViewCompat.setOnApplyWindowInsetsListener(
container,
OnApplyWindowInsetsListener { view: View?, insets: WindowInsetsCompat? ->
val innerPadding =
insets!!.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
view!!.setPadding(
innerPadding.left,
innerPadding.top,
innerPadding.right,
innerPadding.bottom
)
insets
}
)
fun applyInsets(container: View? = null) {
// Handled automatically in SamplesBaseActivity
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Google LLC
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@ package com.example.kotlindemos
import android.os.Bundle
import android.os.Parcelable

import androidx.core.os.BundleCompat
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener
Expand All @@ -26,7 +27,8 @@ import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.maps.android.ktx.addMarker
import com.google.maps.android.ktx.awaitMap
import kotlinx.android.parcel.Parcelize
import kotlinx.parcelize.Parcelize
import kotlinx.coroutines.launch
import java.util.Random

/**
Expand Down Expand Up @@ -73,13 +75,15 @@ class SaveStateDemoActivity : SamplesBaseActivity() {
// the savedInsanceState Bundle.
// - Custom Parcelable objects were wrapped in another Bundle.
mMarkerPosition =
savedInstanceState?.getParcelable(MARKER_POSITION) ?: DEFAULT_MARKER_POSITION
savedInstanceState?.let { BundleCompat.getParcelable(it, MARKER_POSITION, LatLng::class.java) }
?: DEFAULT_MARKER_POSITION
mMarkerInfo =
savedInstanceState?.getBundle(OTHER_OPTIONS)?.getParcelable(MARKER_INFO) ?: MarkerInfo(
BitmapDescriptorFactory.HUE_RED)
savedInstanceState?.getBundle(OTHER_OPTIONS)?.let {
BundleCompat.getParcelable(it, MARKER_INFO, MarkerInfo::class.java)
} ?: MarkerInfo(BitmapDescriptorFactory.HUE_RED)
mMoveCameraToMarker = savedInstanceState == null

lifecycleScope.launchWhenCreated {
lifecycleScope.launch {
val map = awaitMap()
map.addMarker {
icon(BitmapDescriptorFactory.defaultMarker(mMarkerInfo.hue))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright 2026 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/map_container"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/top_bar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/save_state_demo_label" />

<com.google.android.material.textview.MaterialTextView
android:id="@+id/instructions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/save_state_instructions"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_bar" />

<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
class="com.example.kotlindemos.SaveStateDemoActivity$SaveStateMapFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/instructions" />

</androidx.constraintlayout.widget.ConstraintLayout>
Loading