我创建了自己的自定义过滤器视图,就像自动完成文本视图一样。
因此,为了解释,我将完整列表传递给适配器,在用户输入 3 个或更多字符后,我然后过滤列表并使用 diff util 显示。这一切都在我的适配器中完成(见下文)
import android.os.Handler
import android.support.v7.recyclerview.extensions.ListAdapter
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Filter
import android.widget.Filterable
import kotlinx.android.synthetic.main.autocomplete_item_layout.view.*
private const val EXACT_MATCH = 1
private const val STARTS_WITH_AND_CONTAINED_ONCE = 2
private const val STARTS_WITH_AND_CONTAINED_MORE_THAN_ONCE = 3
private const val SEARCH_TERM_IS_CONTAINED_MORE_THAN_ONCE = 4
private const val SEARCH_TERM_IS_CONTAINED_ONCE = 5
class AutoCompleteAdapter (private val locations: MutableList<String>, private val filteredlistener: FilteredResultsListener, private val suggestedLocationClickListener : (AutoCompleteLocationSuggestion) -> Unit) :
ListAdapter<String, AutoCompleteAdapter.AutoCompleteViewHolder>(SuggestedLocationDiffCallback()), Filterable {
var initalFilteredLocation = ArrayList<String>()
var filteredLocationSuggestions = ArrayList<String>()
private var currentSearchTerm = ""
override fun getFilter(): Filter = autoSuggestionFilter
private val locationComparatorForSorting = Comparator {
s1: String, s2: String -> calculateRank(s1, currentSearchTerm) - calculateRank(s2, currentSearchTerm)
}
private fun calculateRank(value: String, searchTerm: String): Int {
val cleanedValue = value.trim().toLowerCase()
val startsWithSearchTerm = cleanedValue.startsWith(searchTerm)
val searchTermCount = cleanedValue.countSubString(searchTerm)
// rule 1
if (searchTerm == cleanedValue) {
return EXACT_MATCH
}
// rule 2
if (startsWithSearchTerm && searchTermCount == 1) {
return STARTS_WITH_AND_CONTAINED_ONCE
}
// rule 3
if (startsWithSearchTerm && searchTermCount > 1) {
return STARTS_WITH_AND_CONTAINED_MORE_THAN_ONCE
}
// rule 4
if (searchTermCount > 1) {
return SEARCH_TERM_IS_CONTAINED_MORE_THAN_ONCE
}
// rule 5
return SEARCH_TERM_IS_CONTAINED_ONCE
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AutoCompleteViewHolder
= AutoCompleteViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.autocomplete_item_layout, parent, false))
override fun onBindViewHolder(holder: AutoCompleteViewHolder, position: Int) {
val location = getItem(position)
holder.bind(location, suggestedLocationClickListener)
}
private fun handleRecyclerViewVisibility() {
filteredlistener.hasFilteredResults(filteredLocationSuggestions.isNotEmpty())
}
fun addSuggestedLocation(suggestion: AutoCompleteLocationSuggestion) {
val newList = ArrayList<String>()
newList.addAll(filteredLocationSuggestions)
newList.add(suggestion.position, suggestion.location)
submitList(newList)
filteredLocationSuggestions = newList
}
fun removeSuggestedLocation(suggestion: AutoCompleteLocationSuggestion) {
val newList = ArrayList<String>()
newList.addAll(filteredLocationSuggestions)
newList.removeAt(suggestion.position)
submitList(newList)
filteredLocationSuggestions = newList
}
fun clearSuggestedLocations() {
filteredLocationSuggestions.clear()
val newList = ArrayList<String>()
submitList(newList)
}
fun addAll(selectedItems : ArrayList<AutoCompleteLocationSuggestion>) {
val newList = ArrayList<String>()
newList.addAll(filteredLocationSuggestions)
selectedItems.forEach {
newList.add(it.position, it.location)
}
submitList(newList)
}
class AutoCompleteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(locationName: String, clickListener: (AutoCompleteLocationSuggestion) -> Unit) {
itemView.auto_complete_suggestion_text.text = locationName
if (itemView.location_check_box.isChecked) {
itemView.location_check_box.isChecked = false
}
itemView.setOnClickListener {
itemView.location_check_box.isChecked = true
Handler().postDelayed({
clickListener(AutoCompleteLocationSuggestion(adapterPosition, locationName))
}, 200)
}
}
}
private var autoSuggestionFilter: Filter = object : Filter() {
override fun performFiltering(constraint: CharSequence?): FilterResults {
val searchConstraint = cleanSearchTerm(constraint)
currentSearchTerm = searchConstraint
val filteredList = locations.filter { it.toLowerCase().contains(searchConstraint) }
val filterResults = FilterResults()
filterResults.values = filteredList
filterResults.count = filteredList.size
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: Filter.FilterResults) {
if (results.count == 0) {
return
}
filteredLocationSuggestions = results.values as ArrayList<String>
initalFilteredLocation = filteredLocationSuggestions
filteredLocationSuggestions.sortWith(locationComparatorForSorting)
submitList(filteredLocationSuggestions)
handleRecyclerViewVisibility()
}
}
private fun cleanSearchTerm(constraint: CharSequence?): String = constraint.toString().trim().toLowerCase()
}
还有我的 SuggestedLocationDiffCallback() :
import android.support.v7.util.DiffUtil
class SuggestedLocationDiffCallback : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String?, newItem: String?): Boolean = oldItem == newItem
override fun areContentsTheSame(oldItem: String?, newItem: String?): Boolean = oldItem == newItem
}
所以这很好用,过滤显示很好,如果我删除或添加回一个项目。如果列表很大(350 多个项目),则会出现问题。我读过最好在后台线程上进行处理的文档,所以我这样做了。我将 diff util 调用改回:
import android.support.v7.util.DiffUtil
class SuggestedDiffCallback(private val newList: MutableList<String>, private val oldList: MutableList<String>): DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldList[oldItemPosition] == newList[newItemPosition]
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = oldList[oldItemPosition] == newList[newItemPosition]
}
更新了我的适配器以扩展RecyclerView.Adapter
并在后台线程上运行,例如
if (handler == null) handler = Handler()
Thread(Runnable {
val diffResult = DiffUtil.calculateDiff(callback)
handler!!.post(Runnable {
diffResult.dispatchUpdatesTo(this@AutoCompleteAdapter)
})
}).start()
不幸的是,这没有用,所以我尝试使用协程(我几乎没有或没有经验,所以我不能确定它们是否有效)
private suspend fun update(callback: SuggestedDiffCallback) {
withContext(Dispatchers.Default) {
val diffResult = DiffUtil.calculateDiff(callback)
diffResult.dispatchUpdatesTo(this@AutoCompleteAdapter1)
}
handleRecyclerViewVisibility()
}
像这样调用上面的代码: CoroutineScope(Dispatchers.Default).launch {update(callback)}
然而,这也不起作用,UI 阻塞仍在发生。所以我将适配器恢复为扩展 aListAdapter
并使用AsyncListDiffer
初始化如下val differ = AsyncListDiffer(this, SuggestedLocationDiffCallback())
。并像使用它一样differ.submitList(newList)
不幸的是,这也不起作用,UI 阻塞仍在继续。我开始寻找分页的道路,我希望这可以解决问题,但是任何人都可以看看上面的内容,看看我是否做错了什么?
这里也是封装视图类:
import android.arch.paging.PagedList
import android.arch.paging.PagedListAdapter
import android.content.Context
import android.support.constraint.ConstraintLayout
import android.support.v4.widget.NestedScrollView
import android.util.AttributeSet
import android.view.inputmethod.InputMethodManager
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.auto_complete_component_layout.view.*
import java.util.*
private const val MAX_TAGS = 10
private const val FILTER_CHAR_COUNT = 3
class AutocompleteLocationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
defStyleAttr: Int = 0): ConstraintLayout (context, attrs, defStyleAttr), SuggestedLocationUpdateListener, FilteredResultsListener {
private var maxLocationsAddedListener: MaxLocationsAddedListener? = null
private var adapter: AutoCompleteAdapter1? = null
private var pagedListAdapter: LocationPagedListAdapter? = null
private val selectedItems : ArrayList<AutoCompleteLocationSuggestion> = ArrayList()
private var applyButtonVisibilityListener: ApplyButtonVisibilityListener? = null
private var suggestionListVisibleCallback: SuggestionListVisibleCallback? = null
private val disposables = CompositeDisposable()
init {
inflate(context, R.layout.auto_complete_component_layout, this)
observeClearSearchTerm()
}
fun setLocations(locations: MutableList<String>) {
pagedListAdapter = LocationPagedListAdapter(locations, SuggestedLocationDiffCallback(), this, this::suggestedLocationClicked)
//adapter = AutoCompleteAdapter(locations, this, this::suggestedLocationClicked)
location_suggestion_list.adapter = pagedListAdapter
location_search_box.onChange { handleSearchTerm(it)}
scrollview.setOnScrollChangeListener(onScrollChangeListener)
}
private fun handleSearchTerm(searchTerm: String) {
if(searchTerm.length >= FILTER_CHAR_COUNT) {
pagedListAdapter?.filter?.filter(searchTerm)
} else {
location_suggestion_list.visible(false)
suggestionListVisibleCallback?.areSuggestionsVisible(false)
adapter?.clearSuggestedLocations()
}
}
private fun observeClearSearchTerm() {
disposables + clear_search_term
.throttleClicks()
.subscribe {
handleClearLocationSearchBox()
}
}
private fun handleClearLocationSearchBox() {
location_search_box.text.clear()
if (!selectedItems.isNullOrEmpty()) {
location_search_box.hint = context.getString(R.string.add_another_location)
}
}
fun setSuggestionListVisibleCallback(suggestionListVisibleCallback: SuggestionListVisibleCallback) {
this.suggestionListVisibleCallback = suggestionListVisibleCallback
}
fun clearAreas() {
adapter?.addAll(selectedItems)
selectedItems.clear()
selected_location_container.removeAllViews()
}
fun setMaxLocationsAddedListener(maxLocationsAddedListener: MaxLocationsAddedListener) {
this.maxLocationsAddedListener = maxLocationsAddedListener
}
fun setApplyButtonListener(applyButtonVisibilityListener: ApplyButtonVisibilityListener) {
this.applyButtonVisibilityListener = applyButtonVisibilityListener
}
private fun suggestedLocationClicked(suggestedLocation: AutoCompleteLocationSuggestion) {
val selectedItemView = SelectedSuggestionView(context, null, 0, this)
selectedItemView.setAutoCompleteSuggestion(suggestedLocation)
if (selected_location_container.childCount == MAX_TAGS) {
maxLocationsAddedListener?.maxLocationsAdded()
} else {
selected_location_container.addView(selectedItemView, 0)
selectedItems.add(suggestedLocation)
adapter?.removeSuggestedLocation(suggestedLocation)
applyButtonVisibilityListener?.isApplyButtonVisible(selectedItems.isNotEmpty())
hideKeyboard()
}
}
private fun updateFilteredResults() {
selectedItems.forEach {
if (adapter?.filteredLocationSuggestions?.contains(it.location)!!) {
adapter?.filteredLocationSuggestions?.remove(it.location)
}
}
}
fun submitList(suggestedLocations: PagedList<String>?) {
pagedListAdapter?.submitList(suggestedLocations)
}
fun getSelectedItems() : ArrayList<String> = selectedItems.map { it.location } as ArrayList<String>
override fun hasFilteredResults(results: Boolean) {
location_suggestion_list.visible(results)
suggestionListVisibleCallback?.areSuggestionsVisible(results)
}
override fun selectedLocationDeselected(suggestedLocation: AutoCompleteLocationSuggestion) {
if (adapter?.filteredLocationSuggestions?.isNotEmpty()!!) {
selectedItems.remove(suggestedLocation)
adapter?.addSuggestedLocation(suggestedLocation)
applyButtonVisibilityListener?.isApplyButtonVisible(selectedItems.isNotEmpty())
}
}
fun addSelectedItemsToCloud(suggestedLocations: ArrayList<String>) {
suggestedLocations.forEach {
val selectedItemView = SelectedSuggestionView(context, null, 0, this)
selectedItemView.setAutoCompleteSuggestionText(it)
selected_location_container.addView(selectedItemView)
}
}
interface MaxLocationsAddedListener {
fun maxLocationsAdded()
}
private var onScrollChangeListener: NestedScrollView.OnScrollChangeListener = NestedScrollView.OnScrollChangeListener { _, _, _, _, _ -> hideKeyboard() }
private fun hideKeyboard() {
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(windowToken, 0)
}
}
当我把头发拉出来时,任何帮助都会受到赞赏。
编辑
因此,经过更多调查,似乎不是导致问题的 diffcallback 。我在绑定方法中添加了一个日志,它被称为x
次数,从最后一个到显示在屏幕上有很长的延迟,bind
并且在此期间ui被阻塞。