Android Academy · Lektion

Adaptere og DiffUtil

Byg effektive, animerede listeadaptere med ListAdapter og DiffUtil.ItemCallback. Brug submitList() til jævne, delvise opdateringer.

Lektion 6 af 613 trin

Adaptere og DiffUtil er en gratis Android Academy-lektion på CoddyKit. Dette er lektion 6 af 6. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Android Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Android Academy-kurset indeholder 6 lektioner i alt.

Problemet med notifyDataSetChanged()

Når dataene i din RecyclerView ændres, tegner et kald til notifyDataSetChanged() hvert eneste element igen — også elementer, der ikke er ændret. Det medfører:

  • Ingen elementanimationer
  • Dårlig ydeevne for store lister
  • Flimren i brugergrænsefladen

DiffUtil beregner det mindst mulige sæt af ændringer og anvender dem med jævne animationer.

DiffUtil.ItemCallback

Definer, hvordan to elementer sammenlignes. Der er to spørgsmål, du skal besvare:

  • areItemsTheSame — repræsenterer de det samme logiske element? (sammenlign normalt id'er)
  • areContentsTheSame — er alle synlige felter identiske?
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 er en RecyclerView.Adapter med indbygget DiffUtil. Giv din ItemCallback til dens konstruktør, og kald submitList() for at opdatere dataene:

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"
    }
}

Indsend nye lister

Kald submitList(), hver gang dine data ændres. ListAdapter sammenligner den gamle og den nye liste i en baggrundstråd og anvender kun de nødvendige ændringer:

// 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)

Kliklyttere i ListAdapter

Giv klikhåndteringer med via konstruktøren — et almindeligt og overskueligt mønster:

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() — delvise opdateringer

Når kun ét felt ændres (f.eks. antallet af likes), kan du undgå en fuld genbinding med getChangePayload():

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)
    }
}

Flere visningstyper

Brug getItemViewType() til at gengive forskellige elementlayouts i den samme liste:

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 — sammensætning af lister

ConcatAdapter kombinerer flere adaptere til én enkelt RecyclerView — velegnet til sidehoveder, sidefødder og indlæsningstilstande:

// 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 — manuel styring

Hvis du ikke kan nedarve fra ListAdapter, kan du bruge AsyncListDiffer direkte:

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
}

Tip til ydeevne

Hold din RecyclerView hurtig:

  • Brug setHasStableIds(true), hvis dine elementer har entydige, stabile id'er
  • Undgå at oprette objekter inde i onBindViewHolder
  • Brug RecycledViewPool til at dele genbrugte visninger mellem flere RecyclerViews
  • Brug setItemViewCacheSize() til at øge cachen uden for skærmen

Animationer følger automatisk med

ListAdapters integration med DiffUtil udløser automatisk DefaultItemAnimator for:

  • Indtoning af nye elementer
  • Udtoning af fjernede elementer
  • Flytteanimation, når et element skifter position

Angiv en brugerdefineret animator: recyclerView.itemAnimator = MyAnimator(). Sæt den til null for at deaktivere alle animationer.

Hurtigt tjek

Hvilke to metoder skal du implementere i DiffUtil.ItemCallback?

Opsummering: adaptere og DiffUtil

Effektive, animerede lister med minimal kode:

  • DiffUtil.ItemCallback — definer identitet (areItemsTheSame) og lighed (areContentsTheSame)
  • ListAdapter — asynkron sammenligning er indbygget; kald submitList()
  • Indsend altid en ny listereference, og mutér aldrig den aktuelle liste
  • getChangePayload() til delvise genbindinger
  • ConcatAdapter til at sammensætte flere adaptere

Næste trin: navigation mellem flere skærme med Navigation Component.

Gratis at komme i gang

Lær Kotlin med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
36
Lektioner
152

Ofte stillede spørgsmål

Er lektionen “Adaptere og DiffUtil” gratis?

Ja — alle 3 lektioner i læringssporet Android Academy, inklusive “Adaptere og DiffUtil”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Android Academy-kurset indeholder 6 lektioner i alt.

Hvad lærer jeg i “Adaptere og DiffUtil”?

Byg effektive, animerede listeadaptere med ListAdapter og DiffUtil.ItemCallback. Brug submitList() til jævne, delvise opdateringer. Du øver dig i Android Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Android Academy?

Der kræves ingen tidligere erfaring. Android Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 6 af 6.

Hvor lang tid tager lektionen “Adaptere og DiffUtil”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Android Academy-lektion?

Ja. Alle Android Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Kotlin-samlinger
  2. Grundlæggende RecyclerView
  3. Klikhændelser i RecyclerView
  4. SharedPreferences
  5. DataStore Preferences
  6. Adaptere og DiffUtil
← Tilbage til Android Academy