Kể từ Android 12 (API cấp 31), bạn có thể sử dụng
RoundedCorner
và
WindowInsets.getRoundedCorner(int
position)
để nhận
bán kính và điểm giữa cho các góc bo tròn của màn hình thiết bị. Các API này
đảm bảo các thành phần trên giao diện người dùng của ứng dụng không bị cắt bớt trên màn hình có bo tròn
các góc. Khung này cung cấp
getPrivacyIndicatorBounds()
API này trả về hình chữ nhật có viền bao quanh của mọi micrô và máy ảnh nhìn thấy được
chỉ báo.
Khi được triển khai trong ứng dụng, các API này không ảnh hưởng đến thiết bị có màn hình không cong.
Để triển khai tính năng này, hãy lấy thông tin RoundedCorner
bằng cách sử dụng
WindowInsets.getRoundedCorner(int position)
so với các giới hạn của
. Nếu ứng dụng không chiếm toàn bộ màn hình, API sẽ áp dụng
góc tròn bằng cách đặt điểm giữa của góc tròn lên cửa sổ
các giới hạn của ứng dụng.
Đoạn mã sau đây cho thấy cách một ứng dụng có thể tránh việc giao diện người dùng bị cắt bớt
đặt lề của chế độ xem dựa trên thông tin từ RoundedCorner
. Trong phần này
đó là góc bo tròn ở trên cùng bên phải.
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);
Hãy cẩn thận khi cắt đoạn
Nếu giao diện người dùng lấp đầy toàn bộ màn hình, thì các góc tròn có thể gây ra vấn đề với nội dung đoạn video. Ví dụ: hình 2 cho thấy một biểu tượng ở góc màn hình với bố cục được vẽ phía sau thanh hệ thống:
Bạn có thể tránh điều này bằng cách kiểm tra các góc tròn và áp dụng khoảng đệm để giữ cho nội dung của ứng dụng ngoài các góc của thiết bị, như được thể hiện trong ví dụ:
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); } }
Bố cục này xác định xem giao diện người dùng có mở rộng sang khu vực các góc bo tròn hay không và thêm khoảng đệm khi có. Hình 3 có mục "Show layout Layout" (Hiển thị các giới hạn bố cục) nhà phát triển bật để hiển thị khoảng đệm đang được áp dụng rõ ràng hơn:
Để xác định điều này, bố cục này tính toán 2 hình chữ nhật: safeArea
là
diện tích trong bán kính của các góc tròn, và layoutBounds
là kích thước
của bố cục trừ đi mọi khoảng đệm. Nếu layoutArea
chứa đầy đủ safeArea
thì
phần tử con của bố cục có thể bị cắt bớt. Trong trường hợp này, khoảng đệm sẽ là
thêm để đảm bảo bố cục vẫn nằm trong safeArea
.
Bằng cách kiểm tra xem layoutBounds
có bao gồm đầy đủ safeArea
hay không, bạn tránh thêm
khoảng đệm khi bố cục không mở rộng đến các cạnh của màn hình. Số 4
hiển thị bố cục khi bố cục không được vẽ phía sau thanh điều hướng. Trong trường hợp này,
bố cục không mở rộng xuống đủ sâu để nằm trong các góc tròn, như
chúng nằm vừa trong vùng
bị thanh điều hướng chiếm chỗ. Không cần khoảng đệm.