ListView: co to jest, proste listy w aplikacjach Android

Autor: IT Sectr Opublikowano: 2026-02-23 Czas czytania: 8 min

ListView — podstawowy komponent Android do wyświetlania pionowych list, dostępny od API 1 (Android 1.0). ListView tworzy listę elementów za pomocą ArrayAdapter i obsługuje niestandardowe układy komórek. Dowiedz się, czym jest ListView, jak go skonfigurować i jakie pułapki kryje ten stary, ale wciąż używany komponent. W IT Sectr spotykamy ListView w legacy-projektach i utrzymujemy wsteczną zgodność z Android 4.4. Porównanie z nowoczesną alternatywą znajdziesz w artykule o RecyclerView.

Najważniejsze

  • ListView — wbudowany komponent Android SDK dla pionowych list, dostępny od Android 1.0.
  • ArrayAdapter — standardowy adapter do wiązania tablicy danych z ListView przez toString() lub niestandardowy układ.
  • SimpleAdapter — adapter dla list z danych Map, wygodny dla prostych ekranów bez ViewModel.
  • onItemClickListener — wbudowany obsługiwacz kliknięć, który nie wymaga dodatkowej implementacji w adapterze.
  • convertView — mechanizm ponownego wykorzystania View w ListView, który programista musi zaimplementować ręcznie (odpowiednik ViewHolder).

Czym jest ListView w Androidzie?

ListView — to grupa View z pakietu android.widget, przeznaczona do wyświetlania przewijanej listy elementów. ListView implementuje wzorzec AdapterView: dane są przekazywane przez adapter (Adapter), który konwertuje elementy danych na View wyświetlane na ekranie. ListView istnieje w Android SDK od pierwszej wersji (API 1) i nie wymaga dodatkowych bibliotek.

Architektura ListView jest prostsza niż RecyclerView: LayoutManager jest wbudowany (tylko pionowe przewijanie), animacja zmian nie występuje, separatory ustawiane są przez atrybut XML android:divider. ListView automatycznie dodaje OverScrollView i podświetlanie przy fokusie, co upraszcza prototypowanie. Według danych Statista (2025), około 15% aplikacji Android w top-100 Play Store wciąż zawiera ListView — głównie legacy-projekty z minimalnym wsparciem.

Historia i aktualność

ListView był głównym komponentem list w Androidzie od 2008 do 2014 roku, kiedy na Google I/O 2014 zaprezentowano RecyclerView. Mimo statusu częściowo przestarzałego (soft-deprecated), ListView pozostaje dostępny we wszystkich wersjach Androida i nie jest oznaczony jako @Deprecated w SDK API 35. Jeśli Twój projekt obsługuje Android 4.4 (API 19) i starsze, ListView jest jedynym wbudowanym wyborem, ponieważ RecyclerView wymaga AndroidX i zależności gradle.

Typy adapterów: ArrayAdapter i SimpleAdapter

ArrayAdapter — najpopularniejszy adapter dla ListView. Przyjmuje List<T> i układ dla każdego elementu. Domyślnie ArrayAdapter wywołuje toString() dla każdego obiektu i wyświetla tekst w simple_list_item_1 (jeden TextView). Dla niestandardowego wyświetlania należy nadpisać getView() lub przekazać niestandardowy układ w konstruktorze.

kotlin
// ListView z ArrayAdapter (prosty tekst)
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val countries = listOf(
        "Rosja", "USA", "Niemcy",
        "Francja", "Japonia", "Chiny", "Brazylia"
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val adapter = ArrayAdapter<String>(
            this,
            android.R.layout.simple_list_item_1,
            countries
        )
        binding.listView.adapter = adapter

        // Obsługa kliknięcia
        binding.listView.onItemClickListener =
            AdapterView.OnItemClickListener { _, _, position, _ ->
                Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
            }
    }
}

SimpleAdapter jest przeznaczony dla list z danych Map. Przyjmuje List<Map<String, Any>> i tablicę from/to do wiązania kluczy Map z ID widżetów w układzie. SimpleAdapter jest wygodny dla szybkich prototypów, gdy nie potrzeba osobnej klasy adaptera. Wadą jest brak type-safe typizacji i trudność z niestandardową logiką.

xml
<!-- Układ z ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:divider="@color/divider"
        android:dividerHeight="1px"
        android:scrollbars="vertical" />
</LinearLayout>

Tworzenie niestandardowego adaptera z ViewHolder

Niestandardowy adapter dla ListView nadpisuje metodę getView(), którą RecyclerView dzieli na onCreateViewHolder + onBindViewHolder. W ListView oba kroki są połączone, więc programista musi samodzielnie zaimplementować wzorzec ViewHolder przez convertView (ponownie wykorzystywane View) i tag setTag().

kotlin
// Model danych
data class Product(
    name: String,
    price: String,
    imageResId: Int
)

// Niestandardowy adapter z ViewHolder
class ProductAdapter(
    context: Context,
    private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {

    // ViewHolder do buforowania
    private class ViewHolder(view: View) {
        val nameText: TextView = view.findViewById(R.id.tvName)
        val priceText: TextView = view.findViewById(R.id.tvPrice)
        val imageView: ImageView = view.findViewById(R.id.ivProduct)
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val view = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.item_product, parent, false)
            .also { it.tag = ViewHolder(it) }

        val holder = view.tag as ViewHolder
        val product = getItem(position)

        holder.nameText.text = product.name
        holder.priceText.text = product.price
        holder.imageView.setImageResource(product.imageResId)

        return view
    }
}

Kluczowa różnica od RecyclerView: w ListView metoda getView() jest wywoływana przy każdym pojawieniu się elementu na ekranie. Jeśli nie użyjesz convertView i ViewHolder, każde przewinięcie tworzy nowy obiekt View, co prowadzi do wycieków pamięci i opóźnień na listach z 50+ elementami. ViewHolder z view.tag — obowiązkowa optymalizacja dla ListView z niestandardowymi układami.

Ograniczenia ListView: dlaczego Google zaleca RecyclerView

ListView ma cztery ograniczenia architektoniczne, które Google naprawiło w RecyclerView: (1) tylko pionowa orientacja — brak siatek, poziomych list lub kaskad; (2) brak punktowych powiadomień — każda zmiana danych powoduje pełne przerysowanie przez notifyDataSetChanged(), co powoduje drgania; (3) brak wbudowanej animacji dodawania/usuwania elementów; (4) brak podziału odpowiedzialności — LayoutManager jest wbudowany, ItemAnimator nie istnieje.

xml
<!-- item_product.xml — układ elementu ListView -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="12dp">

    <ImageView
        android:id="@+id/ivProduct"
        android:layout_width="56dp"
        android:layout_height="56dp"
        android:scaleType="centerCrop" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical"
        android:paddingStart="12dp">

        <TextView
            android:id="@+id/tvName"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/tvPrice"
            android:textSize="14sp"
            android:textColor="@color/price" />
    </LinearLayout>
</LinearLayout>

Praktyczne konsekwencje: na listach do 20 elementów ListView działa tak samo szybko jak RecyclerView. Przy 100+ elementach różnica staje się zauważalna: ListView zaczyna tracić FPS z powodu braku obliczeń różnicowych i wymuszonego ViewHolder. Dla list z częstymi aktualizacjami (czat, feed, logi) ListView nie nadaje się — użyj RecyclerView z Paging 3. W IT Sectr migrujemy ListView na RecyclerView przy każdej pracy nad legacy-ekranami z więcej niż 50 elementami.

Konfiguracja ListView przez atrybuty XML

ListView udostępnia zestaw atrybutów XML do szybkiej konfiguracji bez kodu: android:divider (kolor/rysunek separatora), android:dividerHeight (grubość), android:scrollbars (paski przewijania), android:fastScrollEnabled (szybkie przewijanie przez uchwyt), android:choiceMode (tryb wyboru: singleChoice, multipleChoice). Atrybut android:entries pozwala wypełnić listę z tablicy zasobów (string-array) bez adaptera.

AtrybutOpisPrzykładowa wartość
android:dividerKolor lub drawable separatora@color/gray_light
android:dividerHeightGrubość separatora w px/dp1dp
android:entriesTablica stringów z zasobów@array/countries
android:choiceModeTryb wyboru elementusingleChoice
android:fastScrollEnabledSzybkie przewijanietrue
android:overlapAnchorKotwica dla list rozwijanych (API 21+)true

Tryb wyboru: choiceMode="singleChoice" włącza styl RadioButton, w którym wybrany element jest podświetlany. choiceMode="multipleChoice" — pola wyboru dla wielu elementów. Dane wyboru są dostępne przez listView.checkedItemPosition lub listView.checkedItemIds. Ważne: przy choiceMode ListView używa StateListDrawable dla tła — ustaw własny selektor, jeśli standardowy niebieski nie pasuje do projektu.

ListView vs RecyclerView: porównanie możliwości

Wybór między ListView a RecyclerView zależy od wymagań projektu. RecyclerView to nowoczesny standard, ale ListView może być uzasadniony dla prostych ekranów z minimalnymi kosztami utrzymania starego kodu. Porównanie według wszystkich kluczowych parametrów:

ParametrListViewRecyclerView
Rozmiar bibliotekiWbudowany w SDK (≈60 KB)AndroidX (≈300 KB z zależnościami)
Minimalna wersjaAPI 1API 14 (przez AndroidX)
OrientacjaTylko pionowaPionowa, pozioma, siatka, kaskada
ViewHolderRęczna implementacjaObowiązkowy z założenia
AnimacjaTylko przejścia fadeDefaultItemAnimator (add, remove, move, change)
Aktualizacje punktoweNie (tylko notifyDataSetChanged)notifyItemInserted/Removed/Changed
Click listeneronItemClickListener (wbudowany)Przez ViewHolder (brak wbudowanego)
WydajnośćWystarczająca dla list do 50 elementówStabilne 60 FPS dla 1000+ elementów

Podsumowanie: dla nowych projektów używaj RecyclerView. ListView pozostaw tylko w legacy-kodzie, gdzie migracja jest nieuzasadniona kosztowna. Według wytycznych Material Design 3 (2026), listy dowolnej złożoności powinny używać RecyclerView, aby spełniać nowoczesne standardy wydajności i dostępności.

Często zadawane pytania

Czy ListView jest przestarzałe czy nie?

ListView nie jest oznaczone jako @Deprecated w Android SDK API 35, ale Google nie zaleca jego używania w nowych projektach (soft-deprecated). Komponent pozostaje działający we wszystkich wersjach Androida. RecyclerView jest oficjalnym zamiennikiem od 2014 roku. Jeśli zaczynasz nowy projekt — wybierz RecyclerView. Jeśli utrzymujesz istniejący — ListView będzie działać bez zmian.

Jak dodać header/footer do ListView?

ListView obsługuje metody addHeaderView(View, Object, boolean) i addFooterView(View, Object, boolean). Header i footer są dodawane do ogólnej listy elementów, ale nie uczestniczą w adapterze — są zawsze wyświetlane na początku i końcu listy. Ważne: dodawaj header/footer przed ustawieniem adaptera (setAdapter()), w przeciwnym razie nie zostaną uwzględnione. Po dodaniu header ID pozycji elementów przesuwają się o liczbę headerów.

Dlaczego ListView nie przewija się wewnątrz ScrollView?

ListView i ScrollView oba obsługują pionowe gesty, co powoduje konflikt: zewnętrzny ScrollView przechwytuje przewijanie, a ListView się nie przewija. Rozwiązanie: (1) nie umieszczaj ListView w ScrollView — użyj RecyclerView z NestedScrollingChild, (2) zastąp ScrollView na NestedScrollView (API 21+), (3) użyj ListView ze stałą wysokością (android:layout_height="...dp") wewnątrz ScrollView, ale to zepsuje responsywność.

Jak wyczyścić ListView i odświeżyć dane?

Czyszczenie danych w ListView zależy od typu adaptera. Dla ArrayAdapter: adapter.clear() i adapter.addAll(newList) z następującym adapter.notifyDataSetChanged(). Dla niestandardowego adaptera: zaktualizuj listę danych i wywołaj notifyDataSetChanged(). Uwaga: listView.invalidateViews() tylko przerysowuje View, ale nie aktualizuje danych. Zawsze zmieniaj dane przez adapter, a nie bezpośrednio.

Jak zmienić wysokość elementu ListView?

Wysokość elementu ListView jest ustawiana w układzie item-rozmieszczenia. Jeśli układ używa layout_height="wrap_content", wysokość dostosowuje się do zawartości. Dla stałej wysokości wszystkich elementów użyj niestandardowego adaptera, który ustawia LayoutParams w getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). Dla różnorodnej wysokości użyj RecyclerView z różnymi typami widoków.

Podsumowanie

  • ListView — podstawowy komponent Android SDK dla pionowych list, dostępny od API 1.
  • ArrayAdapter — najprostszy adapter dla listy stringów; dla niestandardowych układów potrzebny nadpisany getView().
  • ViewHolder w ListView implementuje się ręcznie przez convertView i view.tag — obowiązkowy warunek wydajności.
  • ListView obsługuje choiceMode (single/multiple), header/footer, fastScroll i android:entries bez adaptera.
  • Ograniczenia ListView: tylko pionowa orientacja, brak animacji, pełne przerysowanie przy każdej zmianie.
  • ListView nie jest oznaczony jako przestarzały, ale Google zaleca RecyclerView dla wszystkich nowych projektów.
  • Dla list do 50 elementów ListView jest wystarczający; dla wszystkiego, co bardziej złożone — użyj RecyclerView.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również