WebView में विंडो इंसर्ट को समझना

WebView, कॉन्टेंट के अलाइनमेंट को मैनेज करने के लिए दो व्यूपोर्ट का इस्तेमाल करता है: लेआउट व्यूपोर्ट (पेज का साइज़) और विज़ुअल व्यूपोर्ट (पेज का वह हिस्सा जो उपयोगकर्ता को दिखता है). आम तौर पर, लेआउट व्यूपोर्ट स्टैटिक होता है. वहीं, विज़ुअल व्यूपोर्ट में डाइनैमिक तरीके से बदलाव होता है. जैसे, ज़ूम करने, स्क्रोल करने या सिस्टम यूज़र इंटरफ़ेस (यूआई) एलिमेंट (सॉफ़्टवेयर कीबोर्ड) दिखने पर.

सुविधा की ज़रूरी शर्तें

विंडो इनसेट के लिए WebView का सपोर्ट समय के साथ बेहतर हुआ है. इससे वेब कॉन्टेंट के व्यवहार को, Android के नेटिव ऐप्लिकेशन की उम्मीदों के मुताबिक बनाया जा सकता है:

Milestone जोड़ी गई सुविधा दायरा
M136 सीएसएस सेफ़-एरिया-इनसेट के ज़रिए, displayCutout() और systemBars() के लिए सहायता. सिर्फ़ फ़ुलस्क्रीन वेबव्यू.
M139 विज़ुअल व्यूपोर्ट का साइज़ बदलने की सुविधा के ज़रिए, ime() (इनपुट के तरीके का एडिटर, जो कीबोर्ड होता है) के लिए सहायता. सभी वेबव्यू.
M144 displayCutout() और systemBars() के लिए सहायता. सभी वेबव्यू (चाहे वे फ़ुलस्क्रीन में हों या नहीं).

ज़्यादा जानकारी के लिए, WindowInsetsCompat देखें.

मुख्य मकैनिक्स

WebView, इनसेट को मैनेज करने के लिए दो मुख्य तरीकों का इस्तेमाल करता है:

  • सेफ़ एरिया (displayCutout, systemBars): WebView, सीएसएस सेफ़-एरिया-इनसेट-* वैरिएबल के ज़रिए, इन डाइमेंशन को वेब कॉन्टेंट पर फ़ॉरवर्ड करता है. इससे डेवलपर, अपने इंटरैक्टिव एलिमेंट (जैसे, नेविगेशन बार) को नॉच या स्टेटस बार से छिपने से बचा सकते हैं.

  • इनपुट के तरीके के एडिटर (IME) का इस्तेमाल करके, विज़ुअल व्यूपोर्ट का साइज़ बदलना: M139 से, इनपुट के तरीके का एडिटर (IME) सीधे तौर पर विज़ुअल व्यूपोर्ट का साइज़ बदलता है. साइज़ बदलने का यह तरीका, WebView-विंडो के इंटरसेक्शन पर भी आधारित होता है. उदाहरण के लिए, Android के मल्टीटास्किंग मोड में, अगर किसी वेबव्यू का निचला हिस्सा, विंडो के निचले हिस्से से 200dp नीचे तक फैला है, तो विज़ुअल व्यूपोर्ट, वेबव्यू के साइज़ से 200dp छोटा होगा. विज़ुअल व्यूपोर्ट का साइज़ बदलने की यह सुविधा (IME और WebView-विंडो के इंटरसेक्शन, दोनों के लिए) सिर्फ़ वेबव्यू के निचले हिस्से पर लागू होती है. इस तरीके से, बाईं, दाईं या सबसे ऊपर मौजूद ओवरलैप के लिए, साइज़ बदलने की सुविधा का इस्तेमाल नहीं किया जा सकता. इसका मतलब है कि इन किनारों पर दिखने वाले डॉक्ड कीबोर्ड, विज़ुअल व्यूपोर्ट का साइज़ बदलने की सुविधा को ट्रिगर नहीं करते.

पहले, विज़ुअल व्यूपोर्ट फ़िक्स रहता था. इस वजह से, अक्सर इनपुट फ़ील्ड कीबोर्ड के पीछे छिप जाते थे. व्यूपोर्ट का साइज़ बदलने से, पेज का दिखने वाला हिस्सा डिफ़ॉल्ट रूप से स्क्रोल किया जा सकता है. इससे उपयोगकर्ता, छिपे हुए कॉन्टेंट को ऐक्सेस कर पाते हैं.

बाउंड और ओवरलैप लॉजिक

WebView को, नॉन-ज़ीरो इनसेट वैल्यू सिर्फ़ तब मिलनी चाहिए, जब सिस्टम यूज़र इंटरफ़ेस (यूआई) एलिमेंट (बार, डिसप्ले कटआउट या कीबोर्ड) सीधे तौर पर 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
}

Java

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() JavaScript फ़ंक्शन या फ़ोकस-साफ़ करने वाले व्यवहार ट्रिगर न हों.

इनसेट हैंडलिंग लागू करना

ज़्यादातर ऐप्लिकेशन के लिए, WebView की डिफ़ॉल्ट सेटिंग अपने-आप काम करती हैं. हालांकि, अगर आपका ऐप्लिकेशन, कस्टम लेआउट का इस्तेमाल करता है (उदाहरण के लिए, अगर आपने स्टेटस बार या कीबोर्ड के लिए अपनी पैडिंग जोड़ी है), तो वेब कॉन्टेंट और नेटिव यूज़र इंटरफ़ेस (यूआई) के एक साथ काम करने के तरीके को बेहतर बनाने के लिए, यहां दिए गए तरीकों का इस्तेमाल किया जा सकता है. अगर आपका नेटिव यूज़र इंटरफ़ेस (यूआई), पैडिंग को किसी कंटेनर पर लागू करता है, जो WindowInsets पर आधारित है, तो डबल-पैडिंग से बचने के लिए, इन इनसेट को WebView तक पहुंचने से पहले सही तरीके से मैनेज करना होगा.

डबल-पैडिंग एक ऐसी स्थिति है जिसमें नेटिव लेआउट और वेब कॉन्टेंट, एक ही इनसेट डाइमेंशन लागू करते हैं. इससे, ज़रूरत से ज़्यादा स्पेसिंग हो जाती है. उदाहरण के लिए, मान लें कि किसी फ़ोन में 40 पिक्सल का स्टेटस बार है. नेटिव व्यू और WebView, दोनों को 40 पिक्सल का इनसेट दिखता है. दोनों में 40 पिक्सल की पैडिंग जुड़ जाती है. इससे उपयोगकर्ता को सबसे ऊपर 80 पिक्सल का गैप दिखता है.

ज़ीरोइंग तरीका

डबल-पैडिंग से बचने के लिए, यह पक्का करना होगा कि नेटिव व्यू, पैडिंग के लिए इनसेट डाइमेंशन का इस्तेमाल करने के बाद, उस डाइमेंशन को रीसेट करके ज़ीरो कर दिया जाए. इसके लिए, WebView को व्यू हाइरार्की में बदलाव किया गया ऑब्जेक्ट पास करने से पहले, नए WindowInsets ऑब्जेक्ट पर Insets.NONE का इस्तेमाल करें.

पैरंट व्यू पर पैडिंग लागू करते समय, आम तौर पर ज़ीरोइंग तरीके का इस्तेमाल करना चाहिए. इसके लिए, WindowInsetsCompat.CONSUMED के बजाय Insets.NONE सेट करें. कुछ स्थितियों में, WindowInsetsCompat.CONSUMED को वापस पाने से काम चल सकता है. हालांकि, अगर आपके ऐप्लिकेशन का हैंडलर, इनसेट में बदलाव करता है या अपनी पैडिंग जोड़ता है, तो इससे समस्याएं आ सकती हैं. ज़ीरोइंग तरीके में ये समस्याएं नहीं आती हैं.

इनसेट को ज़ीरो करके, घोस्ट पैडिंग से बचें

अगर ऐप्लिकेशन ने पहले, बिना इस्तेमाल किए गए इनसेट पास किए हैं और अब इनसेट का इस्तेमाल किया जाता है या इनसेट में बदलाव होता है (जैसे, कीबोर्ड छिप जाता है), तो इनसेट का इस्तेमाल करने से, WebView को ज़रूरी अपडेट की सूचना नहीं मिल पाती. इससे, WebView, पिछली स्थिति से घोस्ट पैडिंग को बरकरार रख सकता है. उदाहरण के लिए, कीबोर्ड छिप जाने के बाद भी, कीबोर्ड पैडिंग को बरकरार रखना.

यहां दिए गए उदाहरण में, ऐप्लिकेशन और WebView के बीच काम न करने की समस्या दिखाई गई है:

  1. शुरुआती स्थिति: ऐप्लिकेशन, शुरुआत में WebView को बिना इस्तेमाल किए गए इनसेट पास करता है. उदाहरण के लिए, displayCutout() या systemBars(). WebView, अंदरूनी तौर पर वेब कॉन्टेंट पर पैडिंग लागू करता है.
  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()
}

Java

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 को अपने शुरुआती विज़ुअल व्यूपोर्ट के साइज़ को बरकरार रखना पड़ता है.