ListView: какво е това, прости списъци в Android приложения

Автор: IT Sectr Публикувано: 2026-02-23 Време за четене: 8 мин

ListView — основен Android компонент за показване на вертикални списъци, достъпен от API 1 (Android 1.0). ListView създава списък от елементи с помощта на ArrayAdapter и поддържа персонализирани оформления на клетки. Разбираме какво е ListView, как да го конфигурираме и какви капани крие този стар, но все още използван компонент. В IT Sectr срещаме ListView в legacy проекти и поддържаме обратна съвместимост с Android 4.4. За сравнение с модерната алтернатива прочетете статията за RecyclerView.

Основни точки

  • ListView — вграден Android SDK компонент за вертикални списъци, появил се в Android 1.0.
  • ArrayAdapter — стандартен адаптер за свързване на масив от данни с ListView чрез toString() или персонализирано оформление.
  • SimpleAdapter — адаптер за списъци от Map данни, удобен за прости екрани без ViewModel.
  • onItemClickListener — вграден манипулатор на кликвания, който не изисква допълнителна реализация в адаптера.
  • convertView — механизъм за повторно използване на View в ListView, който разработчикът трябва да реализира ръчно (аналог на ViewHolder).

Какво е ListView в Android?

ListView — е View група от пакета android.widget, предназначена за показване на превъртаем списък от елементи. ListView имплементира шаблона AdapterView: данните се предават чрез адаптер (Adapter), който преобразува елементите от данни във View, показвани на екрана. ListView съществува в Android SDK от първата версия (API 1) и не изисква допълнителни библиотеки.

Архитектура на ListView е по-проста от RecyclerView: LayoutManager е вграден (само вертикално превъртане), анимация на промени липсва, разделителите се задават чрез XML атрибута android:divider. ListView автоматично добавя OverScrollView и подчертаване при фокус, което опростява прототипирането. Според данни на Statista (2025), около 15% от Android приложенията в топ-100 на Play Store все още съдържат ListView — основно legacy проекти с минимална поддръжка.

История и актуалност

ListView беше основният компонент за списъци в Android от 2008 до 2014 г., когато на Google I/O 2014 беше представен RecyclerView. Въпреки статуса на частично остарял (soft-deprecated), ListView остава достъпен във всички версии на Android и не е маркиран като @Deprecated в SDK API 35. Ако вашият проект поддържа Android 4.4 (API 19) и по-ниски, ListView е единственият вграден избор, тъй като RecyclerView изисква AndroidX и gradle зависимости.

Типове адаптери: ArrayAdapter и SimpleAdapter

ArrayAdapter — най-популярният адаптер за ListView. Той приема List<T> и оформление за всеки елемент. По подразбиране ArrayAdapter извиква toString() за всеки обект и показва текста в simple_list_item_1 (един TextView). За персонализирано показване трябва да се презапише getView() или да се предаде персонализирано оформление в конструктора.

kotlin
// ListView с ArrayAdapter (прост текст)
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val countries = listOf(
        "Русия", "САЩ", "Германия",
        "Франция", "Япония", "Китай", "Бразилия"
    )

    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

        // Обработка на кликване
        binding.listView.onItemClickListener =
            AdapterView.OnItemClickListener { _, _, position, _ ->
                Toast.makeText(this, countries[position], Toast.LENGTH_SHORT).show()
            }
    }
}

SimpleAdapter е предназначен за списъци от Map данни. Той приема List<Map<String, Any>> и масив from/to за свързване на ключовете на Map с ID-тата на джаджите в оформлението. SimpleAdapter е удобен за бързи прототипи, когато не е нужен отделен клас адаптер. Недостатък — липса на type-safe типизация и сложност с персонализирана логика.

xml
<!-- Оформление с 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>

Създаване на персонализиран адаптер с ViewHolder

Персонализиран адаптер за ListView презаписва метода getView(), който RecyclerView разделя на onCreateViewHolder + onBindViewHolder. В ListView и двете стъпки са комбинирани, затова разработчикът трябва самостоятелно да имплементира шаблона ViewHolder чрез convertView (повторно използвано View) и етикет setTag().

kotlin
// Модел на данни
data class Product(
    name: String,
    price: String,
    imageResId: Int
)

// Персонализиран адаптер с ViewHolder
class ProductAdapter(
    context: Context,
    private val products: List<Product>
) : ArrayAdapter<Product>(context, 0, products) {

    // ViewHolder за кеширане
    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
    }
}

Ключова разлика от RecyclerView: в ListView методът getView() се извиква за всяко появяване на елемент на екрана. Ако не се използват convertView и ViewHolder, всяко превъртане създава нов обект View, което води до изтичане на памет и забавяния на списъци с 50+ елемента. ViewHolder с view.tag — задължителна оптимизация за ListView с персонализирани оформления.

Ограничения на ListView: защо Google препоръчва RecyclerView

ListView има четири архитектурни ограничения, които Google поправи в RecyclerView: (1) само вертикална ориентация — без мрежи, хоризонтални списъци или каскади; (2) липса на точкови известия — всяка промяна на данни причинява пълно прерисуване чрез notifyDataSetChanged(), което създава потрепвания; (3) няма вградена анимация за добавяне/премахване на елементи; (4) няма разделение на отговорностите — LayoutManager е вграден, ItemAnimator липсва.

xml
<!-- item_product.xml — оформление на елемент на 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>

Практически последствия: на списъци до 20 елемента ListView работи толкова бързо, колкото RecyclerView. При 100+ елемента разликата става забележима: ListView започва да губи FPS поради липса на диференциални изчисления и принудителен ViewHolder. За списъци с чести обновявания (чат, фийд, логове) ListView не е подходящ — използвайте RecyclerView с Paging 3. В IT Sectr мигрираме ListView към RecyclerView при всяка работа по legacy екрани с повече от 50 елемента.

Конфигуриране на ListView чрез XML атрибути

ListView предоставя набор от XML атрибути за бърза конфигурация без код: android:divider (цвят/рисунка на разделител), android:dividerHeight (дебелина), android:scrollbars (ленти за превъртане), android:fastScrollEnabled (бързо превъртане чрез дръжка), android:choiceMode (режим на избор: singleChoice, multipleChoice). Атрибутът android:entries позволява попълване на списъка от масив с ресурси (string-array) без адаптер.

АтрибутОписаниеПримерна стойност
android:dividerЦвят или drawable на разделител@color/gray_light
android:dividerHeightДебелина на разделител в px/dp1dp
android:entriesМасив от низове от ресурси@array/countries
android:choiceModeРежим на избор на елементsingleChoice
android:fastScrollEnabledБързо превъртанеtrue
android:overlapAnchorКотва за изскачащи списъци (API 21+)true

Режим на избор: choiceMode="singleChoice" активира стил RadioButton, при който избраният елемент се подчертава. choiceMode="multipleChoice" — квадратчета за отметка за множество елементи. Данните за избор са достъпни чрез listView.checkedItemPosition или listView.checkedItemIds. Важно: при choiceMode ListView използва StateListDrawable за фон — задайте собствен селектор, ако стандартният син не подхожда на дизайна.

ListView vs RecyclerView: сравнение на възможностите

Избор между ListView и RecyclerView зависи от изискванията на проекта. RecyclerView е модерният стандарт, но ListView може да бъде оправдан за прости екрани с минимални разходи за поддръжка на стар код. Сравнение по всички ключови параметри:

ПараметърListViewRecyclerView
Размер на библиотекаВграден в SDK (≈60 KB)AndroidX (≈300 KB със зависимости)
Минимална версияAPI 1API 14 (чрез AndroidX)
ОриентацияСамо вертикалнаВертикална, хоризонтална, мрежа, каскада
ViewHolderРъчна имплементацияЗадължителен по архитектура
АнимацияСамо fade преходиDefaultItemAnimator (add, remove, move, change)
Точкови обновяванияНе (само notifyDataSetChanged)notifyItemInserted/Removed/Changed
Click listeneronItemClickListener (вграден)Чрез ViewHolder (няма вграден)
ПроизводителностДостатъчен за списъци до 50 елементаСтабилни 60 FPS за 1000+ елемента

Заключение: за нови проекти използвайте RecyclerView. Оставете ListView само в legacy код, където миграцията е неоправдано скъпа. Според насоките на Material Design 3 (2026), списъци с всякаква сложност трябва да използват RecyclerView за съответствие с модерните стандарти за производителност и достъпност.

Често задавани въпроси

ListView остарял ли е или не?

ListView не е маркиран като @Deprecated в Android SDK API 35, но Google не препоръчва използването му в нови проекти (soft-deprecated). Компонентът остава работещ във всички версии на Android. RecyclerView е официалният заместител от 2014 г. Ако започвате нов проект — изберете RecyclerView. Ако поддържате съществуващ — ListView ще продължи да работи без промени.

Как да добавя header/footer към ListView?

ListView поддържа методите addHeaderView(View, Object, boolean) и addFooterView(View, Object, boolean). Header и footer се добавят към общия списък от елементи, но не участват в адаптера — те винаги се показват в началото и края на списъка. Важно: добавяйте header/footer преди настройване на адаптера (setAdapter()), в противен случай няма да бъдат взети предвид. След добавяне на header, ID-тата на позициите на елементите се изместват с броя на header-ите.

Защо ListView не се превърта вътре в ScrollView?

ListView и ScrollView и двата обработват вертикални жестове, което създава конфликт: външният ScrollView прихваща превъртането и ListView не се превърта. Решение: (1) не влагайте ListView в ScrollView — използвайте RecyclerView с NestedScrollingChild, (2) заменете ScrollView с NestedScrollView (API 21+), (3) използвайте ListView с фиксирана височина (android:layout_height="...dp") вътре в ScrollView, но това ще счупи адаптивността.

Как да изчистя ListView и да обновя данните?

Изчистването на данни в ListView зависи от типа на адаптера. За ArrayAdapter: adapter.clear() и adapter.addAll(newList) последвано от adapter.notifyDataSetChanged(). За персонализиран адаптер: обновете списъка с данни и извикайте notifyDataSetChanged(). Внимание: listView.invalidateViews() само прерисува View, но не обновява данните. Винаги променяйте данните чрез адаптера, а не директно.

Как да променя височината на елемент на ListView?

Височината на елемент на ListView се задава в оформлението на item. Ако оформлението използва layout_height="wrap_content", височината се нагажда според съдържанието. За фиксирана височина на всички елементи използвайте персонализиран адаптер, който задава LayoutParams в getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). За разнородна височина използвайте RecyclerView с различни типове изгледи.

Резюме

  • ListView — основен Android SDK компонент за вертикални списъци, достъпен от API 1.
  • ArrayAdapter — най-простият адаптер за списък от низове; за персонализирани оформления е нужен презаписан getView().
  • ViewHolder в ListView се имплементира ръчно чрез convertView и view.tag — задължително условие за производителност.
  • ListView поддържа choiceMode (single/multiple), header/footer, fastScroll и android:entries без адаптер.
  • Ограничения на ListView: само вертикална ориентация, липса на анимация, пълно прерисуване при всяка промяна.
  • ListView не е маркиран като остарял, но Google препоръчва RecyclerView за всички нови проекти.
  • За списъци до 50 елемента ListView е достатъчен; за всичко по-сложно — използвайте RecyclerView.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също