blob: 97926a73cb18827b53f681f6d15ea9972f338731 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package android.widget;
18
19import android.content.Context;
20import android.util.Log;
21import android.view.LayoutInflater;
22import android.view.View;
23import android.view.ViewGroup;
24
25import java.util.ArrayList;
26import java.util.Arrays;
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +020027import java.util.Collection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070028import java.util.Collections;
Gilles Debunnebe2c4f92011-01-17 15:14:32 -080029import java.util.Comparator;
30import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031
32/**
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070033 * A concrete BaseAdapter that is backed by an array of arbitrary
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034 * objects. By default this class expects that the provided resource id references
35 * a single TextView. If you want to use a more complex layout, use the constructors that
36 * also takes a field id. That field id should reference a TextView in the larger layout
37 * resource.
38 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070039 * <p>However the TextView is referenced, it will be filled with the toString() of each object in
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040 * the array. You can add lists or arrays of custom objects. Override the toString() method
41 * of your objects to determine what text will be displayed for the item in the list.
42 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070043 * <p>To use something other than TextViews for the array display, for instance, ImageViews,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044 * or to have some of data besides toString() results fill the views,
45 * override {@link #getView(int, View, ViewGroup)} to return the type of view you want.
46 */
47public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
48 /**
49 * Contains the list of objects that represent the data of this ArrayAdapter.
50 * The content of this list is referred to as "the array" in the documentation.
51 */
52 private List<T> mObjects;
53
54 /**
55 * Lock used to modify the content of {@link #mObjects}. Any write operation
56 * performed on the array should be synchronized on this lock. This lock is also
57 * used by the filter (see {@link #getFilter()} to make a synchronized copy of
58 * the original array of data.
59 */
60 private final Object mLock = new Object();
61
62 /**
63 * The resource indicating what views to inflate to display the content of this
64 * array adapter.
65 */
66 private int mResource;
67
68 /**
69 * The resource indicating what views to inflate to display the content of this
70 * array adapter in a drop down widget.
71 */
72 private int mDropDownResource;
73
74 /**
75 * If the inflated resource is not a TextView, {@link #mFieldId} is used to find
76 * a TextView inside the inflated views hierarchy. This field must contain the
77 * identifier that matches the one defined in the resource file.
78 */
79 private int mFieldId = 0;
80
81 /**
82 * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever
83 * {@link #mObjects} is modified.
84 */
85 private boolean mNotifyOnChange = true;
86
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +020087 private Context mContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
Gilles Debunnebe2c4f92011-01-17 15:14:32 -080089 // A copy of the original mObjects array, initialized from and then used instead as soon as
90 // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 private ArrayList<T> mOriginalValues;
92 private ArrayFilter mFilter;
93
94 private LayoutInflater mInflater;
95
96 /**
97 * Constructor
98 *
99 * @param context The current context.
Romain Guy9c5d1b12013-01-09 20:16:37 -0800100 * @param resource The resource ID for a layout file containing a TextView to use when
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 * instantiating views.
102 */
Romain Guy9c5d1b12013-01-09 20:16:37 -0800103 public ArrayAdapter(Context context, int resource) {
104 init(context, resource, 0, new ArrayList<T>());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105 }
106
107 /**
108 * Constructor
109 *
110 * @param context The current context.
111 * @param resource The resource ID for a layout file containing a layout to use when
112 * instantiating views.
113 * @param textViewResourceId The id of the TextView within the layout resource to be populated
114 */
115 public ArrayAdapter(Context context, int resource, int textViewResourceId) {
116 init(context, resource, textViewResourceId, new ArrayList<T>());
117 }
118
119 /**
120 * Constructor
121 *
122 * @param context The current context.
Romain Guy9c5d1b12013-01-09 20:16:37 -0800123 * @param resource The resource ID for a layout file containing a TextView to use when
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 * instantiating views.
125 * @param objects The objects to represent in the ListView.
126 */
Romain Guy9c5d1b12013-01-09 20:16:37 -0800127 public ArrayAdapter(Context context, int resource, T[] objects) {
128 init(context, resource, 0, Arrays.asList(objects));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 }
130
131 /**
132 * Constructor
133 *
134 * @param context The current context.
135 * @param resource The resource ID for a layout file containing a layout to use when
136 * instantiating views.
137 * @param textViewResourceId The id of the TextView within the layout resource to be populated
138 * @param objects The objects to represent in the ListView.
139 */
140 public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
141 init(context, resource, textViewResourceId, Arrays.asList(objects));
142 }
143
144 /**
145 * Constructor
146 *
147 * @param context The current context.
Romain Guy9c5d1b12013-01-09 20:16:37 -0800148 * @param resource The resource ID for a layout file containing a TextView to use when
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800149 * instantiating views.
150 * @param objects The objects to represent in the ListView.
151 */
Romain Guy9c5d1b12013-01-09 20:16:37 -0800152 public ArrayAdapter(Context context, int resource, List<T> objects) {
153 init(context, resource, 0, objects);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 }
155
156 /**
157 * Constructor
158 *
159 * @param context The current context.
160 * @param resource The resource ID for a layout file containing a layout to use when
161 * instantiating views.
162 * @param textViewResourceId The id of the TextView within the layout resource to be populated
163 * @param objects The objects to represent in the ListView.
164 */
165 public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
166 init(context, resource, textViewResourceId, objects);
167 }
168
169 /**
170 * Adds the specified object at the end of the array.
171 *
172 * @param object The object to add at the end of the array.
173 */
174 public void add(T object) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800175 synchronized (mLock) {
176 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 mOriginalValues.add(object);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800178 } else {
179 mObjects.add(object);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800182 if (mNotifyOnChange) notifyDataSetChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 }
184
185 /**
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200186 * Adds the specified Collection at the end of the array.
187 *
188 * @param collection The Collection to add at the end of the array.
189 */
190 public void addAll(Collection<? extends T> collection) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800191 synchronized (mLock) {
192 if (mOriginalValues != null) {
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200193 mOriginalValues.addAll(collection);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800194 } else {
195 mObjects.addAll(collection);
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200196 }
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200197 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800198 if (mNotifyOnChange) notifyDataSetChanged();
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200199 }
200
201 /**
202 * Adds the specified items at the end of the array.
203 *
204 * @param items The items to add at the end of the array.
205 */
206 public void addAll(T ... items) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800207 synchronized (mLock) {
208 if (mOriginalValues != null) {
Romain Guy95a78c32011-08-15 15:36:30 -0700209 Collections.addAll(mOriginalValues, items);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800210 } else {
Romain Guy95a78c32011-08-15 15:36:30 -0700211 Collections.addAll(mObjects, items);
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200212 }
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200213 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800214 if (mNotifyOnChange) notifyDataSetChanged();
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200215 }
216
217 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 * Inserts the specified object at the specified index in the array.
219 *
220 * @param object The object to insert into the array.
221 * @param index The index at which the object must be inserted.
222 */
223 public void insert(T object, int index) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800224 synchronized (mLock) {
225 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800226 mOriginalValues.add(index, object);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800227 } else {
228 mObjects.add(index, object);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800229 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800231 if (mNotifyOnChange) notifyDataSetChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 }
233
234 /**
235 * Removes the specified object from the array.
236 *
237 * @param object The object to remove.
238 */
239 public void remove(T object) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800240 synchronized (mLock) {
241 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800242 mOriginalValues.remove(object);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800243 } else {
244 mObjects.remove(object);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800246 }
247 if (mNotifyOnChange) notifyDataSetChanged();
248 }
249
250 /**
251 * Remove all elements from the list.
252 */
253 public void clear() {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800254 synchronized (mLock) {
255 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 mOriginalValues.clear();
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800257 } else {
258 mObjects.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 }
261 if (mNotifyOnChange) notifyDataSetChanged();
262 }
263
264 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700265 * Sorts the content of this adapter using the specified comparator.
266 *
267 * @param comparator The comparator used to sort the objects contained
268 * in this adapter.
269 */
270 public void sort(Comparator<? super T> comparator) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800271 synchronized (mLock) {
272 if (mOriginalValues != null) {
273 Collections.sort(mOriginalValues, comparator);
274 } else {
275 Collections.sort(mObjects, comparator);
276 }
277 }
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200278 if (mNotifyOnChange) notifyDataSetChanged();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700279 }
280
281 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 * {@inheritDoc}
283 */
284 @Override
285 public void notifyDataSetChanged() {
286 super.notifyDataSetChanged();
287 mNotifyOnChange = true;
288 }
289
290 /**
291 * Control whether methods that change the list ({@link #add},
292 * {@link #insert}, {@link #remove}, {@link #clear}) automatically call
293 * {@link #notifyDataSetChanged}. If set to false, caller must
294 * manually call notifyDataSetChanged() to have the changes
295 * reflected in the attached view.
296 *
297 * The default is true, and calling notifyDataSetChanged()
298 * resets the flag to true.
299 *
300 * @param notifyOnChange if true, modifications to the list will
301 * automatically call {@link
302 * #notifyDataSetChanged}
303 */
304 public void setNotifyOnChange(boolean notifyOnChange) {
305 mNotifyOnChange = notifyOnChange;
306 }
307
308 private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
309 mContext = context;
310 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
311 mResource = mDropDownResource = resource;
312 mObjects = objects;
313 mFieldId = textViewResourceId;
314 }
315
316 /**
317 * Returns the context associated with this array adapter. The context is used
318 * to create views from the resource passed to the constructor.
319 *
320 * @return The Context associated with this adapter.
321 */
322 public Context getContext() {
323 return mContext;
324 }
325
326 /**
327 * {@inheritDoc}
328 */
329 public int getCount() {
330 return mObjects.size();
331 }
332
333 /**
334 * {@inheritDoc}
335 */
336 public T getItem(int position) {
337 return mObjects.get(position);
338 }
339
340 /**
341 * Returns the position of the specified item in the array.
342 *
343 * @param item The item to retrieve the position of.
344 *
345 * @return The position of the specified item.
346 */
347 public int getPosition(T item) {
348 return mObjects.indexOf(item);
349 }
350
351 /**
352 * {@inheritDoc}
353 */
354 public long getItemId(int position) {
355 return position;
356 }
357
358 /**
359 * {@inheritDoc}
360 */
361 public View getView(int position, View convertView, ViewGroup parent) {
362 return createViewFromResource(position, convertView, parent, mResource);
363 }
364
365 private View createViewFromResource(int position, View convertView, ViewGroup parent,
366 int resource) {
367 View view;
368 TextView text;
369
370 if (convertView == null) {
371 view = mInflater.inflate(resource, parent, false);
372 } else {
373 view = convertView;
374 }
375
376 try {
377 if (mFieldId == 0) {
378 // If no custom field is assigned, assume the whole resource is a TextView
379 text = (TextView) view;
380 } else {
381 // Otherwise, find the TextView field within the layout
382 text = (TextView) view.findViewById(mFieldId);
383 }
384 } catch (ClassCastException e) {
385 Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
386 throw new IllegalStateException(
387 "ArrayAdapter requires the resource ID to be a TextView", e);
388 }
389
Daisuke Miyakawab5d91322009-07-09 14:26:56 +0900390 T item = getItem(position);
391 if (item instanceof CharSequence) {
392 text.setText((CharSequence)item);
393 } else {
394 text.setText(item.toString());
395 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396
397 return view;
398 }
399
400 /**
401 * <p>Sets the layout resource to create the drop down views.</p>
402 *
403 * @param resource the layout resource defining the drop down views
404 * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
405 */
406 public void setDropDownViewResource(int resource) {
407 this.mDropDownResource = resource;
408 }
409
410 /**
411 * {@inheritDoc}
412 */
413 @Override
414 public View getDropDownView(int position, View convertView, ViewGroup parent) {
415 return createViewFromResource(position, convertView, parent, mDropDownResource);
416 }
417
418 /**
419 * Creates a new ArrayAdapter from external resources. The content of the array is
420 * obtained through {@link android.content.res.Resources#getTextArray(int)}.
421 *
422 * @param context The application's environment.
423 * @param textArrayResId The identifier of the array to use as the data source.
424 * @param textViewResId The identifier of the layout used to create views.
425 *
426 * @return An ArrayAdapter<CharSequence>.
427 */
428 public static ArrayAdapter<CharSequence> createFromResource(Context context,
429 int textArrayResId, int textViewResId) {
430 CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
431 return new ArrayAdapter<CharSequence>(context, textViewResId, strings);
432 }
433
434 /**
435 * {@inheritDoc}
436 */
437 public Filter getFilter() {
438 if (mFilter == null) {
439 mFilter = new ArrayFilter();
440 }
441 return mFilter;
442 }
443
444 /**
445 * <p>An array filter constrains the content of the array adapter with
446 * a prefix. Each item that does not start with the supplied prefix
447 * is removed from the list.</p>
448 */
449 private class ArrayFilter extends Filter {
450 @Override
451 protected FilterResults performFiltering(CharSequence prefix) {
452 FilterResults results = new FilterResults();
453
454 if (mOriginalValues == null) {
455 synchronized (mLock) {
456 mOriginalValues = new ArrayList<T>(mObjects);
457 }
458 }
459
460 if (prefix == null || prefix.length() == 0) {
Romain Guy95a78c32011-08-15 15:36:30 -0700461 ArrayList<T> list;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 synchronized (mLock) {
Romain Guy95a78c32011-08-15 15:36:30 -0700463 list = new ArrayList<T>(mOriginalValues);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 }
Romain Guy95a78c32011-08-15 15:36:30 -0700465 results.values = list;
466 results.count = list.size();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 } else {
468 String prefixString = prefix.toString().toLowerCase();
469
Romain Guy95a78c32011-08-15 15:36:30 -0700470 ArrayList<T> values;
471 synchronized (mLock) {
472 values = new ArrayList<T>(mOriginalValues);
473 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474
Romain Guy95a78c32011-08-15 15:36:30 -0700475 final int count = values.size();
476 final ArrayList<T> newValues = new ArrayList<T>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800477
478 for (int i = 0; i < count; i++) {
479 final T value = values.get(i);
480 final String valueText = value.toString().toLowerCase();
481
482 // First match against the whole, non-splitted value
483 if (valueText.startsWith(prefixString)) {
484 newValues.add(value);
485 } else {
486 final String[] words = valueText.split(" ");
487 final int wordCount = words.length;
488
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800489 // Start at index 0, in case valueText starts with space(s)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800490 for (int k = 0; k < wordCount; k++) {
491 if (words[k].startsWith(prefixString)) {
492 newValues.add(value);
493 break;
494 }
495 }
496 }
497 }
498
499 results.values = newValues;
500 results.count = newValues.size();
501 }
502
503 return results;
504 }
505
506 @Override
507 protected void publishResults(CharSequence constraint, FilterResults results) {
508 //noinspection unchecked
509 mObjects = (List<T>) results.values;
510 if (results.count > 0) {
511 notifyDataSetChanged();
512 } else {
513 notifyDataSetInvalidated();
514 }
515 }
516 }
517}