ตั้งแต่ Android 12 (API ระดับ 31) เป็นต้นไป คุณสามารถใช้
RoundedCorner
และ
WindowInsets.getRoundedCorner(int
position)
เพื่อรับ
รัศมีและจุดศูนย์กลางสำหรับมุมโค้งมนของหน้าจออุปกรณ์ API เหล่านี้
ป้องกันไม่ให้องค์ประกอบ UI ของแอปถูกตัดบนหน้าจอที่มีความโค้งมน
มุม เฟรมเวิร์กนี้จะนำเสนอ
getPrivacyIndicatorBounds()
API ซึ่งแสดงสี่เหลี่ยมผืนผ้าที่มีกรอบของ ไมโครโฟนและกล้องใดก็ตามที่มองเห็นได้
สัญญาณบอกสถานะ
เมื่อใช้งานในแอป API เหล่านี้จะไม่มีผลกับอุปกรณ์ที่มี หน้าจอไม่กลมมน
![รูปภาพแสดงมุมโค้งมนที่มีรัศมีและจุดศูนย์กลาง](https://developer.android.com/static/guide/topics/ui/images/look-and-feel/rounded-corners-radius.png?authuser=0000&hl=th)
หากต้องการใช้ฟีเจอร์นี้ โปรดรับข้อมูล RoundedCorner
โดยใช้
WindowInsets.getRoundedCorner(int position)
เมื่อเทียบกับขอบเขตของ
แอปพลิเคชัน หากแอปไม่ครอบคลุมทั้งหน้าจอ API จะใช้
มุมโค้งมนโดยใช้จุดศูนย์กลางของมุมโค้งมนบนหน้าต่าง
ขอบเขตของแอป
ข้อมูลโค้ดต่อไปนี้แสดงให้เห็นวิธีหลีกเลี่ยงไม่ให้แอปถูกตัด UI
ตั้งค่าขอบของมุมมองโดยอิงตามข้อมูลจาก RoundedCorner
ด้วยวิธีนี้
ซึ่งจะเป็นมุมโค้งมนด้านขวาบน
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);
ระวังการตัดคลิป
หาก UI แสดงเต็มพื้นที่หน้าจอ ขอบมนอาจทำให้เกิดปัญหากับเนื้อหาได้ การตัดคลิป ตัวอย่างเช่น รูปที่ 2 แสดงไอคอนที่มุมของหน้าจอพร้อมกับ เลย์เอาต์ที่วาดอยู่หลังแถบระบบ
![ไอคอนถูกตัดทอนด้วยมุมโค้งมน](https://developer.android.com/static/develop/ui/views/theming/images/rounded-corners-none.png?authuser=0000&hl=th)
คุณหลีกเลี่ยงปัญหานี้ได้โดยตรวจหามุมโค้งมนและใช้ระยะห่างจากขอบเพื่อคงไว้ เนื้อหาของแอปออกจากมุมของอุปกรณ์ดังที่แสดงในภาพต่อไปนี้ ตัวอย่าง:
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); } }
เลย์เอาต์นี้กำหนดว่า UI จะขยายไปถึงพื้นที่ของมุมโค้งมนหรือไม่ และเพิ่มระยะห่างจากขอบในตำแหน่งเดิม รูปที่ 3 มี "แสดงขอบเขตของเลย์เอาต์" นักพัฒนาแอป เปิดใช้ตัวเลือกเพื่อแสดงระยะห่างจากขอบอย่างชัดเจนมากขึ้น
![ไอคอนที่มีการใช้ระยะห่างจากขอบเพื่อดึงอุปกรณ์ออกจากมุม](https://developer.android.com/static/develop/ui/views/theming/images/rounded-corners-both.png?authuser=0000&hl=th)
ในการคำนวณ เลย์เอาต์นี้จะคำนวณสี่เหลี่ยมผืนผ้า 2 รูป: safeArea
เท่ากับ
พื้นที่ภายในรัศมีของมุมกลม และ layoutBounds
คือขนาด
ของเลย์เอาต์ลบด้วยระยะห่างจากขอบด้วย หาก layoutArea
มี safeArea
ครบถ้วนแล้ว
องค์ประกอบย่อยของเลย์เอาต์อาจถูกตัดออก ในกรณีนี้ Padding จะ
เพิ่มเพื่อให้มั่นใจว่าเลย์เอาต์จะยังอยู่ใน safeArea
การตรวจสอบว่า layoutBounds
ครอบคลุม safeArea
อย่างสมบูรณ์หรือไม่ จะเป็นการหลีกเลี่ยงการเพิ่ม
ระยะห่างจากขอบเมื่อเลย์เอาต์ไม่ขยายไปถึงขอบจอแสดงผล รูปที่ 4
แสดงเลย์เอาต์เมื่อไม่ได้วาดไว้หลังแถบนำทาง ในกรณีนี้
เลย์เอาต์ไม่ขยายออกไกลพอที่จะอยู่ในมุมโค้งมน เช่น
ให้พอดีกับพื้นที่ที่แถบนำทาง ไม่ต้องใช้ระยะห่างจากขอบ
![เลย์เอาต์ที่ไม่ได้วาดไว้ด้านหลังระบบและแถบนำทาง](https://developer.android.com/static/develop/ui/views/theming/images/rounded-corners-neither.png?authuser=0000&hl=th)