1 /* 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 17 package android.widget; 18 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; 24 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; 30 31 /** 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 36 * resource. 37 * 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. 41 * 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. 45 */ 46 public class ArrayAdapter<T> extends BaseAdapter implements Filterable { 47 /** 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. 50 */ 51 private List<T> mObjects; 52 53 /** 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. 58 */ 59 private final Object mLock = new Object(); 60 61 /** 62 * The resource indicating what views to inflate to display the content of this 63 * array adapter. 64 */ 65 private int mResource; 66 67 /** 68 * The resource indicating what views to inflate to display the content of this 69 * array adapter in a drop down widget. 70 */ 71 private int mDropDownResource; 72 73 /** 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. 77 */ 78 private int mFieldId = 0; 79 80 /** 81 * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever 82 * {@link #mObjects} is modified. 83 */ 84 private boolean mNotifyOnChange = true; 85 86 private Context mContext; 87 88 private ArrayList<T> mOriginalValues; 89 private ArrayFilter mFilter; 90 91 private LayoutInflater mInflater; 92 93 /** 94 * Constructor 95 * 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. 99 */ ArrayAdapter(Context context, int textViewResourceId)100 public ArrayAdapter(Context context, int textViewResourceId) { 101 init(context, textViewResourceId, 0, new ArrayList<T>()); 102 } 103 104 /** 105 * Constructor 106 * 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 111 */ ArrayAdapter(Context context, int resource, int textViewResourceId)112 public ArrayAdapter(Context context, int resource, int textViewResourceId) { 113 init(context, resource, textViewResourceId, new ArrayList<T>()); 114 } 115 116 /** 117 * Constructor 118 * 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. 123 */ ArrayAdapter(Context context, int textViewResourceId, T[] objects)124 public ArrayAdapter(Context context, int textViewResourceId, T[] objects) { 125 init(context, textViewResourceId, 0, Arrays.asList(objects)); 126 } 127 128 /** 129 * Constructor 130 * 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. 136 */ ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects)137 public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) { 138 init(context, resource, textViewResourceId, Arrays.asList(objects)); 139 } 140 141 /** 142 * Constructor 143 * 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. 148 */ ArrayAdapter(Context context, int textViewResourceId, List<T> objects)149 public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) { 150 init(context, textViewResourceId, 0, objects); 151 } 152 153 /** 154 * Constructor 155 * 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. 161 */ ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects)162 public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) { 163 init(context, resource, textViewResourceId, objects); 164 } 165 166 /** 167 * Adds the specified object at the end of the array. 168 * 169 * @param object The object to add at the end of the array. 170 */ add(T object)171 public void add(T object) { 172 if (mOriginalValues != null) { 173 synchronized (mLock) { 174 mOriginalValues.add(object); 175 if (mNotifyOnChange) notifyDataSetChanged(); 176 } 177 } else { 178 mObjects.add(object); 179 if (mNotifyOnChange) notifyDataSetChanged(); 180 } 181 } 182 183 /** 184 * Inserts the specified object at the specified index in the array. 185 * 186 * @param object The object to insert into the array. 187 * @param index The index at which the object must be inserted. 188 */ insert(T object, int index)189 public void insert(T object, int index) { 190 if (mOriginalValues != null) { 191 synchronized (mLock) { 192 mOriginalValues.add(index, object); 193 if (mNotifyOnChange) notifyDataSetChanged(); 194 } 195 } else { 196 mObjects.add(index, object); 197 if (mNotifyOnChange) notifyDataSetChanged(); 198 } 199 } 200 201 /** 202 * Removes the specified object from the array. 203 * 204 * @param object The object to remove. 205 */ remove(T object)206 public void remove(T object) { 207 if (mOriginalValues != null) { 208 synchronized (mLock) { 209 mOriginalValues.remove(object); 210 } 211 } else { 212 mObjects.remove(object); 213 } 214 if (mNotifyOnChange) notifyDataSetChanged(); 215 } 216 217 /** 218 * Remove all elements from the list. 219 */ clear()220 public void clear() { 221 if (mOriginalValues != null) { 222 synchronized (mLock) { 223 mOriginalValues.clear(); 224 } 225 } else { 226 mObjects.clear(); 227 } 228 if (mNotifyOnChange) notifyDataSetChanged(); 229 } 230 231 /** 232 * Sorts the content of this adapter using the specified comparator. 233 * 234 * @param comparator The comparator used to sort the objects contained 235 * in this adapter. 236 */ sort(Comparator<? super T> comparator)237 public void sort(Comparator<? super T> comparator) { 238 Collections.sort(mObjects, comparator); 239 if (mNotifyOnChange) notifyDataSetChanged(); 240 } 241 242 /** 243 * {@inheritDoc} 244 */ 245 @Override notifyDataSetChanged()246 public void notifyDataSetChanged() { 247 super.notifyDataSetChanged(); 248 mNotifyOnChange = true; 249 } 250 251 /** 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. 257 * 258 * The default is true, and calling notifyDataSetChanged() 259 * resets the flag to true. 260 * 261 * @param notifyOnChange if true, modifications to the list will 262 * automatically call {@link 263 * #notifyDataSetChanged} 264 */ setNotifyOnChange(boolean notifyOnChange)265 public void setNotifyOnChange(boolean notifyOnChange) { 266 mNotifyOnChange = notifyOnChange; 267 } 268 init(Context context, int resource, int textViewResourceId, List<T> objects)269 private void init(Context context, int resource, int textViewResourceId, List<T> objects) { 270 mContext = context; 271 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 272 mResource = mDropDownResource = resource; 273 mObjects = objects; 274 mFieldId = textViewResourceId; 275 } 276 277 /** 278 * Returns the context associated with this array adapter. The context is used 279 * to create views from the resource passed to the constructor. 280 * 281 * @return The Context associated with this adapter. 282 */ getContext()283 public Context getContext() { 284 return mContext; 285 } 286 287 /** 288 * {@inheritDoc} 289 */ getCount()290 public int getCount() { 291 return mObjects.size(); 292 } 293 294 /** 295 * {@inheritDoc} 296 */ getItem(int position)297 public T getItem(int position) { 298 return mObjects.get(position); 299 } 300 301 /** 302 * Returns the position of the specified item in the array. 303 * 304 * @param item The item to retrieve the position of. 305 * 306 * @return The position of the specified item. 307 */ getPosition(T item)308 public int getPosition(T item) { 309 return mObjects.indexOf(item); 310 } 311 312 /** 313 * {@inheritDoc} 314 */ getItemId(int position)315 public long getItemId(int position) { 316 return position; 317 } 318 319 /** 320 * {@inheritDoc} 321 */ getView(int position, View convertView, ViewGroup parent)322 public View getView(int position, View convertView, ViewGroup parent) { 323 return createViewFromResource(position, convertView, parent, mResource); 324 } 325 createViewFromResource(int position, View convertView, ViewGroup parent, int resource)326 private View createViewFromResource(int position, View convertView, ViewGroup parent, 327 int resource) { 328 View view; 329 TextView text; 330 331 if (convertView == null) { 332 view = mInflater.inflate(resource, parent, false); 333 } else { 334 view = convertView; 335 } 336 337 try { 338 if (mFieldId == 0) { 339 // If no custom field is assigned, assume the whole resource is a TextView 340 text = (TextView) view; 341 } else { 342 // Otherwise, find the TextView field within the layout 343 text = (TextView) view.findViewById(mFieldId); 344 } 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); 349 } 350 351 T item = getItem(position); 352 if (item instanceof CharSequence) { 353 text.setText((CharSequence)item); 354 } else { 355 text.setText(item.toString()); 356 } 357 358 return view; 359 } 360 361 /** 362 * <p>Sets the layout resource to create the drop down views.</p> 363 * 364 * @param resource the layout resource defining the drop down views 365 * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) 366 */ setDropDownViewResource(int resource)367 public void setDropDownViewResource(int resource) { 368 this.mDropDownResource = resource; 369 } 370 371 /** 372 * {@inheritDoc} 373 */ 374 @Override getDropDownView(int position, View convertView, ViewGroup parent)375 public View getDropDownView(int position, View convertView, ViewGroup parent) { 376 return createViewFromResource(position, convertView, parent, mDropDownResource); 377 } 378 379 /** 380 * Creates a new ArrayAdapter from external resources. The content of the array is 381 * obtained through {@link android.content.res.Resources#getTextArray(int)}. 382 * 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. 386 * 387 * @return An ArrayAdapter<CharSequence>. 388 */ createFromResource(Context context, int textArrayResId, int textViewResId)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); 393 } 394 395 /** 396 * {@inheritDoc} 397 */ getFilter()398 public Filter getFilter() { 399 if (mFilter == null) { 400 mFilter = new ArrayFilter(); 401 } 402 return mFilter; 403 } 404 405 /** 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> 409 */ 410 private class ArrayFilter extends Filter { 411 @Override performFiltering(CharSequence prefix)412 protected FilterResults performFiltering(CharSequence prefix) { 413 FilterResults results = new FilterResults(); 414 415 if (mOriginalValues == null) { 416 synchronized (mLock) { 417 mOriginalValues = new ArrayList<T>(mObjects); 418 } 419 } 420 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(); 426 } 427 } else { 428 String prefixString = prefix.toString().toLowerCase(); 429 430 final ArrayList<T> values = mOriginalValues; 431 final int count = values.size(); 432 433 final ArrayList<T> newValues = new ArrayList<T>(count); 434 435 for (int i = 0; i < count; i++) { 436 final T value = values.get(i); 437 final String valueText = value.toString().toLowerCase(); 438 439 // First match against the whole, non-splitted value 440 if (valueText.startsWith(prefixString)) { 441 newValues.add(value); 442 } else { 443 final String[] words = valueText.split(" "); 444 final int wordCount = words.length; 445 446 for (int k = 0; k < wordCount; k++) { 447 if (words[k].startsWith(prefixString)) { 448 newValues.add(value); 449 break; 450 } 451 } 452 } 453 } 454 455 results.values = newValues; 456 results.count = newValues.size(); 457 } 458 459 return results; 460 } 461 462 @Override publishResults(CharSequence constraint, FilterResults results)463 protected void publishResults(CharSequence constraint, FilterResults results) { 464 //noinspection unchecked 465 mObjects = (List<T>) results.values; 466 if (results.count > 0) { 467 notifyDataSetChanged(); 468 } else { 469 notifyDataSetInvalidated(); 470 } 471 } 472 } 473 } 474