2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.widget;
19 import android.content.Context;
20 import android.util.Log;
21 import android.view.LayoutInflater;
22 import android.view.View;
23 import android.view.ViewGroup;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28 import java.util.Comparator;
29 import java.util.Collections;
32 * A ListAdapter that manages a ListView backed by an array of arbitrary
33 * objects. By default this class expects that the provided resource id references
34 * a single TextView. If you want to use a more complex layout, use the constructors that
35 * also takes a field id. That field id should reference a TextView in the larger layout
38 * However the TextView is referenced, it will be filled with the toString() of each object in
39 * the array. You can add lists or arrays of custom objects. Override the toString() method
40 * of your objects to determine what text will be displayed for the item in the list.
42 * To use something other than TextViews for the array display, for instance, ImageViews,
43 * or to have some of data besides toString() results fill the views,
44 * override {@link #getView(int, View, ViewGroup)} to return the type of view you want.
46 public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
48 * Contains the list of objects that represent the data of this ArrayAdapter.
49 * The content of this list is referred to as "the array" in the documentation.
51 private List<T> mObjects;
54 * Lock used to modify the content of {@link #mObjects}. Any write operation
55 * performed on the array should be synchronized on this lock. This lock is also
56 * used by the filter (see {@link #getFilter()} to make a synchronized copy of
57 * the original array of data.
59 private final Object mLock = new Object();
62 * The resource indicating what views to inflate to display the content of this
65 private int mResource;
68 * The resource indicating what views to inflate to display the content of this
69 * array adapter in a drop down widget.
71 private int mDropDownResource;
74 * If the inflated resource is not a TextView, {@link #mFieldId} is used to find
75 * a TextView inside the inflated views hierarchy. This field must contain the
76 * identifier that matches the one defined in the resource file.
78 private int mFieldId = 0;
81 * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever
82 * {@link #mObjects} is modified.
84 private boolean mNotifyOnChange = true;
86 private Context mContext;
88 private ArrayList<T> mOriginalValues;
89 private ArrayFilter mFilter;
91 private LayoutInflater mInflater;
96 * @param context The current context.
97 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
98 * instantiating views.
100 public ArrayAdapter(Context context, int textViewResourceId) {
101 init(context, textViewResourceId, 0, new ArrayList<T>());
107 * @param context The current context.
108 * @param resource The resource ID for a layout file containing a layout to use when
109 * instantiating views.
110 * @param textViewResourceId The id of the TextView within the layout resource to be populated
112 public ArrayAdapter(Context context, int resource, int textViewResourceId) {
113 init(context, resource, textViewResourceId, new ArrayList<T>());
119 * @param context The current context.
120 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
121 * instantiating views.
122 * @param objects The objects to represent in the ListView.
124 public ArrayAdapter(Context context, int textViewResourceId, T[] objects) {
125 init(context, textViewResourceId, 0, Arrays.asList(objects));
131 * @param context The current context.
132 * @param resource The resource ID for a layout file containing a layout to use when
133 * instantiating views.
134 * @param textViewResourceId The id of the TextView within the layout resource to be populated
135 * @param objects The objects to represent in the ListView.
137 public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
138 init(context, resource, textViewResourceId, Arrays.asList(objects));
144 * @param context The current context.
145 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when
146 * instantiating views.
147 * @param objects The objects to represent in the ListView.
149 public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
150 init(context, textViewResourceId, 0, objects);
156 * @param context The current context.
157 * @param resource The resource ID for a layout file containing a layout to use when
158 * instantiating views.
159 * @param textViewResourceId The id of the TextView within the layout resource to be populated
160 * @param objects The objects to represent in the ListView.
162 public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
163 init(context, resource, textViewResourceId, objects);
167 * Adds the specified object at the end of the array.
169 * @param object The object to add at the end of the array.
171 public void add(T object) {
172 if (mOriginalValues != null) {
173 synchronized (mLock) {
174 mOriginalValues.add(object);
175 if (mNotifyOnChange) notifyDataSetChanged();
178 mObjects.add(object);
179 if (mNotifyOnChange) notifyDataSetChanged();
184 * Inserts the specified object at the specified index in the array.
186 * @param object The object to insert into the array.
187 * @param index The index at which the object must be inserted.
189 public void insert(T object, int index) {
190 if (mOriginalValues != null) {
191 synchronized (mLock) {
192 mOriginalValues.add(index, object);
193 if (mNotifyOnChange) notifyDataSetChanged();
196 mObjects.add(index, object);
197 if (mNotifyOnChange) notifyDataSetChanged();
202 * Removes the specified object from the array.
204 * @param object The object to remove.
206 public void remove(T object) {
207 if (mOriginalValues != null) {
208 synchronized (mLock) {
209 mOriginalValues.remove(object);
212 mObjects.remove(object);
214 if (mNotifyOnChange) notifyDataSetChanged();
218 * Remove all elements from the list.
220 public void clear() {
221 if (mOriginalValues != null) {
222 synchronized (mLock) {
223 mOriginalValues.clear();
228 if (mNotifyOnChange) notifyDataSetChanged();
232 * Sorts the content of this adapter using the specified comparator.
234 * @param comparator The comparator used to sort the objects contained
237 public void sort(Comparator<? super T> comparator) {
238 Collections.sort(mObjects, comparator);
239 if (mNotifyOnChange) notifyDataSetChanged();
246 public void notifyDataSetChanged() {
247 super.notifyDataSetChanged();
248 mNotifyOnChange = true;
252 * Control whether methods that change the list ({@link #add},
253 * {@link #insert}, {@link #remove}, {@link #clear}) automatically call
254 * {@link #notifyDataSetChanged}. If set to false, caller must
255 * manually call notifyDataSetChanged() to have the changes
256 * reflected in the attached view.
258 * The default is true, and calling notifyDataSetChanged()
259 * resets the flag to true.
261 * @param notifyOnChange if true, modifications to the list will
262 * automatically call {@link
263 * #notifyDataSetChanged}
265 public void setNotifyOnChange(boolean notifyOnChange) {
266 mNotifyOnChange = notifyOnChange;
269 private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
271 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
272 mResource = mDropDownResource = resource;
274 mFieldId = textViewResourceId;
278 * Returns the context associated with this array adapter. The context is used
279 * to create views from the resource passed to the constructor.
281 * @return The Context associated with this adapter.
283 public Context getContext() {
290 public int getCount() {
291 return mObjects.size();
297 public T getItem(int position) {
298 return mObjects.get(position);
302 * Returns the position of the specified item in the array.
304 * @param item The item to retrieve the position of.
306 * @return The position of the specified item.
308 public int getPosition(T item) {
309 return mObjects.indexOf(item);
315 public long getItemId(int position) {
322 public View getView(int position, View convertView, ViewGroup parent) {
323 return createViewFromResource(position, convertView, parent, mResource);
326 private View createViewFromResource(int position, View convertView, ViewGroup parent,
331 if (convertView == null) {
332 view = mInflater.inflate(resource, parent, false);
339 // If no custom field is assigned, assume the whole resource is a TextView
340 text = (TextView) view;
342 // Otherwise, find the TextView field within the layout
343 text = (TextView) view.findViewById(mFieldId);
345 } catch (ClassCastException e) {
346 Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
347 throw new IllegalStateException(
348 "ArrayAdapter requires the resource ID to be a TextView", e);
351 T item = getItem(position);
352 if (item instanceof CharSequence) {
353 text.setText((CharSequence)item);
355 text.setText(item.toString());
362 * <p>Sets the layout resource to create the drop down views.</p>
364 * @param resource the layout resource defining the drop down views
365 * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
367 public void setDropDownViewResource(int resource) {
368 this.mDropDownResource = resource;
375 public View getDropDownView(int position, View convertView, ViewGroup parent) {
376 return createViewFromResource(position, convertView, parent, mDropDownResource);
380 * Creates a new ArrayAdapter from external resources. The content of the array is
381 * obtained through {@link android.content.res.Resources#getTextArray(int)}.
383 * @param context The application's environment.
384 * @param textArrayResId The identifier of the array to use as the data source.
385 * @param textViewResId The identifier of the layout used to create views.
387 * @return An ArrayAdapter<CharSequence>.
389 public static ArrayAdapter<CharSequence> createFromResource(Context context,
390 int textArrayResId, int textViewResId) {
391 CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
392 return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
398 public Filter getFilter() {
399 if (mFilter == null) {
400 mFilter = new ArrayFilter();
406 * <p>An array filter constrains the content of the array adapter with
407 * a prefix. Each item that does not start with the supplied prefix
408 * is removed from the list.</p>
410 private class ArrayFilter extends Filter {
412 protected FilterResults performFiltering(CharSequence prefix) {
413 FilterResults results = new FilterResults();
415 if (mOriginalValues == null) {
416 synchronized (mLock) {
417 mOriginalValues = new ArrayList<T>(mObjects);
421 if (prefix == null || prefix.length() == 0) {
422 synchronized (mLock) {
423 ArrayList<T> list = new ArrayList<T>(mOriginalValues);
424 results.values = list;
425 results.count = list.size();
428 String prefixString = prefix.toString().toLowerCase();
430 final ArrayList<T> values = mOriginalValues;
431 final int count = values.size();
433 final ArrayList<T> newValues = new ArrayList<T>(count);
435 for (int i = 0; i < count; i++) {
436 final T value = values.get(i);
437 final String valueText = value.toString().toLowerCase();
439 // First match against the whole, non-splitted value
440 if (valueText.startsWith(prefixString)) {
441 newValues.add(value);
443 final String[] words = valueText.split(" ");
444 final int wordCount = words.length;
446 for (int k = 0; k < wordCount; k++) {
447 if (words[k].startsWith(prefixString)) {
448 newValues.add(value);
455 results.values = newValues;
456 results.count = newValues.size();
463 protected void publishResults(CharSequence constraint, FilterResults results) {
464 //noinspection unchecked
465 mObjects = (List<T>) results.values;
466 if (results.count > 0) {
467 notifyDataSetChanged();
469 notifyDataSetInvalidated();