ListView — основна Android компонента за приказ вертикалних листи, доступна од API 1 (Android 1.0). ListView креира листу елемената помоћу ArrayAdapter-а и подржава прилагођене изгледе ћелија. Разјашњавамо шта је ListView, како га подесити и какве замке крије ова стара, али још увек коришћена компонента. У IT Sectr-у сусрећемо ListView у legacy пројектима и одржавамо уназад компатибилност са Android 4.4. За поређење са савременом алтернативом прочитајте чланак о RecyclerView-у.
Главно
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 апликација у top-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 — најпопуларнији адаптер за ListView. Он прихвата List<T> и изглед за сваки елемент. Подразумевано ArrayAdapter позива toString() за сваки објекат и приказује текст у simple_list_item_1 (један TextView). За прилагођени приказ потребно је прегазити getView() или проследити прилагођени изглед у конструктору.
// 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 типизације и сложеност са прилагођеном логиком.
<!-- Изглед са 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>
Прилагођени адаптер за ListView прегази метод getView(), који RecyclerView раздваја на onCreateViewHolder + onBindViewHolder. У ListView-у оба корака су спојена, зато програмер мора сам да имплементира образац ViewHolder кроз convertView (поновно коришћени View) и tag setTag().
// Модел података
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-у: (1) само вертикална оријентација — нема мрежа, хоризонталних листи или каскада; (2) одсуство тачкастих обавештења — било која промена података изазива потпуно прецртавање путем notifyDataSetChanged(), што ствара трзаје; (3) нема уграђене анимације додавања/уклањања елемената; (4) нема раздвајања одговорности — LayoutManager је уграђен, ItemAnimator не постоји.
<!-- 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 атрибута за брзо подешавање без кода: 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/dp | 1dp |
| 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-а и RecyclerView-а зависи од захтева пројекта. RecyclerView је савремени стандард, али ListView може бити оправдан за једноставне екране са минималним трошковима одржавања старог кода. Поређење по свим кључним параметрима:
| Параметар | ListView | RecyclerView |
|---|---|---|
| Величина библиотеке | Уграђен у SDK (≈60 KB) | AndroidX (≈300 KB са зависностима) |
| Минимална верзија | API 1 | API 14 (путем AndroidX) |
| Оријентација | Само вертикална | Вертикална, хоризонтална, мрежа, каскада |
| ViewHolder | Ручна имплементација | Обавезан по архитектури |
| Анимација | Само fade прелази | DefaultItemAnimator (add, remove, move, change) |
| Тачкаста ажурирања | Не (само notifyDataSetChanged) | notifyItemInserted/Removed/Changed |
| Click listener | onItemClickListener (уграђен) | Путем ViewHolder-а (нема уграђеног) |
| Перформансе | Довољне за листе до 50 елемената | Стабилних 60 FPS за 1000+ елемената |
Закључак: за нове пројекте користите RecyclerView. ListView оставите само у legacy коду где миграција није оправдано скупа. Према смерницама Material Design 3 (2026), листе било које сложености треба да користе RecyclerView за усаглашавање са савременим стандардима перформанси и доступности.
Често постављана питања
ListView није означен као @Deprecated у Android SDK API 35, али Google не препоручује његово коришћење у новим пројектима (soft-deprecated). Компонента остаје функционална у свим верзијама Android-а. RecyclerView је званична замена од 2014. године. Ако започињете нови пројекат — изаберите RecyclerView. Ако одржавате постојећи — ListView ће наставити да ради без промена.
ListView подржава методе addHeaderView(View, Object, boolean) и addFooterView(View, Object, boolean). Header и footer се додају у општу листу елемената, али не учествују у адаптеру — увек се приказују на почетку и крају листе. Важно: додајте header/footer пре постављања адаптера (setAdapter()), иначе неће бити узети у обзир. Након додавања header-а, ID позиција елемената се померају за број header-а.
ListView и ScrollView оба обрађују вертикалне гестове, што ствара конфликт: спољашњи ScrollView пресреће померање, а ListView се не помера. Решење: (1) не уграђујте ListView у ScrollView — користите RecyclerView са NestedScrollingChild, (2) замените ScrollView са NestedScrollView (API 21+), (3) користите ListView са фиксном висином (android:layout_height="...dp") унутар ScrollView-а, али то ће сломити прилагодљивост.
Чишћење података у ListView-у зависи од типа адаптера. За ArrayAdapter: adapter.clear() и adapter.addAll(newList) са накнадним adapter.notifyDataSetChanged(). За прилагођени адаптер: ажурирајте листу података и позовите notifyDataSetChanged(). Пажња: listView.invalidateViews() само прецртава View, али не ажурира податке. Увек мењајте податке кроз адаптер, а не директно.
Висина елемента ListView-а се поставља у изгледу item-разметке. Ако изглед користи layout_height="wrap_content", висина се прилагођава садржају. За фиксну висину свих елемената користите прилагођени адаптер који поставља LayoutParams у getView(): view.layoutParams.height = resources.getDimensionPixelSize(R.dimen.itemHeight). За различите висине користите RecyclerView са различитим типовима приказа.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође