0Pricing
Android Academy · Lektion

Adapter und DiffUtil

Erstellen Sie effiziente, animierte Listenadapter mit ListAdapter und DiffUtil.ItemCallback. Verwenden Sie submitList() für flüssige, partielle Aktualisierungen.

Adapter und DiffUtil ist eine kostenlose Android Academy-Lektion auf CoddyKit. Dies ist Lektion 6 von 6. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Android Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Android Academy-Kurs umfasst insgesamt 6 Lektionen.

Das Problem mit notifyDataSetChanged()

Wenn sich die Daten Ihrer RecyclerView ändern, zeichnet der Aufruf von notifyDataSetChanged() jedes Element neu — auch unveränderte. Das führt zu:

  • Keine Elementanimationen
  • Schlechter Leistung bei großen Listen
  • Flackernder UI

DiffUtil berechnet die kleinstmögliche Menge an Änderungen und wendet sie mit flüssigen Animationen an.

DiffUtil.ItemCallback

Definieren Sie, wie zwei Elemente verglichen werden. Zwei Fragen müssen beantwortet werden:

  • areItemsTheSame — repräsentieren sie dasselbe logische Element? (Vergleichen Sie üblicherweise die IDs.)
  • areContentsTheSame — sind alle sichtbaren Felder identisch?
data class Post(val id: Int, val title: String, val likes: Int)

class PostDiffCallback : DiffUtil.ItemCallback<Post>() {

    override fun areItemsTheSame(oldItem: Post, newItem: Post): Boolean {
        return oldItem.id == newItem.id   // same post?
    }

    override fun areContentsTheSame(oldItem: Post, newItem: Post): Boolean {
        return oldItem == newItem         // data class equality (all fields)
    }
}

ListAdapter

ListAdapter ist ein RecyclerView.Adapter mit integrierter DiffUtil-Unterstützung. Übergeben Sie Ihren ItemCallback an den Konstruktor und rufen Sie submitList() auf, um die Daten zu aktualisieren:

class PostAdapter : ListAdapter<Post, PostAdapter.ViewHolder>(PostDiffCallback()) {

    inner class ViewHolder(val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val post = getItem(position)   // use getItem(), not a manual list
        holder.binding.tvTitle.text = post.title
        holder.binding.tvLikes.text = "${post.likes} likes"
    }
}

Neue Listen übergeben

Rufen Sie submitList() immer auf, wenn sich Ihre Daten ändern. ListAdapter vergleicht die alte und die neue Liste in einem Hintergrundthread und wendet nur die erforderlichen Änderungen an:

// In Activity or Fragment:
val adapter = PostAdapter()
binding.recyclerView.adapter = adapter

// Observe ViewModel LiveData:
viewModel.posts.observe(viewLifecycleOwner) { posts ->
    adapter.submitList(posts)   // diff calculated off main thread
}

// Important: always submit a NEW list, not a mutated old one
// WRONG:  currentList.add(newPost); adapter.submitList(currentList)
// RIGHT:  adapter.submitList(currentList + newPost)

Click-Listener in ListAdapter

Übergeben Sie Click-Handler über den Konstruktor — ein gängiges und sauberes Muster:

class PostAdapter(
    private val onLike: (Post) -> Unit,
    private val onOpen: (Post) -> Unit
) : ListAdapter<Post, PostAdapter.ViewHolder>(PostDiffCallback()) {

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val post = getItem(position)
        holder.binding.tvTitle.text = post.title
        holder.binding.btnLike.setOnClickListener { onLike(post) }
        holder.itemView.setOnClickListener { onOpen(post) }
    }
}

// Usage:
val adapter = PostAdapter(
    onLike = { viewModel.likePost(it) },
    onOpen = { navigateToDetail(it.id) }
)

getChangePayload() — Teilaktualisierungen

Wenn sich nur ein Feld ändert (z. B. die Anzahl der Likes), vermeiden Sie mit getChangePayload() ein vollständiges erneutes Binden:

class PostDiffCallback : DiffUtil.ItemCallback<Post>() {
    override fun areItemsTheSame(oldItem: Post, newItem: Post) = oldItem.id == newItem.id
    override fun areContentsTheSame(oldItem: Post, newItem: Post) = oldItem == newItem

    override fun getChangePayload(oldItem: Post, newItem: Post): Any? {
        // Return non-null payload to trigger partial bind
        return if (oldItem.likes != newItem.likes) "likes_changed" else null
    }
}

// In Adapter.onBindViewHolder with payloads:
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: List<Any>) {
    if (payloads.contains("likes_changed")) {
        holder.binding.tvLikes.text = "${getItem(position).likes} likes"
    } else {
        super.onBindViewHolder(holder, position, payloads)
    }
}

Mehrere View-Typen

Verwenden Sie getItemViewType(), um verschiedene Elementlayouts in derselben Liste darzustellen:

class FeedAdapter : ListAdapter<FeedItem, RecyclerView.ViewHolder>(FeedDiffCallback()) {

    companion object {
        const val TYPE_POST = 0
        const val TYPE_AD   = 1
    }

    override fun getItemViewType(position: Int): Int {
        return when (getItem(position)) {
            is FeedItem.Post -> TYPE_POST
            is FeedItem.Ad   -> TYPE_AD
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
        TYPE_POST -> PostViewHolder(ItemPostBinding.inflate(LayoutInflater.from(parent.context), parent, false))
        else      -> AdViewHolder(ItemAdBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }
}

ConcatAdapter — Listen zusammenstellen

ConcatAdapter kombiniert mehrere Adapter zu einer einzigen RecyclerView — ideal für Kopfbereiche, Fußbereiche und Ladezustände:

// Separate adapters for different sections
val headerAdapter = HeaderAdapter()
val postAdapter   = PostAdapter()
val loaderAdapter = LoadingAdapter()

// Combine them in order
binding.recyclerView.adapter = ConcatAdapter(headerAdapter, postAdapter, loaderAdapter)

// Update each independently
postAdapter.submitList(posts)
loaderAdapter.showLoading(true)

AsyncListDiffer — Manuelle Steuerung

Wenn Sie ListAdapter nicht erweitern können, verwenden Sie AsyncListDiffer direkt:

class CustomAdapter : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    private val differ = AsyncListDiffer(this, PostDiffCallback())

    fun submitList(list: List<Post>) = differ.submitList(list)
    fun getItem(position: Int): Post = differ.currentList[position]

    override fun getItemCount() = differ.currentList.size
}

Tipps zur Performance

Halten Sie Ihre RecyclerView schnell:

  • Verwenden Sie setHasStableIds(true), wenn Ihre Elemente eindeutige, stabile IDs besitzen
  • Vermeiden Sie das Erstellen von Objekten innerhalb von onBindViewHolder
  • Verwenden Sie RecycledViewPool, um wiederverwendete Views über mehrere RecyclerViews hinweg zu teilen
  • Verwenden Sie setItemViewCacheSize(), um den Cache für nicht sichtbare Elemente zu vergrößern

Animationen inklusive

Die DiffUtil-Integration von ListAdapter löst automatisch DefaultItemAnimator aus für:

  • Einblenden neuer Elemente
  • Ausblenden entfernter Elemente
  • Verschiebeanimationen, wenn ein Element seine Position ändert

Legen Sie einen eigenen Animator fest: recyclerView.itemAnimator = MyAnimator(). Setzen Sie ihn auf null, um alle Animationen zu deaktivieren.

Schnelltest

Welche beiden Methoden müssen Sie in DiffUtil.ItemCallback implementieren?

Zusammenfassung: Adapter & DiffUtil

Effiziente, animierte Listen mit minimalem Code:

  • DiffUtil.ItemCallback — Identität (areItemsTheSame) und Gleichheit (areContentsTheSame) definieren
  • ListAdapter — asynchroner Listenvergleich integriert; rufen Sie submitList() auf
  • Übergeben Sie immer eine neue Listenreferenz und verändern Sie niemals die aktuelle Liste
  • getChangePayload() für Teilaktualisierungen
  • ConcatAdapter, um mehrere Adapter zusammenzustellen

Als Nächstes: Navigation über mehrere Bildschirme mit der Navigation Component.

Häufig gestellte Fragen

Ist die Lektion „Adapter und DiffUtil“ kostenlos?

Ja — der vollständige Text von „Adapter und DiffUtil“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Android Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Android Academy-Kurs umfasst insgesamt 6 Lektionen.

Was lerne ich in „Adapter und DiffUtil“?

Erstellen Sie effiziente, animierte Listenadapter mit ListAdapter und DiffUtil.ItemCallback. Verwenden Sie submitList() für flüssige, partielle Aktualisierungen. Du übst Android Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Android Academy zu starten?

Keine Vorkenntnisse erforderlich. Android Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 6 von 6.

Wie lange dauert die Lektion „Adapter und DiffUtil“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Android Academy-Lektion Code schreiben und ausführen?

Ja. Jede Android Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Kotlin-Collections
  2. Grundlagen von RecyclerView
  3. Click-Ereignisse in RecyclerView
  4. SharedPreferences
  5. DataStore Preferences
  6. Adapter und DiffUtil
← Zurück zu Android Academy