Последовательность действий:
- Вводится поисковый запрос (например, «a»).
- Приложение выполняет поиск по списку элементов сообщения, фильтрует результаты и обновления полей isSearched
и ключевых слов. - Для каждого найденного элемента я вызываю notifyItemChanged(position, arrayListOf(KEY_SEARCHING)), который должен вызвать onBindViewHolder с полезной нагрузкой.
Вот моя текущая настройка:
MessageFragment.kt
private var currentPosition: Int = -1
private var searchResults: List = emptyList()
private fun searchMessage(searchQuery: String?) {
messageAdapter?.let { adapter ->
searchResults = adapter.currentList.filter { item ->
item.isSearched = true
item.keyword = searchQuery
item.content?.contains(searchQuery.toString(), ignoreCase = true) == true
}
if (searchResults.isNotEmpty()) {
currentPosition = searchResults.size - 1
val position = adapter.currentList.indexOf(searchResults[currentPosition])
binding.rvMessage.scrollToPosition(position)
adapter.notifyItemChanged(position, arrayListOf(KEY_SEARCHING))
} else {
activity?.toast(getString(R.string.empty_message))
currentPosition = -1 // Reset position
}
}
}
private fun searchNextMessage() {
if (searchResults.isEmpty() || currentPosition == -1) return
currentPosition++
if (currentPosition >= searchResults.size) {
activity?.toast(getString(R.string.empty_message))
currentPosition = searchResults.size - 1 // Limit position
} else {
messageAdapter?.let { adapter ->
searchResults = adapter.currentList.filter { item ->
item.isSearched = true
item.keyword = searchQuery
item.content?.contains(searchQuery.toString(), ignoreCase = true) == true
}
val nextItemPosition = adapter.currentList.indexOf(searchResults[currentPosition])
binding.rvMessage.scrollToPosition(nextItemPosition)
adapter.notifyItemChanged(nextItemPosition, arrayListOf(KEY_SEARCHING))
}
}
}
private fun searchPreviousMessage() {
if (searchResults.isEmpty() || currentPosition == -1) return
currentPosition--
if (currentPosition < 0) {
activity?.toast(getString(R.string.empty_message))
currentPosition = 0 // Limit position
} else {
messageAdapter?.let { adapter ->
searchResults = adapter.currentList.filter { item ->
item.isSearched = true
item.keyword = searchQuery
item.content?.contains(searchQuery.toString(), ignoreCase = true) == true
}
val previousItemPosition =
adapter.currentList.indexOf(searchResults[currentPosition])
binding.rvMessage.scrollToPosition(previousItemPosition)
adapter.notifyItemChanged(previousItemPosition, arrayListOf(KEY_SEARCHING))
}
}
}
}
Адаптер сообщений
class MessageDiffCallback : DiffUtil.ItemCallback() {
override fun areItemsTheSame(oldItem: Message, newItem: Message): Boolean {
return oldItem.id == newItem.id && oldItem.divider == newItem.divider
}
override fun areContentsTheSame(oldItem: Message, newItem: Message): Boolean {
return oldItem == newItem
}
override fun getChangePayload(oldItem: Message, newItem: Message): Any {
val data = arrayListOf()
if (oldItem.displayType != newItem.displayType) {
data.add(KEY_TYPE)
}
if (oldItem.isSearched != newItem.isSearched) {
data.add(KEY_SEARCHING)
}
if (oldItem.keyword != newItem.keyword) {
data.add(KEY_SEARCHING)
}
if (oldItem.isFocused != newItem.isFocused) {
data.add(KEY_FOCUS)
}
return data
}
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList
) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val item = getItem(position)
val listChanges = payloads.firstOrNull() as? ArrayList
if (item != null) {
if (listChanges?.contains(KEY_TYPE) == true) {
(holder as? BaseViewHolder)?.changeBg(item)
}
if (listChanges?.contains(KEY_SEARCHING) == true) {
(holder as? TextViewHolder)?.bindSearching(item)
}
if (listChanges?.contains(KEY_FOCUS) == true) {
(holder as? TextViewHolder)?.bindFocus(item)
}
}
}
}
fun bindSearching(item: Message) {
if (item.isSearched) {
val sb = SpannableStringBuilder(item.content)
val word: Pattern = Pattern.compile(item.keyword?.lowercase() ?: "")
val match: Matcher = word.matcher(item.content?.lowercase() ?: "")
while (match.find()) {
itemView.setBackgroundColor(ContextCompat.getColor(itemView.context,R.color.color_backdrop))
Handler(Looper.getMainLooper()).postDelayed({
itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.white))
}, 2000)
val highlightText = BackgroundColorSpan(
ContextCompat.getColor(itemView.context, R.color.color_backdrop)
) //specify color here
sb.setSpan(highlightText, match.start(), match.end(), Spannable.SPAN_INCLUSIVE_INCLUSIVE)
}
getContentView()?.text = sb
} else {
getContentView()?.text = item.content
}
}
Подробнее здесь: https://stackoverflow.com/questions/791 ... ighlightin