ইনসেট: গোলাকার কোণগুলি প্রয়োগ করুন

Android 12 (API লেভেল 31) থেকে শুরু করে, আপনি ডিভাইস স্ক্রিনের গোলাকার কোণগুলির জন্য ব্যাসার্ধ এবং কেন্দ্র বিন্দু পেতে RoundedCorner এবং WindowInsets.getRoundedCorner(int position) ব্যবহার করতে পারেন। এই APIগুলি আপনার অ্যাপের UI উপাদানগুলিকে বৃত্তাকার কোণ সহ স্ক্রিনে কাটা থেকে রাখে৷ ফ্রেমওয়ার্ক getPrivacyIndicatorBounds() API প্রদান করে, যা যেকোনো দৃশ্যমান মাইক্রোফোন এবং ক্যামেরা সূচকের আবদ্ধ আয়তক্ষেত্র প্রদান করে।

আপনার অ্যাপ্লিকেশানে প্রয়োগ করা হলে, এই APIগুলি অ-গোলাকার স্ক্রীন সহ ডিভাইসগুলিতে কোনও প্রভাব ফেলে না।

ব্যাসার্ধ এবং একটি কেন্দ্র বিন্দু সহ একটি বৃত্তাকার কোণ দেখানো চিত্র
চিত্র 1. রেডিআই এবং একটি কেন্দ্র বিন্দু সহ গোলাকার কোণগুলি।

এই বৈশিষ্ট্যটি বাস্তবায়ন করতে, অ্যাপ্লিকেশনের সীমার সাথে সম্পর্কিত WindowInsets.getRoundedCorner(int position) ব্যবহার করে RoundedCorner তথ্য পান। যদি অ্যাপটি পুরো স্ক্রিনটি না নেয়, তাহলে এপিআই অ্যাপের উইন্ডো বাউন্ডে গোলাকার কোণার কেন্দ্র বিন্দুকে ভিত্তি করে গোলাকার কোণে প্রয়োগ করে।

নিম্নলিখিত কোড স্নিপেট দেখায় যে কীভাবে একটি অ্যাপ তার UI ছেঁটে যাওয়া এড়াতে পারে RoundedCorner থেকে তথ্যের উপর ভিত্তি করে ভিউয়ের একটি মার্জিন সেট করে। এই ক্ষেত্রে, এটি উপরের-ডান বৃত্তাকার কোণে।

কোটলিন

// 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. একটি আইকন বৃত্তাকার কোণে ক্লিপ করা হচ্ছে।

আপনি বৃত্তাকার কোণগুলি পরীক্ষা করে এবং প্যাডিং প্রয়োগ করে ডিভাইসের কোণ থেকে আপনার অ্যাপের সামগ্রীকে দূরে রাখতে এটি এড়াতে পারেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে:

কোটলিন

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. প্যাডিং সহ একটি আইকন কোণা থেকে দূরে সরানোর জন্য প্রয়োগ করা হয়েছে।

এই নির্ণয় করতে, এই লেআউটটি দুটি আয়তক্ষেত্র গণনা করে: safeArea হল বৃত্তাকার কোণার ব্যাসার্ধের মধ্যে থাকা এলাকা, এবং layoutBounds হল লেআউটের আকার বিয়োগ করে যেকোন প্যাডিং। যদি layoutArea সম্পূর্ণরূপে safeArea ধারণ করে, তাহলে লেআউটের শিশুদের ক্লিপ করা হতে পারে। যদি এটি হয়, তাহলে লেআউটটি safeArea ভিতরে থাকে তা নিশ্চিত করতে প্যাডিং যোগ করা হয়।

layoutBounds সম্পূর্ণরূপে safeArea জুড়ে আছে কিনা তা পরীক্ষা করে, আপনি প্যাডিং যোগ করা এড়িয়ে যান যখন লেআউটটি প্রদর্শনের প্রান্তে প্রসারিত হয় না। চিত্র 4 লেআউটটি দেখায় যখন এটি নেভিগেশন বারের পিছনে আঁকা হয় না। এই ক্ষেত্রে, লেআউটটি বৃত্তাকার কোণগুলির মধ্যে থাকার জন্য যথেষ্ট নিচে প্রসারিত হয় না, কারণ তারা নেভিগেশন বার দ্বারা দখলকৃত এলাকার মধ্যে ফিট করে। কোন প্যাডিং প্রয়োজন হয় না.

একটি লেআউট যা সিস্টেম এবং নেভিগেশন বারের পিছনে আঁকা হয় না।
চিত্র 4. একটি বিন্যাস যা সিস্টেম এবং নেভিগেশন বারের পিছনে আঁকা হয় না।