我使用的AutoCompleteTextView有点像这样:
public class MainActivity extends Activity {
private AutoCompleteTextView actv;
private MultiAutoCompleteTextView mactv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[] countries = getResources().
getStringArray(R.array.list_of_countries);
ArrayAdapter adapter = new ArrayAdapter
(this,android.R.layout.simple_list_item_1,countries);
actv = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
mactv = (MultiAutoCompleteTextView) findViewById
(R.id.multiAutoCompleteTextView1);
actv.setAdapter(adapter);
mactv.setAdapter(adapter);
mactv.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
这完成了大部分工作。但是,在我的情况下,我需要自动完成,以在返回的下拉菜单建议的底部显示类似“自定义...”的内容。
所以,如果有自动填充建议,他们会显示,然后是“自定义...”建议。如果没有任何建议,它仍然会显示一个建议'自定义......'。我还需要一个单击“自定义...”的侦听器。
答案 0 :(得分:6)
有趣的问题,我试了一下,并基于ArrayAdapter source code实现了一个简单的自定义适配器。
为简洁起见,我省略了大部分未使用的代码和注释,因此如果您不确定 - 请查看我上面链接的ArrayAdapter的源代码,可以很好地评论。
操作原理非常简单,适配器的getCount()
将一个元素添加到实际数量。此外,getItem(int position)
将检查最后一个,"虚拟"请求该项目,并将返回您的"自定义..."字符串然后。
createViewFromResource(...)
方法还会检查它是否会显示最后一个"虚拟" item,如果是,它将绑定一个onClick监听器。
覆盖的过滤器还会在结果计数中添加一个,以使AutoCompleteView相信存在匹配,从而使下拉列表保持打开状态。
MainActivity.java
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[] countries = new String[]{
"Switzerland", "Mexico", "Poland", "United States of Murica"};
// the footer item's text
String footerText = "Custom Footer....";
// our custom adapter with the custom footer text as last parameter
CustomAutoCompleteAdapter adapter = new CustomAutoCompleteAdapter(
this, android.R.layout.simple_list_item_1, countries, footerText);
// bind to our custom click listener interface
adapter.setOnFooterClickListener(new CustomAutoCompleteAdapter.OnFooterClickListener() {
@Override
public void onFooterClicked() {
// your custom item has been clicked, make some toast
Toast toast = Toast.makeText(getApplicationContext(), "Yummy Toast!", Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
}
});
// find auto complete text view
AutoCompleteTextView actv = (AutoCompleteTextView) findViewById(R.id.autoCompleteTextView1);
actv.setThreshold(0);
actv.setAdapter(adapter);
}
}
CustomAutoCompleteAdapter.java
public class CustomAutoCompleteAdapter extends BaseAdapter implements Filterable {
public interface OnFooterClickListener {
public void onFooterClicked();
}
private List<String> mObjects;
private final Object mLock = new Object();
private int mResource;
private int mDropDownResource;
private ArrayList<String> mOriginalValues;
private ArrayFilter mFilter;
private LayoutInflater mInflater;
// the last item, i.e the footer
private String mFooterText;
// our listener
private OnFooterClickListener mListener;
public CustomAutoCompleteAdapter(Context context, int resource, String[] objects, String footerText) {
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mResource = mDropDownResource = resource;
mObjects = Arrays.asList(objects);
mFooterText = footerText;
}
/**
* Set listener for clicks on the footer item
*/
public void setOnFooterClickListener(OnFooterClickListener listener) {
mListener = listener;
}
@Override
public int getCount() {
return mObjects.size()+1;
}
@Override
public String getItem(int position) {
if(position == (getCount()-1)) {
// last item is always the footer text
return mFooterText;
}
// return real text
return mObjects.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mResource);
}
private View createViewFromResource(int position, View convertView, ViewGroup parent,
int resource) {
View view;
TextView text;
if (convertView == null) {
view = mInflater.inflate(resource, parent, false);
} else {
view = convertView;
}
try {
// If no custom field is assigned, assume the whole resource is a TextView
text = (TextView) view;
} catch (ClassCastException e) {
Log.e("CustomAutoCompleteAdapter", "Layout XML file is not a text field");
throw new IllegalStateException("Layout XML file is not a text field", e);
}
text.setText(getItem(position));
if(position == (getCount()-1)) {
// it's the last item, bind click listener
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mListener != null) {
mListener.onFooterClicked();
}
}
});
} else {
// it's a real item, set click listener to null and reset to original state
view.setOnClickListener(null);
view.setClickable(false);
}
return view;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return createViewFromResource(position, convertView, parent, mDropDownResource);
}
@Override
public Filter getFilter() {
if (mFilter == null) {
mFilter = new ArrayFilter();
}
return mFilter;
}
/**
* <p>An array filter constrains the content of the array adapter with
* a prefix. Each item that does not start with the supplied prefix
* is removed from the list.</p>
*/
private class ArrayFilter extends Filter {
@Override
protected FilterResults performFiltering(CharSequence prefix) {
FilterResults results = new FilterResults();
if (mOriginalValues == null) {
synchronized (mLock) {
mOriginalValues = new ArrayList<String>(mObjects);
}
}
if (prefix == null || prefix.length() == 0) {
ArrayList<String> list;
synchronized (mLock) {
list = new ArrayList<String>(mOriginalValues);
}
results.values = list;
// add +1 since we have a footer item which is always visible
results.count = list.size()+1;
} else {
String prefixString = prefix.toString().toLowerCase();
ArrayList<String> values;
synchronized (mLock) {
values = new ArrayList<String>(mOriginalValues);
}
final int count = values.size();
final ArrayList<String> newValues = new ArrayList<String>();
for (int i = 0; i < count; i++) {
final String value = values.get(i);
final String valueText = value.toString().toLowerCase();
// First match against the whole, non-splitted value
if (valueText.startsWith(prefixString)) {
newValues.add(value);
} else {
final String[] words = valueText.split(" ");
final int wordCount = words.length;
// Start at index 0, in case valueText starts with space(s)
for (int k = 0; k < wordCount; k++) {
if (words[k].startsWith(prefixString)) {
newValues.add(value);
break;
}
}
}
}
results.values = newValues;
// add one since we always show the footer
results.count = newValues.size()+1;
}
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
//noinspection unchecked
mObjects = (List<String>) results.values;
notifyDataSetChanged();
}
}
}
布局/ activity_main.xml中
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
tools:context=".MainActivity">
<AutoCompleteTextView
android:id="@+id/autoCompleteTextView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
答案 1 :(得分:0)
谢谢@sled,你的回答很好。我已经通过一些修改将其转换为 kotlin。
class CustomAutoCompleteAdapter<T>(
var context: Context,
private var resource: Int,
var objects: ArrayList<T>,
private var footerText: String
): BaseAdapter(), Filterable {
private var footerClickListener: OnFooterClickListener? = null
private var filter = ArrayFilter()
interface OnFooterClickListener {
fun onFooterClick()
}
fun setOnFooterClickListener(listener: OnFooterClickListener) {
footerClickListener = listener
}
override fun getCount() = objects.size + 1
override fun getItem(position: Int) = if(position <= objects.size - 1) objects[position].toString() else footerText
override fun getItemId(position: Int) = position.toLong()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
return createViewFromSource(position, convertView, parent, resource)
}
private fun createViewFromSource(
position: Int,
convertView: View?,
parent: ViewGroup?,
resource: Int
): View {
val text: TextView
val view = convertView ?: LayoutInflater.from(parent?.context).inflate(resource, parent, false)
try {
text = view as TextView
} catch (ex: ClassCastException) {
throw IllegalStateException("Layout xml is not text field", ex)
}
text.text = getItem(position)
if(position == count - 1) {
view.setOnClickListener {
footerClickListener?.onFooterClick()
}
} else {
view.setOnClickListener(null)
view.isClickable = false
}
return view
}
override fun getFilter(): Filter {
return filter
}
override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View {
return createViewFromSource(position, convertView, parent, resource)
}
inner class ArrayFilter: Filter() {
private var originalValues = ArrayList<T>()
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
if(originalValues.isEmpty())
originalValues.addAll(objects)
if(constraint == null || constraint.isEmpty()) {
val outcomes = ArrayList<T>(originalValues)
filterResults.values = outcomes
filterResults.count = outcomes.size + 1
} else {
val prefixStr = constraint.toString().toLowerCase(Locale.ENGLISH)
val values = ArrayList<T>(originalValues)
val newValues = ArrayList<T>()
for(value in values) {
val valueText = value.toString().toLowerCase(Locale.ENGLISH)
if(valueText.contains(prefixStr)) {
newValues.add(value)
} else {
val words = valueText.split(" ", "(", ")", ",", "-")
for(word in words) {
if(word.startsWith(prefixStr)) {
newValues.add(value)
break
}
}
}
}
filterResults.values = newValues
filterResults.count = newValues.size + 1
}
return filterResults
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
@Suppress("UNCHECKED_CAST")
objects = results?.values as ArrayList<T>
notifyDataSetChanged()
}
}
}