Я занимаюсь улучшением стабильности и производительности своего приложения, но сейчас я застрял на предупреждении от Android Studio. Обратите внимание на следующий класс адаптера:

private class CoinsAdapter(private val fragment: CoinFragment, private val coins: List<Coin>): RecyclerView.Adapter<CoinsAdapter.ViewHolder>(), Filterable {

    private val filter = ArrayList(coins)

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val coin = filter[position]
        
        holder.binding.coinImage.setImageResource(coin.image)
        holder.binding.coinText.text = builder.toString()
    }

    override fun getItemCount() = filter.size

    override fun getFilter() = object : Filter() {

        override fun performFiltering(constraint: CharSequence): FilterResults {
            if (constraint.length < 2) return fetchResults(coins)
            val pattern = constraint.toString().lowercase().trim()

            val filter = arrayListOf<Coin>()
            for (coin in coins) if (coin.name.lowercase().contains(pattern)) filter.add(coin)

            return fetchResults(filter)
        }

        private fun fetchResults(coins: List<Coin>): FilterResults {
            val results = FilterResults()
            results.values = coins

            return results
        }

        override fun publishResults(constraint: CharSequence, results: FilterResults) {
            filter.clear()
            filter.addAll(results.values as List<Coin>)

            notifyDataSetChanged()
        }
    }

    private inner class ViewHolder(val binding: ItemCoinBinding) : RecyclerView.ViewHolder(binding.root)
}

Адаптер и фильтр работают отлично, но обратите внимание на функцию publishResults. Android Studio предупреждает, что в отношении notifyDataSetChanged.

It will always be more efficient to use more specific change events if you can. Rely on notifyDataSetChanged as a last resort.

Однако я не знаю, как использовать notifyDataSetChanged в этом случае (с фильтром). Какой метод будет правильным и как его использовать в этом случае?

2
Ravers 11 Окт 2021 в 16:20

2 ответа

Лучший ответ

Насколько мне известно, нет смысла использовать интерфейс Filterable с RecyclerView.Adapter. Filterable предназначен для использования в AdapterView Adapters, потому что есть несколько виджетов, которые проверяют, является ли адаптер фильтруемым, и могут автоматически предоставлять некоторые возможности фильтрации. Однако RecyclerView.Adapter не имеет никакого отношения к адаптеру AdapterView.

Вы по-прежнему можете использовать интерфейс фильтра как способ систематизировать свой код, если хотите, но мне это кажется ненужным дополнительным шаблоном. Я видел другие старые ответы на StackOverflow, в которых говорилось о реализации Filterable в RecyclerView.Adapter, но я думаю, что они делают это по привычке, работая со старым классом адаптера.

Что касается повышения производительности вашего адаптера при фильтрации, есть несколько вариантов.

  1. Используйте SortedList и SortedList.Callback для управления своим списком. Обратный вызов позволяет вам реализовать набор функций для уведомления об изменении определенных элементов или диапазонов элементов, а не всего списка сразу. Я не использовал это, и кажется, что есть много места для того, чтобы что-то сделать не так, потому что нужно реализовать так много функций обратного вызова. Это также тонна шаблонов. главный ответ здесь описывает, как это сделать, но это несколько лет назад, поэтому я не знаю, есть ли более современный способ.

  2. Расширьте свой адаптер из ListAdapter. Конструктор ListAdapter принимает аргумент DiffUtil.ItemCallback. Обратный вызов сообщает ему, как сравнить два элемента. Пока элементы вашей модели имеют уникальные свойства идентификатора, это очень легко реализовать. При использовании ListAdapter вы не создаете собственное свойство List в классе, а вместо этого позволяете суперклассу обрабатывать это. Затем вместо установки нового отфильтрованного списка и вызова notifyDataSetChanged() вы вызываете adapter.submitList() со своим отфильтрованным списком, и он использует DiffUtil для автоматического изменения только необходимых представлений, а также делает это с красивой анимацией. . Обратите внимание, что вам не нужно переопределять getItemCount(), поскольку суперкласс владеет списком.

Поскольку вы фильтруете элементы, вы можете сохранить дополнительное свойство для хранения исходного неотфильтрованного списка и использовать его при применении новых фильтров. Поэтому в этом примере я создал дополнительное свойство списка. Вам нужно быть осторожным, используя его только для передачи в submitList() и всегда использовать currentList в onBindViewHolder(), поскольку currentList - это то, что на самом деле используется адаптером для отображения.

Я удалил функцию Filterable и сделал так, чтобы внешний класс мог просто установить свойство filter.

class CoinsAdapter : ListAdapter<Coin, CoinsAdapter.ViewHolder>(CoinItemCallback) {
    
    object CoinItemCallback : DiffUtil.ItemCallback<Coin>() {
        override fun areItemsTheSame(oldItem: Coin, newItem: Coin): Boolean = oldItem.id == newItem.id
        override fun areContentsTheSame(oldItem: Coin, newItem: Coin): Boolean = oldItem == newItem
    }
    
    var coins: List<Coin> = emptyList()
        set(value) {
            field = value
            onListOrFilterChange()
        }

    var filter: CharSequence = ""
        set(value) {
            field = value
            onListOrFilterChange()
        }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val coin = currentList[position]

        holder.binding.coinImage.setImageResource(coin.image)
        holder.binding.coinText.text = builder.toString()
    }

    private fun onListOrFilterChange() {
        if (filter.length < 2) {
            submitList(coins)
            return
        }
        val pattern = filter.toString().lowercase().trim()
        val filteredList = coins.filter { pattern in it.name.lowercase() }
        submitList(filteredList)
    }

    inner class ViewHolder(val binding: ItemCoinBinding) : RecyclerView.ViewHolder(binding.root)
}
1
Tenfour04 11 Окт 2021 в 14:18