A partir de Android 12 (nivel de API 31), puedes usar
RoundedCorner
y
WindowInsets.getRoundedCorner(int
position)
para obtener
el radio y el punto central de las esquinas redondeadas de la pantalla del dispositivo. Estas APIs
para evitar que los elementos de la IU de la app se trunquen en pantallas con
esquinas. El framework proporciona la
getPrivacyIndicatorBounds()
API, que muestra el rectángulo delimitado de cualquier micrófono y cámara visibles
indicadores.
Cuando estas API se implementan en la app, no producen efectos en los dispositivos con pantallas sin esquinas redondeadas.
Para implementar esta función, obtén la información de RoundedCorner
con
WindowInsets.getRoundedCorner(int position)
en relación con los límites de la
y mantener la integridad de su aplicación. Si la app no ocupa toda la pantalla, la API aplica la
esquina redondeada basando el punto central de la esquina redondeada en la ventana
límites de la app.
En el siguiente fragmento de código, se muestra cómo una app puede evitar que su IU se trunque:
Estableciendo un margen de la vista según la información de RoundedCorner
En este
caso, es la esquina redondeada superior derecha.
Kotlin
// Get the top-right rounded corner from WindowInsets. val insets = rootWindowInsets val topRight = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT) ?: return // Get the location of the close button in window coordinates. val location = IntArray(2) closeButton!!.getLocationInWindow(location) val buttonRightInWindow = location[0] + closeButton.width val buttonTopInWindow = location[1] // Find the point on the quarter circle with a 45-degree angle. val offset = (topRight.radius * Math.sin(Math.toRadians(45.0))).toInt() val topBoundary = topRight.center.y - offset val rightBoundary = topRight.center.x + offset // Check whether the close button exceeds the boundary. if (buttonRightInWindow < rightBoundary << buttonTopInWindow > topBoundary) { return } // Set the margin to avoid truncating. val parentLocation = IntArray(2) getLocationInWindow(parentLocation) val lp = closeButton.layoutParams as FrameLayout.LayoutParams lp.rightMargin = Math.max(buttonRightInWindow - rightBoundary, 0) lp.topMargin = Math.max(topBoundary - buttonTopInWindow, 0) closeButton.layoutParams = lp
Java
// Get the top-right rounded corner from WindowInsets. final WindowInsets insets = getRootWindowInsets(); final RoundedCorner topRight = insets.getRoundedCorner(POSITION_TOP_RIGHT); if (topRight == null) { return; } // Get the location of the close button in window coordinates. int [] location = new int[2]; closeButton.getLocationInWindow(location); final int buttonRightInWindow = location[0] + closeButton.getWidth(); final int buttonTopInWindow = location[1]; // Find the point on the quarter circle with a 45-degree angle. final int offset = (int) (topRight.getRadius() * Math.sin(Math.toRadians(45))); final int topBoundary = topRight.getCenter().y - offset; final int rightBoundary = topRight.getCenter().x + offset; // Check whether the close button exceeds the boundary. if (buttonRightInWindow < rightBoundary << buttonTopInWindow > topBoundary) { return; } // Set the margin to avoid truncating. int [] parentLocation = new int[2]; getLocationInWindow(parentLocation); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) closeButton.getLayoutParams(); lp.rightMargin = Math.max(buttonRightInWindow - rightBoundary, 0); lp.topMargin = Math.max(topBoundary - buttonTopInWindow, 0); closeButton.setLayoutParams(lp);
Ten cuidado con los recortes
Si la IU ocupa toda la pantalla, las esquinas redondeadas pueden causar problemas con el contenido. recortes. Por ejemplo, en la figura 2, se muestra un ícono en la esquina de la pantalla con el diseño que se dibuja detrás de las barras del sistema:
Puedes evitar esto comprobando las esquinas redondeadas y aplicando padding para mantener el contenido de tu app fuera de las esquinas del dispositivo, como se muestra en el siguiente ejemplo:
Kotlin
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) }
Java
public class InsetsLayout extends FrameLayout { public InsetsLayout(@NonNull Context context) { super(context); } public InsetsLayout(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { WindowInsets insets = getRootWindowInsets(); 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 void applyRoundedCornerPadding(WindowInsets insets) { RoundedCorner topLeft = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT); RoundedCorner topRight = insets.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT); RoundedCorner bottomLeft = insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); RoundedCorner bottomRight = insets.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT); int radiusTopLeft = 0; int radiusTopRight = 0; int radiusBottomLeft = 0; int radiusBottomRight = 0; if (topLeft != null) radiusTopLeft = topLeft.getRadius(); if (topRight != null) radiusTopRight = topRight.getRadius(); if (bottomLeft != null) radiusBottomLeft = bottomLeft.getRadius(); if (bottomRight != null) radiusBottomRight = bottomRight.getRadius(); int leftRadius = Math.max(radiusTopLeft, radiusBottomLeft); int topRadius = Math.max(radiusTopLeft, radiusTopRight); int rightRadius = Math.max(radiusTopRight, radiusBottomRight); int bottomRadius = Math.max(radiusBottomLeft, radiusBottomRight); WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); Rect windowBounds = windowManager.getCurrentWindowMetrics().getBounds(); Rect safeArea = new Rect( windowBounds.left + leftRadius, windowBounds.top + topRadius, windowBounds.right - rightRadius, windowBounds.bottom - bottomRadius ); int[] location = {0, 0}; getLocationInWindow(location); int leftMargin = location[0] - windowBounds.left; int topMargin = location[1] - windowBounds.top; int rightMargin = windowBounds.right - getRight() - location[0]; int bottomMargin = windowBounds.bottom - getBottom() - location[1]; Rect layoutBounds = new Rect( location[0] + getPaddingLeft(), location[1] + getPaddingTop(), location[0] + getWidth() - getPaddingRight(), location[1] + getHeight() - getPaddingBottom() ); if (!layoutBounds.equals(safeArea) && layoutBounds.contains(safeArea)) { setPadding( calculatePadding(radiusTopLeft, radiusBottomLeft, leftMargin, getPaddingLeft()), calculatePadding(radiusTopLeft, radiusTopRight, topMargin, getPaddingTop()), calculatePadding(radiusTopRight, radiusBottomRight, rightMargin, getPaddingRight()), calculatePadding(radiusBottomLeft, radiusBottomRight, bottomMargin, getPaddingBottom()) ); } } private int calculatePadding(int radius1, int radius2, int margin, int padding) { return Math.max(Math.max(radius1, radius2) - margin - padding, 0); } }
Este diseño determina si la IU se extiende al área de las esquinas redondeadas. y agrega padding donde lo hace. En la Figura 3, se muestra la sección "Show layout bounds" desarrollador para mostrar el padding con más claridad:
Para hacer esta determinación, este diseño calcula dos rectángulos: safeArea
es
el área dentro del radio de las esquinas redondeadas, y layoutBounds
es el tamaño
del diseño menos cualquier relleno. Si layoutArea
contiene safeArea
por completo, entonces
los elementos secundarios del diseño podrían recortarse. Si este es el caso, el relleno es
Agregado para garantizar que el diseño permanezca dentro de safeArea
.
Si verificas si layoutBounds
incluye safeArea
por completo, evitas agregar
relleno cuando el diseño no se extiende a los bordes de la pantalla. Figura 4
muestra el diseño cuando no se dibuja detrás de la barra de navegación. En este caso,
el diseño no se extiende lo suficiente hacia abajo como para estar dentro de las esquinas redondeadas, ya que
se ajusten al área ocupada por la barra de navegación. No se requiere relleno.