WebView-তে উইন্ডো ইনসেট বোঝা

WebView দুটি ভিউপোর্ট ব্যবহার করে কন্টেন্ট অ্যালাইনমেন্ট ম্যানেজ করে: লেআউট ভিউপোর্ট (পৃষ্ঠার সাইজ) এবং ভিজ্যুয়াল ভিউপোর্ট (পৃষ্ঠার সেই অংশ যা ব্যবহারকারী প্রকৃতপক্ষে দেখেন)। লেআউট ভিউপোর্ট সাধারণত স্ট্যাটিক হলেও, ব্যবহারকারী জুম, স্ক্রল করলে বা সিস্টেম UI এলিমেন্ট (যেমন, সফ্টওয়্যার কীবোর্ড) দেখা গেলে, ভিজ্যুয়াল ভিউপোর্ট ডায়নামিক পদ্ধতিতে পরিবর্তিত হয়।

ফিচারের কম্প্যাটিবিলিটি

নেটিভ Android অ্যাপের প্রত্যাশা অনুযায়ী ওয়েব কন্টেন্টের ব্যবহারকে সাজিয়ে নিতে, উইন্ডো ইনসেটের জন্য WebView সহায়তা সময়ের সাথে সাথে উন্নত হয়েছে:

মাইলস্টোন ফিচার যোগ করা হয়েছে সুযোগ
M136 CSS safe-area-insets-এর মাধ্যমে displayCutout() ও systemBars() কাজ করে। শুধুমাত্র ফুল-স্ক্রিন ওয়েবভিউ।
M139 ime() (ইনপুট মেথড এডিটর, যা হল কীবোর্ড) ভিজ্যুয়াল ভিউপোর্ট রিসাইজিংয়ের মাধ্যমে কাজ করে। সব WebViews.
M144 displayCutout() ও systemBars() সহায়তা। সব ওয়েবভিউ (ফুল-স্ক্রিন স্টেটাস নির্বিশেষে)।

আরও তথ্যের জন্য, WindowInsetsCompat দেখুন।

কোর মেকানিক্স

WebView দুটি প্রধান মেকানিজমের মাধ্যমে ইনসেট ম্যানেজ করে:

  • নিরাপদ জায়গা (displayCutout, systemBars): WebView, CSS safe-area-inset-* ভেরিয়েবলের মাধ্যমে এই ডাইমেনশনগুলি ওয়েব কন্টেন্টে ফরওয়ার্ড করে। এর ফলে নচ বা স্ট্যাটাস বার ডেভেলপারদের নিজস্ব ইন্ট্যার‍্যাক্টিভ এলিমেন্ট (যেমন, নেভিগেশন বার) আড়াল করতে পারে না।

  • ইনপুট মেথড এডিটর (IME) ব্যবহার করে ভিজ্যুয়াল ভিউপোর্ট রিসাইজ করা: M139 থেকে শুরু করে, ইনপুট মেথড এডিটর (IME) সরাসরি ভিজ্যুয়াল ভিউপোর্ট রিসাইজ করে। এই রি-সাইজিং মেকানিজম WebView-Window ইন্টারসেকশনের উপরও ভিত্তি করে তৈরি। যেমন, Android মাল্টিটাস্কিং মোডে, কোনও WebView-এর নিচের অংশ যদি উইন্ডোর নিচের অংশ থেকে 200dp নিচে পর্যন্ত প্রসারিত হয়, তাহলে ভিজ্যুয়াল ভিউপোর্টটি WebView-এর সাইজের থেকে 200dp ছোট হবে। এই ভিজ্যুয়াল ভিউপোর্ট রিসাইজিং (IME এবং WebView-Window ইন্টারসেকশন, দু'টির জন্যই) শুধুমাত্র WebView-এর নিচের দিকে প্রয়োগ করা হয়। এই মেকানিজম বাঁদিক, ডানদিক বা উপরের ওভারল্যাপের জন্য সাইজ পরিবর্তন করার সুবিধা দেয় না। এর অর্থ হল, সেইসব এজে ডক করা কীবোর্ড দেখা গেলে, তা ভিউপোর্ট দৃশ্যমানভাবে রিসাইজ করার ট্রিগার করে না।

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

বাউন্ড ও ওভারল্যাপ লজিক

সিস্টেম UI এলিমেন্ট (বার, ডিসপ্লে কাটআউট বা কীবোর্ড) সরাসরি WebView-এর স্ক্রিন বাউন্ডের সাথে ওভারল্যাপ করলে তবেই WebView-এর নন-জিরো ইনসেট ভ্যালু পাওয়া উচিত। কোনও WebView এইসব UI এলিমেন্টের সাথে ওভারল্যাপ না করলে (যেমন, কোনও WebView স্ক্রিনের কেন্দ্রে থাকলে এবং সিস্টেম বার স্পর্শ না করলে), এটি ইনসেট হিসেবে শূন্য মান পাবে।

এই ডিফল্ট লজিক ওভাররাইড করতে এবং ওভারল্যাপ নির্বিশেষে সম্পূর্ণ সিস্টেমের ডাইমেনশন সহ ওয়েব কন্টেন্ট প্রদান করতে, লিসনার থেকে setOnApplyWindowInsetsListener পদ্ধতি ব্যবহার করুন এবং আসল, অপরিবর্তিত windowInsets অবজেক্ট রিটার্ন করুন। সম্পূর্ণ সিস্টেমের ডাইমেনশন প্রদান করলে ওয়েব কন্টেন্টকে ডিভাইস হার্ডওয়্যারের সাথে অ্যালাইন করার মাধ্যমে ডিজাইন কনসিস্টেন্সি নিশ্চিত করতে সাহায্য করতে পারে, WebView-এর বর্তমান পজিশন যাই হোক না কেন। এর ফলে WebView সরানোর বা স্ক্রিনের প্রান্ত স্পর্শ করার জন্য বড় করার সময় স্মুথ ট্রানজিশন নিশ্চিত করা যায়।

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(myWebView) { _, windowInsets ->
    // By returning the original windowInsets object, we override the default
    // behavior that zeroes out system insets (like system bars or display
    // cutouts) when they don't directly overlap the WebView's screen bounds.
    windowInsets
}

জাভা

ViewCompat.setOnApplyWindowInsetsListener(myWebView, (v, windowInsets) -> {
    // By returning the original windowInsets object, we override the default
    // behavior that zeroes out system insets (like system bars or display
    // cutouts) when they don't directly overlap the WebView's screen bounds.
    return windowInsets;
});

রিসাইজ ইভেন্ট ম্যানেজ করা

কারণ কীবোর্ড দৃশ্যমান হলে এখন ভিজ্যুয়াল ভিউপোর্ট রিসাইজ ট্রিগার হয়, তাই ওয়েব কোড আরও ঘন ঘন রিসাইজ ইভেন্ট দেখতে পেতে পারে। ডেভেলপারদের অবশ্যই নিশ্চিত করতে হবে যে তাদের কোড এলিমেন্ট ফোকাস সরিয়ে দিয়ে এইসব রিসাইজ ইভেন্টে প্রতিক্রিয়া দেখায় না। এটি করলে ফোকাস হারিয়ে যাওয়া এবং কীবোর্ড ডিসমিস করার একটি লুপ তৈরি হয় যা ব্যবহারকারীর ইনপুটকে বাধা দেয়:

  1. ব্যবহারকারী একটি ইনপুট এলিমেন্টে ফোকাস করেন।
  2. কীবোর্ড দেখা যায়, যা রিসাইজ ইভেন্ট ট্রিগার করে।
  3. ওয়েবসাইটের কোড রিসাইজ করার উত্তরে ফোকাস সরিয়ে দেয়।
  4. ফোকাস হারিয়ে যাওয়ার কারণে কীবোর্ড লুকানো থাকে।

এই আচরণ প্রশমিত করতে, ওয়েব-সাইড লিসেনার পর্যালোচনা করে দেখুন যাতে ভিউপোর্ট পরিবর্তনগুলি অনিচ্ছাকৃতভাবে blur() জাভাস্ক্রিপ্ট ফাংশন বা ফোকাস-ক্লিয়ারিং আচরণ ট্রিগার না করে।

ইনসেট হ্যান্ডলিং প্রয়োগ করা

WebView-এর ডিফল্ট সেটিংস বেশিরভাগ অ্যাপের জন্য অটোমেটিক কাজ করে। তবে, আপনার অ্যাপ কাস্টম লেআউট ব্যবহার করলে (যেমন, আপনি যদি স্ট্যাটাস বার বা কীবোর্ডকে অ্যাকাউন্ট করার জন্য নিজের প্যাডিং যোগ করেন), তাহলে ওয়েব কন্টেন্ট এবং নেটিভ UI কীভাবে একসাথে কাজ করে তা উন্নত করতে আপনি নিম্নলিখিত পদ্ধতি ব্যবহার করতে পারেন। আপনার নেটিভ UI যদি WindowInsets-এর উপর ভিত্তি করে কোনও কন্টেনারে প্যাডিং প্রয়োগ করে, তাহলে ডবল প্যাডিং এড়াতে, WebView-তে পৌঁছানোর আগে আপনাকে অবশ্যই এই ইনসেটগুলি সঠিকভাবে ম্যানেজ করতে হবে।

ডবল-প্যাডিং হল এমন একটি পরিস্থিতি যেখানে নেটিভ লেআউট এবং ওয়েব কন্টেন্ট একই ইনসেট ডাইমেনশন প্রয়োগ করে, যার ফলে অপ্রয়োজনীয় স্পেসিং তৈরি হয়। যেমন, ৪০ পিক্সেল স্ট্যাটাস বার সহ একটি ফোনের কথা ভাবুন। নেটিভ ভিউ ও WebView, দু'টিই ৪০ পিক্সেল ইনসেট দেখতে পায়। দুটিই ৪০ পিক্সেল করে প্যাডিং যোগ করে, ফলে ব্যবহারকারী উপরে ৮০ পিক্সেলের গ্যাপ দেখতে পান।

জিরো করা সংক্রান্ত পদ্ধতি

ডবল-প্যাডিং আটকাতে, আপনাকে অবশ্যই নিশ্চিত করতে হবে যে নেটিভ ভিউ প্যাডিংয়ের জন্য ইনসেট ডাইমেনশন ব্যবহার করার পরে, আপনি সেই ডাইমেনশনটি WebView-তে ভিউ হায়ারার্কি ডাউন করার আগে নতুন WindowInsets অবজেক্টে Insets.NONE ব্যবহার করে শূন্যে রিসেট করেছেন।

কোনও পেরেন্ট ভিউতে প্যাডিং প্রয়োগ করার সময়, WindowInsetsCompat.CONSUMED-এর পরিবর্তে Insets.NONE সেট করে আপনাকে সাধারণত শূন্য করার প্রক্রিয়া ব্যবহার করতে হবে। WindowInsetsCompat.CONSUMED ফিরে যাওয়া নির্দিষ্ট কিছু পরিস্থিতিতে কাজ করতে পারে। তবে, আপনার অ্যাপের হ্যান্ডলার ইনসেট পরিবর্তন করলে বা নিজস্ব প্যাডিং যোগ করলে এতে সমস্যা হতে পারে। জিরোইং অ্যাপ্রোচে এই ধরনের সীমাবদ্ধতা নেই।

ইনসেট শূন্য করে দিয়ে ঘোস্ট প্যাডিং এড়ানো

অ্যাপ আগে ব্যবহার না করা ইনসেট পাস করার পরে আপনি ইনসেট ব্যবহার করলে অথবা ইনসেট পরিবর্তন হলে (যেমন, কীবোর্ড লুকানো), সেগুলি ব্যবহার করলে WebView প্রয়োজনীয় আপডেট বিজ্ঞপ্তি পায় না। এর ফলে WebView আগের অবস্থা থেকে (যেমন, কীবোর্ড লুকানোর পরেও কীবোর্ড প্যাডিং রেখে দেওয়া) গোস্ট প্যাডিং রিটেন করতে পারে।

নিচে দেওয়া উদাহরণে অ্যাপ ও WebView-এর মধ্যে ইন্টার‍্যাকশন ভেঙে যাওয়া দেখানো হয়েছে:

  1. প্রাথমিক অবস্থা: অ্যাপটি প্রাথমিকভাবে WebView-তে ব্যবহার না করা ইনসেট (যেমন, displayCutout() বা systemBars()) পাস করে, যা ইন্টার্নালি ওয়েব কন্টেন্টে প্যাডিং প্রয়োগ করে।
  2. স্টেট পরিবর্তন ও সমস্যা: অ্যাপের স্টেট পরিবর্তন হলে (যেমন, কীবোর্ড লুকিয়ে গেলে) এবং অ্যাপ যদি WindowInsetsCompat.CONSUMED রিটার্ন করে ইনসেট ম্যানেজ করার সিদ্ধান্ত নেয়।
  3. বিজ্ঞপ্তি ব্লক করা হয়েছে: ইনসেট ব্যবহার করার ফলে Android সিস্টেম প্রয়োজনীয় আপডেট সংক্রান্ত বিজ্ঞপ্তি ভিউ হায়ারার্কি থেকে WebView-তে পাঠাতে পারে না।
  4. অদৃশ্য প্যাডিং: WebView আপডেট না পাওয়ার কারণে, এটি আগের অবস্থা থেকে প্যাডিং ধরে রাখে, যার ফলে অদৃশ্য প্যাডিং হয় (যেমন, কীবোর্ড লুকানোর পরেও কীবোর্ড প্যাডিং রেখে দেওয়া)।

এর পরিবর্তে, চাইল্ড ভিউতে অবজেক্ট পাস করার আগে, হ্যান্ডেল করা ধরনগুলিকে শূন্যে সেট করতে WindowInsetsCompat.Builder ব্যবহার করুন। এটি WebView-কে জানায় যে, ভিউ হায়ারার্কি চালিয়ে যাওয়ার জন্য বিজ্ঞপ্তি চালু করার সময় সেইসব নির্দিষ্ট ইনসেট আগেই বিবেচনা করা হয়েছে।

Kotlin

ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, windowInsets ->
    // 1. Identify the inset types you want to handle natively
    val types = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()

    // 2. Extract the dimensions and apply them as padding to the native container
    val insets = windowInsets.getInsets(types)
    view.setPadding(insets.left, insets.top, insets.right, insets.bottom)

    // 3. Return a new WindowInsets object with the handled types set to NONE (zeroed).
    // This informs the WebView that these areas are already padded, preventing
    // double-padding while still allowing the WebView to update its internal state.
    WindowInsetsCompat.Builder(windowInsets)
        .setInsets(types, Insets.NONE)
        .build()
}

জাভা

ViewCompat.setOnApplyWindowInsetsListener(rootView, (view, windowInsets) -> {
    // 1. Identify the inset types you want to handle natively
    int types = WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout();

    // 2. Extract the dimensions and apply them as padding to the native container
    Insets insets = windowInsets.getInsets(types);
    rootView.setPadding(insets.left, insets.top, insets.right, insets.bottom);

    // 3. Return a new Insets object with the handled types set to NONE (zeroed).
    // This informs the WebView that these areas are already padded, preventing
    // double-padding while still allowing the WebView to update its internal
    // state.
    return new WindowInsetsCompat.Builder(windowInsets)
            .setInsets(types, Insets.NONE)
            .build();
});

কীভাবে বন্ধ করবেন

এইসব আধুনিক আচরণ বন্ধ করতে এবং পুরনো ভিউপোর্ট হ্যান্ডলিংয়ে ফিরে যেতে, নিম্নলিখিত কাজগুলি করুন:

  1. ইনসেট ইন্টারসেপ্ট করা: WebView সাবক্লাসে setOnApplyWindowInsetsListener ব্যবহার করুন অথবা ওভাররাইড করুন onApplyWindowInsets।

  2. ইনসেট মুছে দেওয়া: শুরু থেকে ইনসেটের (যেমন, WindowInsetsCompat.CONSUMED) ব্যবহার করা সেট ফিরিয়ে দিন। এই অ্যাকশনটি ইনসেট বিজ্ঞপ্তিটিকে WebView-তে সম্পূর্ণভাবে প্রোপাগেট করা থেকে আটকায়, এর ফলে আধুনিক ভিউপোর্ট রিসাইজিং বন্ধ হয়ে যায় এবং WebView-কে তার প্রাথমিক ভিজ্যুয়াল ভিউপোর্ট সাইজ বজায় রাখতে বাধ্য করে।