Skip to content

Instantly share code, notes, and snippets.

@tcw165
Created January 13, 2021 09:20
Show Gist options
  • Save tcw165/970e511231b041ba0b9c06e499ebbfe7 to your computer and use it in GitHub Desktop.
Save tcw165/970e511231b041ba0b9c06e499ebbfe7 to your computer and use it in GitHub Desktop.
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Filter
import androidx.annotation.LayoutRes
import com.fitbit.coin.kit.internal.service.FuzzySearchArrayAdapter.FuzzySearchFilter
import java.util.concurrent.CopyOnWriteArrayList
/**
* Customized [ArrayAdapter] for searching the [T] in the UI.
*
* TODO: Doesn't fully support the lexicographic distance algorithm, [FuzzySearchFilter].
*
* @param itemBinder Like RecyclerView.ViewHolder, you'll provide the hook to bind
* the item view with the data.
* @param filterMatcher The matcher tell if the text matches the item, [T].
*/
internal class FuzzySearchArrayAdapter<T>(
context: Context,
@LayoutRes
private val dropdownResource: Int,
private val fullList: List<T>,
private val itemBinder: (itemView: View, item: T, searchKeywords: String?) -> Unit,
private val itemCompletionConverter: (item: T) -> String,
private val filterMatcher: (searchKeywords: CharSequence, item: T) -> Boolean
) : ArrayAdapter<T>(
context,
dropdownResource,
// Clone the entire list so the original one don't get changed.
// Note: A lot of variables for retaining the original list and
// search list are private.
ArrayList(fullList)
) {
/**
* The list of the search result.
*
* Note: This is a duplicate list to superclass's list due to superclass
* make the list private. Due to the lack of efficient access, we need to
* create our own and also override some functions to make the auto-completion
* work.
*
* @see [getCount] Overridden for the fuzzy search.
* @see [getItem] Overridden for the fuzzy search.
* @see [getPosition] Overridden for the fuzzy search.
*/
private val searchList = CopyOnWriteArrayList<T>(fullList)
private val searchKeywords = StringBuilder()
override fun getFilter(): Filter {
return FuzzySearchFilter()
}
override fun getCount(): Int = searchList.size
override fun getItem(position: Int): T? {
return if (position in 0 until searchList.size) {
searchList[position]
} else {
null
}
}
override fun getPosition(
item: T?
): Int {
return item?.let {
searchList.indexOf(it)
} ?: -1
}
/**
* Determine the item rendering.
*/
override fun getView(
position: Int,
convertView: View?,
parent: ViewGroup
): View {
val layout = convertView ?: kotlin.run {
val inflater = LayoutInflater.from(context)
inflater.inflate(dropdownResource, parent, false)
}
getItem(position)?.let { item ->
val highlightRange = if (searchKeywords.isNotEmpty()) {
searchKeywords.toString()
} else {
null
}
itemBinder(layout, item, highlightRange)
}
return layout
}
/**
* The filter that match the text and the data with flexible way.
*/
private inner class FuzzySearchFilter : Filter() {
override fun performFiltering(
prefix: CharSequence?
): FilterResults {
val results = FilterResults()
val matchedList = ArrayList<T>(fullList.size)
// Clear old keywords.
searchKeywords.setLength(0)
prefix?.let { substring ->
// Cache new keywords.
searchKeywords.append(substring)
for (item in fullList) {
if (filterMatcher(substring, item)) {
matchedList.add(item)
}
}
}
if (matchedList.isEmpty()) {
matchedList.addAll(fullList)
}
results.values = matchedList
results.count = matchedList.size
return results
}
@Suppress("UNCHECKED_CAST")
override fun publishResults(
constraint: CharSequence?,
results: FilterResults?
) {
results?.let {
val resultList = it.values as List<T>
searchList.clear()
searchList.addAll(resultList)
notifyDataSetChanged()
} ?: kotlin.run {
notifyDataSetInvalidated()
}
}
@Suppress("UNCHECKED_CAST")
override fun convertResultToString(
resultValue: Any?
): CharSequence {
return resultValue?.let {
val item = it as T
itemCompletionConverter(item)
} ?: ""
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment