Created
January 13, 2021 09:20
-
-
Save tcw165/970e511231b041ba0b9c06e499ebbfe7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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