ส่วนที่ประกอบ: ใช้มุมโค้งมน

ตั้งแต่ Android 12 (API ระดับ 31) เป็นต้นไป คุณสามารถใช้ RoundedCorner และ WindowInsets.getRoundedCorner(int position)เพื่อรับ รัศมีและจุดศูนย์กลางสำหรับมุมโค้งมนของหน้าจออุปกรณ์ API เหล่านี้ ป้องกันไม่ให้องค์ประกอบ UI ของแอปถูกตัดบนหน้าจอที่มีความโค้งมน มุม เฟรมเวิร์กนี้จะนำเสนอ getPrivacyIndicatorBounds() API ซึ่งแสดงสี่เหลี่ยมผืนผ้าที่มีกรอบของ ไมโครโฟนและกล้องใดก็ตามที่มองเห็นได้ สัญญาณบอกสถานะ

เมื่อใช้งานในแอป API เหล่านี้จะไม่มีผลกับอุปกรณ์ที่มี หน้าจอไม่กลมมน

วันที่ รูปภาพแสดงมุมโค้งมนที่มีรัศมีและจุดศูนย์กลาง
รูปที่ 1 มุมโค้งมนที่มีรัศมีและศูนย์กลาง คะแนน

หากต้องการใช้ฟีเจอร์นี้ โปรดรับข้อมูล RoundedCorner โดยใช้ WindowInsets.getRoundedCorner(int position) เมื่อเทียบกับขอบเขตของ แอปพลิเคชัน หากแอปไม่ครอบคลุมทั้งหน้าจอ API จะใช้ มุมโค้งมนโดยใช้จุดศูนย์กลางของมุมโค้งมนบนหน้าต่าง ขอบเขตของแอป

ข้อมูลโค้ดต่อไปนี้แสดงให้เห็นวิธีหลีกเลี่ยงไม่ให้แอปถูกตัด UI ตั้งค่าขอบของมุมมองโดยอิงตามข้อมูลจาก RoundedCorner ด้วยวิธีนี้ ซึ่งจะเป็นมุมโค้งมนด้านขวาบน

KotlinJava
// 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
// 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 แสดงไอคอนที่มุมของหน้าจอพร้อมกับ เลย์เอาต์ที่วาดอยู่หลังแถบระบบ

วันที่ ไอคอนถูกตัดทอนด้วยมุมโค้งมน
รูปที่ 2 ไอคอนที่มีการตัดทอน มุมต่างๆ

คุณหลีกเลี่ยงปัญหานี้ได้โดยตรวจหามุมโค้งมนและใช้ระยะห่างจากขอบเพื่อคงไว้ เนื้อหาของแอปออกจากมุมของอุปกรณ์ดังที่แสดงในภาพต่อไปนี้ ตัวอย่าง:

KotlinJava
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)
}
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 มี "แสดงขอบเขตของเลย์เอาต์" นักพัฒนาแอป เปิดใช้ตัวเลือกเพื่อแสดงระยะห่างจากขอบอย่างชัดเจนมากขึ้น

วันที่ ไอคอนที่มีการใช้ระยะห่างจากขอบเพื่อดึงอุปกรณ์ออกจากมุม
รูปที่ 3 ไอคอนที่มีการใช้ระยะห่างจากขอบเพื่อย้ายรายการออกจากตัว จากมุม

ในการคำนวณ เลย์เอาต์นี้จะคำนวณสี่เหลี่ยมผืนผ้า 2 รูป: safeArea เท่ากับ พื้นที่ภายในรัศมีของมุมกลม และ layoutBounds คือขนาด ของเลย์เอาต์ลบด้วยระยะห่างจากขอบด้วย หาก layoutArea มี safeArea ครบถ้วนแล้ว องค์ประกอบย่อยของเลย์เอาต์อาจถูกตัดออก ในกรณีนี้ Padding จะ เพิ่มเพื่อให้มั่นใจว่าเลย์เอาต์จะยังอยู่ใน safeArea

การตรวจสอบว่า layoutBounds ครอบคลุม safeArea อย่างสมบูรณ์หรือไม่ จะเป็นการหลีกเลี่ยงการเพิ่ม ระยะห่างจากขอบเมื่อเลย์เอาต์ไม่ขยายไปถึงขอบจอแสดงผล รูปที่ 4 แสดงเลย์เอาต์เมื่อไม่ได้วาดไว้หลังแถบนำทาง ในกรณีนี้ เลย์เอาต์ไม่ขยายออกไกลพอที่จะอยู่ในมุมโค้งมน เช่น ให้พอดีกับพื้นที่ที่แถบนำทาง ไม่ต้องใช้ระยะห่างจากขอบ

วันที่ เลย์เอาต์ที่ไม่ได้วาดไว้ด้านหลังระบบและแถบนำทาง
รูปที่ 4 เลย์เอาต์ที่ไม่ได้วาดไว้ด้านหลังระบบ และแถบนำทาง