# VirtualListView

> उच्च-प्रदर्शन वाली वर्चुअलाइज़्ड सूची जो घटकों को रीसायकल करती है, RecyclerView की शैली में।

Source: https://crossfly.dev/hi/docs/ui/widgets/virtuallistview — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# VirtualListView

`VirtualListView` एक **वर्चुअलाइज़्ड** सूची है: यह केवल स्क्रीन पर दिखाई देने वाले आइटम बनाती और रेंडर करती है और viewport से बाहर जाने वाले घटकों को **रीसायकल** करती है, Android के `RecyclerView` की शैली में। यह कुछ दर्जन जीवित घटकों के साथ हज़ारों (या लाखों) आइटम का समर्थन करती है, मेमोरी उपयोग को स्थिर रखते हुए।

डेटा स्रोत `DataAdapter` इंटरफ़ेस है (`getCount` / `createView` / `bindView`)। प्रत्येक आइटम की ऊँचाई **निश्चित** होती है (`Relative itemHeight`)। इसमें "और लोड करें" कॉलबैक (`onLoadMore`) और लोडिंग स्पिनर शामिल हैं।

> 🔴 · **पैकेज:** `dev.crossfly.ui` · **विस्तार करता है:** `Component` · **स्टाइल:** `VirtualListViewStyle` (`getSkin().virtualListView()` के माध्यम से)

## कब उपयोग करें
- बड़ी सूचियाँ (सैकड़ों हज़ार पंक्तियाँ) जहाँ प्रति आइटम एक घटक बनाना अव्यवहारिक है।
- वृद्धिशील लोडिंग के साथ अनंत स्क्रॉलिंग (`onLoadMore`)।
- कैश के साथ रिमोट पेजिनेशन → [`PagedDataAdapter`](/docs/ui/widgets/paged-data-adapter) के साथ जोड़ें।

## कब उपयोग न करें
- टेक्स्ट की छोटी सूचियाँ → [`ListView`](/docs/ui/widgets/listview) अधिक सरल है और सटीक ऊँचाई मापता है।
- वास्तव में परिवर्तनशील आइटम ऊँचाइयाँ — यहाँ ऊँचाई निश्चित है (एक ऐसा स्लॉट चुनें जो सबसे बड़े आइटम को समा सके)।

## दिखावट
समान ऊँचाई वाले आइटम का ऊर्ध्वाधर स्क्रॉल करने योग्य कॉलम। स्क्रॉल के दौरान, स्क्रीन से बाहर के आइटम एक पूल में एकत्र किए जाते हैं और पुनः उपयोग किए जाते हैं। एक वैकल्पिक स्पिनर फ़ुटर में (load-more) या केंद्र में (पेज लोडिंग) दिखाई देता है।

## पूर्ण उदाहरण (कॉपी करें, पेस्ट करें और चलाएँ)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.graphics.*;   // Relative
import java.util.*;                    // List, ArrayList

/** चलाने योग्य मुख्य स्क्रीन। launcher (classname) की ओर संकेत करें और चलाएँ: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      final List<String> items = new ArrayList<>();
      for (int i = 0; i < 10_000; i++)
         items.add("Item " + i);

      VirtualListView list = new VirtualListView(Relative.ofFont(2f),
         new VirtualListView.DataAdapter() {
            public int getCount() { return items.size(); }
            public Component createView() { return new Label(""); }
            public void bindView(Component v, int index) {
               ((Label) v).setText(items.get(index));
            }
         });

      add(LAY_FILL, list);   // सूचियां LAY_FILL उपयोग करती हैं (ऊंचाई अवशोषित करती हैं)
   }
}
```

> नीचे दिए गए अंश **केवल मुख्य भाग** दिखाते हैं (जो `onShow()` के अंदर जाता है)। चलाने के लिए, उन्हें ऊपर दिए गए `MainWindow` ढाँचे में रखें।

## बुनियादी उपयोग
```java
VirtualListView list = new VirtualListView(Relative.ofFont(2f),
   new VirtualListView.DataAdapter() {
      public int getCount() { return items.size; }
      public Component createView() { return new Label(""); }
      public void bindView(Component v, int index) {
         ((Label) v).setText("Item " + index);
      }
   });
layout.add(LAY_FILL, list);
```

## कंस्ट्रक्टर
| कंस्ट्रक्टर | विवरण |
|---|---|
| `VirtualListView(Relative itemHeight, DataAdapter adapter)` | **एकमात्र** कंस्ट्रक्टर। `itemHeight` प्रत्येक आइटम की निश्चित ऊँचाई है; `adapter` डेटा स्रोत है |

> **कोई तर्क-रहित कंस्ट्रक्टर नहीं है।** यदि `adapter` `null` है, तो कंस्ट्रक्टर `createAdapter()` कॉल करता है (जिसे आप एक सबक्लास में ओवरराइड करते हैं)। यदि adapter एक [`PagedDataAdapter`](/docs/ui/widgets/paged-data-adapter) है, तो कंस्ट्रक्टर स्वचालित रूप से उसमें `this` इंजेक्ट करता है।

### DataAdapter
```java
public interface DataAdapter {
   int getCount();                                   // आइटम की कुल संख्या
   Component createView() throws Exception;          // खाली व्यू बनाता है (सिर्फ़ तभी जब pool खाली हो)
   void bindView(Component view, int index) throws Exception; // इंडेक्स का डेटा व्यू से जोड़ता है
}
```
- `getCount()` कुल की जानकारी देता है — सूची सभी आइटम को मटेरियलाइज़ किए बिना `count * itemHeight` की ऊँचाई का **दिखावा** करती है।
- `createView()` केवल तब कॉल होता है जब रीसाइक्लिंग पूल खाली होता है।
- `bindView()` स्क्रॉल के दौरान कई बार चलता है: इसे **आइडेम्पोटेंट और सस्ता** रखें।

## गुण और अनुकूलन
| गुण / मेथड | प्रकार | डिफ़ॉल्ट | क्या करता है |
|---|---|---|---|
| `onLoadMore(Runnable)` | `VirtualListView` | `null` | अंत के पास स्क्रॉल करने पर सक्रिय होने वाला कॉलबैक |
| `loadMoreThreshold(int)` | `VirtualListView` | `5` | अंत से कितने आइटम पहले load-more सक्रिय करते हैं (`vlvStyle()` में लिखता है) |
| `notifyDataChanged()` | `void` | — | डेटा **जोड़ने** के बाद कॉल करें (नया पेज); स्क्रॉल को री-क्लैंप करता है और सीमा को री-बाइंड करता है |
| `reloadData()` | `void` | — | समान इंडेक्स के तहत सामग्री **बदलने** के बाद कॉल करें (खोज/फ़िल्टर/सॉर्ट); कैश साफ़ करता है, शीर्ष पर स्क्रॉल करता है, सब कुछ री-बाइंड करता है |
| `scrollToIndex(int)` | `void` | — | आइटम को viewport में केंद्रित करता है |
| `scrollToY(float)` | `void` | — | पिक्सेल में एक ऑफ़सेट तक स्क्रॉल करता है (सब-पिक्सेल/एनिमेट करने योग्य) |
| `getScrollY()` | `float` | — | वर्तमान ऊर्ध्वाधर ऑफ़सेट (कोलैप्स करने योग्य हेडर के लिए उपयोगी) |
| `setItemHeight(Relative)` | `void` | — | आइटम की ऊँचाई बदलता है और `notifyDataChanged()` कॉल करता है |
| `getItemHeight()` | `int` | — | px में आइटम की ऊँचाई |
| `replaceView(int, Component)` | `void` | — | किसी इंडेक्स की सक्रिय view को पूरी तरह से नए घटक से बदलता है |

## कॉलबैक और इवेंट
| कॉलबैक | कब सक्रिय होता है |
|---|---|
| `onLoadMore(Runnable)` | जब `lastVisible >= count - threshold` और कोई लोडिंग लंबित नहीं है |

स्क्रॉल इवेंट (`onPan`/`onFling`/`onMouseWheel`) **तब तक अवरुद्ध रहते हैं जब तक पेजिनेटेड adapter डेटा की प्रतीक्षा करता है** (`isWaitingForData()`), जिससे खालीपन में स्क्रॉल करने से बचा जाता है।

## स्थितियाँ
- **रीसाइक्लिंग** — viewport से बाहर के आइटम पूल में वापस जाते हैं; पुनः प्रवेश करने पर, वे `bindView` के माध्यम से री-बाइंड होते हैं।
- **लोडिंग (load-more)** — फ़ुटर में स्पिनर, `notifyDataChanged()` तक `loadMorePending = true`।
- **पेज की प्रतीक्षा** (`PagedDataAdapter` के साथ) — पेज आने तक स्क्रॉल लॉक रहता है।

### load-more के साथ 10,000 आइटम की सूची
```java
VirtualListView list = new VirtualListView(Relative.ofFont(2f),
   new VirtualListView.DataAdapter() {
      public int getCount() { return items.size; }
      public Component createView() { return new Label(""); }
      public void bindView(Component v, int index) {
         ((Label) v).setText("Item " + index);
      }
   });
list.onLoadMore(() -> {
   carregarProximaPagina();   // स्रोत में आइटम जोड़ता है
   list.notifyDataChanged();  // सूची को सूचित करता है
});
layout.add(LAY_FILL, list);
```

### सबक्लास के माध्यम से adapter का गतिशील परिवर्तन
बेस क्लास में कोई सार्वजनिक `setAdapter` नहीं है (`adapter` फ़ील्ड `protected` है)। रनटाइम पर adapter बदलने के लिए, एक सबक्लास बनाएँ:
```java
class RowList extends VirtualListView {
   RowList() { super(Relative.ofFont(2f), null); }
   void setAdapter(DataAdapter a, Relative h) { adapter = a; setItemHeight(h); }
}

RowList vsc = new RowList();
final int count = 2000;
vsc.setAdapter(new VirtualListView.DataAdapter() {
   public int getCount() { return count; }
   public Component createView() { return new Label(""); }
   public void bindView(Component v, int index) {
      ((Label) v).setText("Row id: " + index);
   }
}, Relative.ofPixels(100));
```

## अच्छी प्रथाएँ
- **आइटम की ऊँचाई निश्चित है** — एक ऐसा स्लॉट चुनें जो सबसे बड़े आइटम को समा सके।
- डेटा **जोड़ने** के बाद, `notifyDataChanged()` कॉल करें; समान इंडेक्स के तहत **सामग्री बदलने** के बाद, `reloadData()` कॉल करें (अन्यथा पहले से दिखाई देने वाली पंक्तियाँ पुराने डेटा के साथ रह जाती हैं)।
- यदि `bindView` रीसायकल की गई view को म्यूटेट करने के बजाय प्रति इंडेक्स एक **नया** घटक बनाता है, तो `replaceView(...)` का उपयोग करें — या स्वीकार करें कि वे views पूल में वापस नहीं जातीं।

## आम गलतियाँ
- **`bindView` में भारी संसाधन आवंटित करना** — यह प्रत्येक स्क्रॉल इवेंट पर चलता है। बाइंडिंग को सस्ता रखें।
- **पेज जोड़ने के बाद `notifyDataChanged()` भूल जाना** — नई सामग्री ऊँचाई को पहचाना नहीं जाता और load-more रीसेट नहीं होता।
- **केवल जोड़ा तो `reloadData()` का उपयोग करना** — यह शीर्ष पर स्क्रॉल करता है; append के लिए `notifyDataChanged()` का उपयोग करें।

## यह भी देखें
- क्षेत्र गाइड: [`ui/`](/docs/ui) · रेसिपी: [`cookbook/`](/docs/cookbook)
- संबंधित घटक: [`PagedDataAdapter`](/docs/ui/widgets/paged-data-adapter) · [`ListView`](/docs/ui/widgets/listview)
