blob: 690067b4813302163c1692921bdbadd8dcb329c9 [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
Tor Norbye7b9c9122013-05-30 16:48:33 -070019import android.annotation.ArrayRes;
20import android.annotation.IdRes;
21import android.annotation.LayoutRes;
Tor Norbye417ee5b2015-03-10 20:57:37 -070022import android.annotation.NonNull;
Alan Viverette9e623b62016-04-01 10:11:25 -040023import android.annotation.Nullable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.Context;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080025import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.util.Log;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -080027import android.view.ContextThemeWrapper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31
32import java.util.ArrayList;
33import java.util.Arrays;
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +020034import java.util.Collection;
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -070035import java.util.Collections;
Gilles Debunnebe2c4f92011-01-17 15:14:32 -080036import java.util.Comparator;
37import java.util.List;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080038
39/**
Joe Fernandezd7ef95a2017-04-24 13:50:13 -070040 * You can use this adapter to provide views for an {@link AdapterView},
41 * Returns a view for each object in a collection of data objects you
42 * provide, and can be used with list-based user interface widgets such as
43 * {@link ListView} or {@link Spinner}.
44 * <p>
45 * By default, the array adapter creates a view by calling {@link Object#toString()} on each
46 * data object in the collection you provide, and places the result in a TextView.
47 * You may also customize what type of view is used for the data object in the collection.
48 * To customize what type of view is used for the data object,
49 * override {@link #getView(int, View, ViewGroup)}
50 * and inflate a view resource.
51 * For a code example, see
52 * the <a href="https://developer.android.com/samples/CustomChoiceList/index.html">
53 * CustomChoiceList</a> sample.
54 * </p>
55 * <p>
56 * For an example of using an array adapter with a ListView, see the
57 * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#AdapterViews">
58 * Adapter Views</a> guide.
59 * </p>
60 * <p>
61 * For an example of using an array adapter with a Spinner, see the
62 * <a href="{@docRoot}guide/topics/ui/controls/spinner.html">Spinners</a> guide.
63 * </p>
64 * <p class="note"><strong>Note:</strong>
65 * If you are considering using array adapter with a ListView, consider using
66 * {@link android.support.v7.widget.RecyclerView} instead.
67 * RecyclerView offers similar features with better performance and more flexibility than
68 * ListView provides.
69 * See the
70 * <a href="https://developer.android.com/guide/topics/ui/layout/recyclerview.html">
71 * Recycler View</a> guide.</p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 */
Alan Viverette2add9bc2015-06-02 14:54:40 -070073public class ArrayAdapter<T> extends BaseAdapter implements Filterable, ThemedSpinnerAdapter {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 * Lock used to modify the content of {@link #mObjects}. Any write operation
76 * performed on the array should be synchronized on this lock. This lock is also
77 * used by the filter (see {@link #getFilter()} to make a synchronized copy of
78 * the original array of data.
79 */
80 private final Object mLock = new Object();
81
Alan Viverette7d5967e2015-06-03 16:27:36 -070082 private final LayoutInflater mInflater;
83
Alan Viverette9e623b62016-04-01 10:11:25 -040084 private final Context mContext;
Alan Viverette7d5967e2015-06-03 16:27:36 -070085
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 /**
87 * The resource indicating what views to inflate to display the content of this
88 * array adapter.
89 */
Alan Viverette9e623b62016-04-01 10:11:25 -040090 private final int mResource;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091
92 /**
93 * The resource indicating what views to inflate to display the content of this
94 * array adapter in a drop down widget.
95 */
96 private int mDropDownResource;
97
98 /**
Alan Viverette9e623b62016-04-01 10:11:25 -040099 * Contains the list of objects that represent the data of this ArrayAdapter.
100 * The content of this list is referred to as "the array" in the documentation.
101 */
102 private List<T> mObjects;
103
104 /**
Felipe Leme7e4c2052017-04-18 09:45:58 -0700105 * Indicates whether the contents of {@link #mObjects} came from static resources.
106 */
107 private boolean mObjectsFromResources;
108
109 /**
Alan Viverette9e623b62016-04-01 10:11:25 -0400110 * If the inflated resource is not a TextView, {@code mFieldId} is used to find
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111 * a TextView inside the inflated views hierarchy. This field must contain the
112 * identifier that matches the one defined in the resource file.
113 */
114 private int mFieldId = 0;
115
116 /**
117 * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever
118 * {@link #mObjects} is modified.
119 */
120 private boolean mNotifyOnChange = true;
121
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800122 // A copy of the original mObjects array, initialized from and then used instead as soon as
123 // the mFilter ArrayFilter is used. mObjects will then only contain the filtered values.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 private ArrayList<T> mOriginalValues;
125 private ArrayFilter mFilter;
126
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800127 /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */
128 private LayoutInflater mDropDownInflater;
129
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 /**
131 * Constructor
132 *
133 * @param context The current context.
Romain Guy9c5d1b12013-01-09 20:16:37 -0800134 * @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 -0800135 * instantiating views.
136 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400137 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource) {
138 this(context, resource, 0, new ArrayList<>());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 }
140
141 /**
142 * Constructor
143 *
144 * @param context The current context.
145 * @param resource The resource ID for a layout file containing a layout to use when
146 * instantiating views.
147 * @param textViewResourceId The id of the TextView within the layout resource to be populated
148 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400149 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
150 @IdRes int textViewResourceId) {
151 this(context, resource, textViewResourceId, new ArrayList<>());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 }
153
154 /**
155 * Constructor
156 *
157 * @param context The current context.
Romain Guy9c5d1b12013-01-09 20:16:37 -0800158 * @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 -0800159 * instantiating views.
160 * @param objects The objects to represent in the ListView.
161 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400162 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull T[] objects) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800163 this(context, resource, 0, Arrays.asList(objects));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 }
165
166 /**
167 * Constructor
168 *
169 * @param context The current context.
170 * @param resource The resource ID for a layout file containing a layout to use when
171 * instantiating views.
172 * @param textViewResourceId The id of the TextView within the layout resource to be populated
173 * @param objects The objects to represent in the ListView.
174 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400175 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
176 @IdRes int textViewResourceId, @NonNull T[] objects) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800177 this(context, resource, textViewResourceId, Arrays.asList(objects));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 }
179
180 /**
181 * Constructor
182 *
183 * @param context The current context.
Romain Guy9c5d1b12013-01-09 20:16:37 -0800184 * @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 -0800185 * instantiating views.
186 * @param objects The objects to represent in the ListView.
187 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400188 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
189 @NonNull List<T> objects) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800190 this(context, resource, 0, objects);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800191 }
192
193 /**
194 * Constructor
195 *
196 * @param context The current context.
197 * @param resource The resource ID for a layout file containing a layout to use when
198 * instantiating views.
199 * @param textViewResourceId The id of the TextView within the layout resource to be populated
200 * @param objects The objects to represent in the ListView.
201 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400202 public ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
203 @IdRes int textViewResourceId, @NonNull List<T> objects) {
Felipe Leme7e4c2052017-04-18 09:45:58 -0700204 this(context, resource, textViewResourceId, objects, false);
205 }
206
207 private ArrayAdapter(@NonNull Context context, @LayoutRes int resource,
208 @IdRes int textViewResourceId, @NonNull List<T> objects, boolean objsFromResources) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800209 mContext = context;
210 mInflater = LayoutInflater.from(context);
211 mResource = mDropDownResource = resource;
212 mObjects = objects;
Felipe Leme7e4c2052017-04-18 09:45:58 -0700213 mObjectsFromResources = objsFromResources;
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800214 mFieldId = textViewResourceId;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 }
216
217 /**
218 * Adds the specified object at the end of the array.
219 *
220 * @param object The object to add at the end of the array.
221 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400222 public void add(@Nullable T object) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800223 synchronized (mLock) {
224 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 mOriginalValues.add(object);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800226 } else {
227 mObjects.add(object);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 }
Felipe Leme7e4c2052017-04-18 09:45:58 -0700229 mObjectsFromResources = false;
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 /**
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200235 * Adds the specified Collection at the end of the array.
236 *
237 * @param collection The Collection to add at the end of the array.
Alan Viverette9e623b62016-04-01 10:11:25 -0400238 * @throws UnsupportedOperationException if the <tt>addAll</tt> operation
239 * is not supported by this list
240 * @throws ClassCastException if the class of an element of the specified
241 * collection prevents it from being added to this list
242 * @throws NullPointerException if the specified collection contains one
243 * or more null elements and this list does not permit null
244 * elements, or if the specified collection is null
245 * @throws IllegalArgumentException if some property of an element of the
246 * specified collection prevents it from being added to this list
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200247 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400248 public void addAll(@NonNull Collection<? extends T> collection) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800249 synchronized (mLock) {
250 if (mOriginalValues != null) {
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200251 mOriginalValues.addAll(collection);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800252 } else {
253 mObjects.addAll(collection);
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200254 }
Felipe Leme7e4c2052017-04-18 09:45:58 -0700255 mObjectsFromResources = false;
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200256 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800257 if (mNotifyOnChange) notifyDataSetChanged();
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200258 }
259
260 /**
261 * Adds the specified items at the end of the array.
262 *
263 * @param items The items to add at the end of the array.
264 */
265 public void addAll(T ... items) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800266 synchronized (mLock) {
267 if (mOriginalValues != null) {
Romain Guy95a78c32011-08-15 15:36:30 -0700268 Collections.addAll(mOriginalValues, items);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800269 } else {
Romain Guy95a78c32011-08-15 15:36:30 -0700270 Collections.addAll(mObjects, items);
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200271 }
Felipe Leme7e4c2052017-04-18 09:45:58 -0700272 mObjectsFromResources = false;
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200273 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800274 if (mNotifyOnChange) notifyDataSetChanged();
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200275 }
276
277 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 * Inserts the specified object at the specified index in the array.
279 *
280 * @param object The object to insert into the array.
281 * @param index The index at which the object must be inserted.
282 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400283 public void insert(@Nullable T object, int index) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800284 synchronized (mLock) {
285 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800286 mOriginalValues.add(index, object);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800287 } else {
288 mObjects.add(index, object);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800289 }
Felipe Leme7e4c2052017-04-18 09:45:58 -0700290 mObjectsFromResources = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 }
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800292 if (mNotifyOnChange) notifyDataSetChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 }
294
295 /**
296 * Removes the specified object from the array.
297 *
298 * @param object The object to remove.
299 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400300 public void remove(@Nullable T object) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800301 synchronized (mLock) {
302 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 mOriginalValues.remove(object);
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800304 } else {
305 mObjects.remove(object);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800306 }
Felipe Leme7e4c2052017-04-18 09:45:58 -0700307 mObjectsFromResources = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 }
309 if (mNotifyOnChange) notifyDataSetChanged();
310 }
311
312 /**
313 * Remove all elements from the list.
314 */
315 public void clear() {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800316 synchronized (mLock) {
317 if (mOriginalValues != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 mOriginalValues.clear();
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800319 } else {
320 mObjects.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 }
Felipe Leme7e4c2052017-04-18 09:45:58 -0700322 mObjectsFromResources = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800323 }
324 if (mNotifyOnChange) notifyDataSetChanged();
325 }
326
327 /**
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700328 * Sorts the content of this adapter using the specified comparator.
329 *
330 * @param comparator The comparator used to sort the objects contained
331 * in this adapter.
332 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400333 public void sort(@NonNull Comparator<? super T> comparator) {
Gilles Debunnebe2c4f92011-01-17 15:14:32 -0800334 synchronized (mLock) {
335 if (mOriginalValues != null) {
336 Collections.sort(mOriginalValues, comparator);
337 } else {
338 Collections.sort(mObjects, comparator);
339 }
340 }
Christian Mehlmauer8c582ef2010-06-11 22:28:38 +0200341 if (mNotifyOnChange) notifyDataSetChanged();
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700342 }
343
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800344 @Override
345 public void notifyDataSetChanged() {
346 super.notifyDataSetChanged();
347 mNotifyOnChange = true;
348 }
349
350 /**
Yigit Boyar42c69b92016-08-19 10:15:52 -0700351 * Control whether methods that change the list ({@link #add}, {@link #addAll(Collection)},
352 * {@link #addAll(Object[])}, {@link #insert}, {@link #remove}, {@link #clear},
353 * {@link #sort(Comparator)}) automatically call {@link #notifyDataSetChanged}. If set to
354 * false, caller must manually call notifyDataSetChanged() to have the changes
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800355 * reflected in the attached view.
356 *
357 * The default is true, and calling notifyDataSetChanged()
358 * resets the flag to true.
359 *
360 * @param notifyOnChange if true, modifications to the list will
361 * automatically call {@link
362 * #notifyDataSetChanged}
363 */
364 public void setNotifyOnChange(boolean notifyOnChange) {
365 mNotifyOnChange = notifyOnChange;
366 }
367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 /**
369 * Returns the context associated with this array adapter. The context is used
370 * to create views from the resource passed to the constructor.
371 *
372 * @return The Context associated with this adapter.
373 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400374 public @NonNull Context getContext() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800375 return mContext;
376 }
377
Alan Viverette9e623b62016-04-01 10:11:25 -0400378 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379 public int getCount() {
380 return mObjects.size();
381 }
382
Alan Viverette9e623b62016-04-01 10:11:25 -0400383 @Override
384 public @Nullable T getItem(int position) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 return mObjects.get(position);
386 }
387
388 /**
389 * Returns the position of the specified item in the array.
390 *
391 * @param item The item to retrieve the position of.
392 *
393 * @return The position of the specified item.
394 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400395 public int getPosition(@Nullable T item) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 return mObjects.indexOf(item);
397 }
398
Alan Viverette9e623b62016-04-01 10:11:25 -0400399 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800400 public long getItemId(int position) {
401 return position;
402 }
403
Alan Viverette9e623b62016-04-01 10:11:25 -0400404 @Override
405 public @NonNull View getView(int position, @Nullable View convertView,
406 @NonNull ViewGroup parent) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800407 return createViewFromResource(mInflater, position, convertView, parent, mResource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408 }
409
Alan Viverette9e623b62016-04-01 10:11:25 -0400410 private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position,
411 @Nullable View convertView, @NonNull ViewGroup parent, int resource) {
412 final View view;
413 final TextView text;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800414
415 if (convertView == null) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800416 view = inflater.inflate(resource, parent, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 } else {
418 view = convertView;
419 }
420
421 try {
422 if (mFieldId == 0) {
423 // If no custom field is assigned, assume the whole resource is a TextView
424 text = (TextView) view;
425 } else {
426 // Otherwise, find the TextView field within the layout
Alan Viverette8e1a7292017-02-27 10:57:58 -0500427 text = view.findViewById(mFieldId);
Alan Viverette9e623b62016-04-01 10:11:25 -0400428
429 if (text == null) {
430 throw new RuntimeException("Failed to find view with ID "
431 + mContext.getResources().getResourceName(mFieldId)
432 + " in item layout");
433 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 }
435 } catch (ClassCastException e) {
436 Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
437 throw new IllegalStateException(
438 "ArrayAdapter requires the resource ID to be a TextView", e);
439 }
440
Alan Viverette9e623b62016-04-01 10:11:25 -0400441 final T item = getItem(position);
Daisuke Miyakawab5d91322009-07-09 14:26:56 +0900442 if (item instanceof CharSequence) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400443 text.setText((CharSequence) item);
Daisuke Miyakawab5d91322009-07-09 14:26:56 +0900444 } else {
445 text.setText(item.toString());
446 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447
448 return view;
449 }
450
451 /**
452 * <p>Sets the layout resource to create the drop down views.</p>
453 *
454 * @param resource the layout resource defining the drop down views
455 * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
456 */
Tor Norbye7b9c9122013-05-30 16:48:33 -0700457 public void setDropDownViewResource(@LayoutRes int resource) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 this.mDropDownResource = resource;
459 }
460
461 /**
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800462 * Sets the {@link Resources.Theme} against which drop-down views are
463 * inflated.
464 * <p>
465 * By default, drop-down views are inflated against the theme of the
466 * {@link Context} passed to the adapter's constructor.
467 *
468 * @param theme the theme against which to inflate drop-down views or
469 * {@code null} to use the theme from the adapter's context
470 * @see #getDropDownView(int, View, ViewGroup)
471 */
472 @Override
Alan Viverette9e623b62016-04-01 10:11:25 -0400473 public void setDropDownViewTheme(@Nullable Resources.Theme theme) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800474 if (theme == null) {
475 mDropDownInflater = null;
476 } else if (theme == mInflater.getContext().getTheme()) {
477 mDropDownInflater = mInflater;
478 } else {
479 final Context context = new ContextThemeWrapper(mContext, theme);
480 mDropDownInflater = LayoutInflater.from(context);
481 }
482 }
483
484 @Override
Alan Viverette9e623b62016-04-01 10:11:25 -0400485 public @Nullable Resources.Theme getDropDownViewTheme() {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800486 return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme();
487 }
488
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800489 @Override
Alan Viverette9e623b62016-04-01 10:11:25 -0400490 public View getDropDownView(int position, @Nullable View convertView,
491 @NonNull ViewGroup parent) {
Alan Viveretteb9ead4a2015-01-14 10:43:31 -0800492 final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater;
493 return createViewFromResource(inflater, position, convertView, parent, mDropDownResource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 }
495
496 /**
497 * Creates a new ArrayAdapter from external resources. The content of the array is
498 * obtained through {@link android.content.res.Resources#getTextArray(int)}.
499 *
500 * @param context The application's environment.
501 * @param textArrayResId The identifier of the array to use as the data source.
502 * @param textViewResId The identifier of the layout used to create views.
503 *
504 * @return An ArrayAdapter<CharSequence>.
505 */
Alan Viverette9e623b62016-04-01 10:11:25 -0400506 public static @NonNull ArrayAdapter<CharSequence> createFromResource(@NonNull Context context,
Tor Norbye7b9c9122013-05-30 16:48:33 -0700507 @ArrayRes int textArrayResId, @LayoutRes int textViewResId) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400508 final CharSequence[] strings = context.getResources().getTextArray(textArrayResId);
Felipe Leme7e4c2052017-04-18 09:45:58 -0700509 return new ArrayAdapter<>(context, textViewResId, 0, Arrays.asList(strings), true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800510 }
511
Alan Viverette9e623b62016-04-01 10:11:25 -0400512 @Override
513 public @NonNull Filter getFilter() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800514 if (mFilter == null) {
515 mFilter = new ArrayFilter();
516 }
517 return mFilter;
518 }
519
520 /**
Felipe Leme7e4c2052017-04-18 09:45:58 -0700521 * {@inheritDoc}
522 *
523 * @return values from the string array used by {@link #createFromResource(Context, int, int)},
524 * or {@code null} if object was created otherwsie or if contents were dynamically changed after
525 * creation.
526 */
527 @Override
528 public CharSequence[] getAutofillOptions() {
Felipe Leme49a38c62017-06-13 12:51:43 -0700529 // First check if app developer explicitly set them.
530 final CharSequence[] explicitOptions = super.getAutofillOptions();
531 if (explicitOptions != null) {
532 return explicitOptions;
533 }
534
535 // Otherwise, only return options that came from static resources.
Felipe Leme7e4c2052017-04-18 09:45:58 -0700536 if (!mObjectsFromResources || mObjects == null || mObjects.isEmpty()) {
537 return null;
538 }
539 final int size = mObjects.size();
540 final CharSequence[] options = new CharSequence[size];
541 mObjects.toArray(options);
542 return options;
543 }
544
545 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546 * <p>An array filter constrains the content of the array adapter with
547 * a prefix. Each item that does not start with the supplied prefix
548 * is removed from the list.</p>
549 */
550 private class ArrayFilter extends Filter {
551 @Override
552 protected FilterResults performFiltering(CharSequence prefix) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400553 final FilterResults results = new FilterResults();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554
555 if (mOriginalValues == null) {
556 synchronized (mLock) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400557 mOriginalValues = new ArrayList<>(mObjects);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800558 }
559 }
560
561 if (prefix == null || prefix.length() == 0) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400562 final ArrayList<T> list;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 synchronized (mLock) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400564 list = new ArrayList<>(mOriginalValues);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800565 }
Romain Guy95a78c32011-08-15 15:36:30 -0700566 results.values = list;
567 results.count = list.size();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800568 } else {
Alan Viverette9e623b62016-04-01 10:11:25 -0400569 final String prefixString = prefix.toString().toLowerCase();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570
Alan Viverette9e623b62016-04-01 10:11:25 -0400571 final ArrayList<T> values;
Romain Guy95a78c32011-08-15 15:36:30 -0700572 synchronized (mLock) {
Alan Viverette9e623b62016-04-01 10:11:25 -0400573 values = new ArrayList<>(mOriginalValues);
Romain Guy95a78c32011-08-15 15:36:30 -0700574 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575
Romain Guy95a78c32011-08-15 15:36:30 -0700576 final int count = values.size();
Alan Viverette9e623b62016-04-01 10:11:25 -0400577 final ArrayList<T> newValues = new ArrayList<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578
579 for (int i = 0; i < count; i++) {
580 final T value = values.get(i);
581 final String valueText = value.toString().toLowerCase();
582
583 // First match against the whole, non-splitted value
584 if (valueText.startsWith(prefixString)) {
585 newValues.add(value);
586 } else {
587 final String[] words = valueText.split(" ");
Alan Viverette9e623b62016-04-01 10:11:25 -0400588 for (String word : words) {
589 if (word.startsWith(prefixString)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 newValues.add(value);
591 break;
592 }
593 }
594 }
595 }
596
597 results.values = newValues;
598 results.count = newValues.size();
599 }
600
601 return results;
602 }
603
604 @Override
605 protected void publishResults(CharSequence constraint, FilterResults results) {
606 //noinspection unchecked
607 mObjects = (List<T>) results.values;
608 if (results.count > 0) {
609 notifyDataSetChanged();
610 } else {
611 notifyDataSetInvalidated();
612 }
613 }
614 }
615}