For the migration to Android 16 of my app, I tested it on a virtual device with a screen that features large radius rounded corners. I found out my views were nibbled by the rounded corners. To avoid that I'd need to add padding to the main activity layout rootview according to the radius of the screen corners.
What I need is precisely adressed in Developers Doc at Apply Rounded Corners.
I managed to insert the given example with the class InsetsLayout in my code:
class InsetsLayout(context: Context, attrs: AttributeSet) : FrameLayout(context, attrs) {
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
val insets = rootWindowInsets
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && insets != null) {
applyRoundedCornerPadding(insets)
}
super.onLayout(changed, left, top, right, bottom)
}
@RequiresApi(Build.VERSION_CODES.S)
private fun applyRoundedCornerPadding(insets: WindowInsets) {
val topLeft = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT)
val topRight = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT)
val bottomLeft = insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT)
val bottomRight = insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT)
val leftRadius = max(topLeft?.radius ?: 0, bottomLeft?.radius ?: 0)
val topRadius = max(topLeft?.radius ?: 0, topRight?.radius ?: 0)
val rightRadius = max(topRight?.radius ?: 0, bottomRight?.radius ?: 0)
val bottomRadius = max(bottomLeft?.radius ?: 0, bottomRight?.radius ?: 0)
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val windowBounds = windowManager.currentWindowMetrics.bounds
val safeArea = Rect(
windowBounds.left + leftRadius,
windowBounds.top + topRadius,
windowBounds.right - rightRadius,
windowBounds.bottom - bottomRadius
)
val location = intArrayOf(0, 0)
getLocationInWindow(location)
val leftMargin = location[0] - windowBounds.left
val topMargin = location[1] - windowBounds.top
val rightMargin = windowBounds.right - right - location[0]
val bottomMargin = windowBounds.bottom - bottom - location[1]
val layoutBounds = Rect(
location[0] + paddingLeft,
location[1] + paddingTop,
location[0] + width - paddingRight,
location[1] + height - paddingBottom
)
if (layoutBounds != safeArea && layoutBounds.contains(safeArea)) {
setPadding(
calculatePadding(leftRadius, leftMargin, paddingLeft),
calculatePadding(topRadius, topMargin, paddingTop),
calculatePadding(rightRadius, rightMargin, paddingRight),
calculatePadding(bottomRadius, bottomMargin, paddingBottom)
)
}
}
private fun calculatePadding(radius1: Int?, radius2: Int?, margin: Int, padding: Int): Int =
(max(radius1 ?: 0, radius2 ?: 0) - margin - padding).coerceAtLeast(0)
}
Unfortunately they do not explain usage of this class. It might be very straitforward but everything I tried did not work. Where in the code and how to make the padding happen ?
Following @CommonsWare comment I made the attrs
parameter nullable and this is how I try to wrap my activity_main
layout in an InsetsLayout
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val parent = InsetsLayout(applicationContext)
parent.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,FrameLayout.LayoutParams.MATCH_PARENT )
setContentView(parent)
hideSystemBars()
}
The result is a blank screen.
By the way, here is the activity_main
layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/fondbleufonce"
android:foregroundTintMode="multiply"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/navigation_graph"
tools:layout_editor_absoluteX="18dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
By the way, here is the activity_main layout:
Replace that with something resembling this:
<?xml version="1.0" encoding="utf-8"?>
<com.simon.says.InsetsLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/fondbleufonce"
android:foregroundTintMode="multiply">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/navigation_graph"
tools:layout_editor_absoluteX="18dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.simon.says.InsetsLayout>
This wraps your ConstraintLayout
in an InsetsLayout
that is set to fill the available space, with some utility attributes moved up from the ConstraintLayout
to the InsetsLayout
.
You will need to replace com.simon.says.InsetsLayout
with the correct fully-qualified class name of your edition of InsetsLayout
. It is possible that you will also need to implement a different constructor on InsetsLayout
, but try what you have and see if it works.