ArrayAdapter.java revision 8c582ef859fcbbb97623d22024c3ecb32b65c5ef
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 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; 27import java.util.Collection; 28import java.util.List; 29import java.util.Comparator; 30import java.util.Collections; 31 32/** 33 * A ListAdapter that manages a ListView backed by an array of arbitrary 34 * 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 * 39 * However the TextView is referenced, it will be filled with the toString() of each object in 40 * 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 * 43 * To use something other than TextViews for the array display, for instance, ImageViews, 44 * 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 87 private Context mContext; 88 89 private ArrayList<T> mOriginalValues; 90 private ArrayFilter mFilter; 91 92 private LayoutInflater mInflater; 93 94 /** 95 * Constructor 96 * 97 * @param context The current context. 98 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when 99 * instantiating views. 100 */ 101 public ArrayAdapter(Context context, int textViewResourceId) { 102 init(context, textViewResourceId, 0, new ArrayList<T>()); 103 } 104 105 /** 106 * Constructor 107 * 108 * @param context The current context. 109 * @param resource The resource ID for a layout file containing a layout to use when 110 * instantiating views. 111 * @param textViewResourceId The id of the TextView within the layout resource to be populated 112 */ 113 public ArrayAdapter(Context context, int resource, int textViewResourceId) { 114 init(context, resource, textViewResourceId, new ArrayList<T>()); 115 } 116 117 /** 118 * Constructor 119 * 120 * @param context The current context. 121 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when 122 * instantiating views. 123 * @param objects The objects to represent in the ListView. 124 */ 125 public ArrayAdapter(Context context, int textViewResourceId, T[] objects) { 126 init(context, textViewResourceId, 0, Arrays.asList(objects)); 127 } 128 129 /** 130 * Constructor 131 * 132 * @param context The current context. 133 * @param resource The resource ID for a layout file containing a layout to use when 134 * instantiating views. 135 * @param textViewResourceId The id of the TextView within the layout resource to be populated 136 * @param objects The objects to represent in the ListView. 137 */ 138 public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) { 139 init(context, resource, textViewResourceId, Arrays.asList(objects)); 140 } 141 142 /** 143 * Constructor 144 * 145 * @param context The current context. 146 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when 147 * instantiating views. 148 * @param objects The objects to represent in the ListView. 149 */ 150 public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) { 151 init(context, textViewResourceId, 0, objects); 152 } 153 154 /** 155 * Constructor 156 * 157 * @param context The current context. 158 * @param resource The resource ID for a layout file containing a layout to use when 159 * instantiating views. 160 * @param textViewResourceId The id of the TextView within the layout resource to be populated 161 * @param objects The objects to represent in the ListView. 162 */ 163 public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) { 164 init(context, resource, textViewResourceId, objects); 165 } 166 167 /** 168 * Adds the specified object at the end of the array. 169 * 170 * @param object The object to add at the end of the array. 171 */ 172 public void add(T object) { 173 if (mOriginalValues != null) { 174 synchronized (mLock) { 175 mOriginalValues.add(object); 176 if (mNotifyOnChange) notifyDataSetChanged(); 177 } 178 } else { 179 mObjects.add(object); 180 if (mNotifyOnChange) notifyDataSetChanged(); 181 } 182 } 183 184 /** 185 * Adds the specified Collection at the end of the array. 186 * 187 * @param collection The Collection to add at the end of the array. 188 */ 189 public void addAll(Collection<? extends T> collection) { 190 if (mOriginalValues != null) { 191 synchronized (mLock) { 192 mOriginalValues.addAll(collection); 193 if (mNotifyOnChange) notifyDataSetChanged(); 194 } 195 } else { 196 mObjects.addAll(collection); 197 if (mNotifyOnChange) notifyDataSetChanged(); 198 } 199 } 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) { 207 if (mOriginalValues != null) { 208 synchronized (mLock) { 209 for (T item : items) { 210 mOriginalValues.add(item); 211 } 212 if (mNotifyOnChange) notifyDataSetChanged(); 213 } 214 } else { 215 for (T item : items) { 216 mObjects.add(item); 217 } 218 if (mNotifyOnChange) notifyDataSetChanged(); 219 } 220 } 221 222 /** 223 * Inserts the specified object at the specified index in the array. 224 * 225 * @param object The object to insert into the array. 226 * @param index The index at which the object must be inserted. 227 */ 228 public void insert(T object, int index) { 229 if (mOriginalValues != null) { 230 synchronized (mLock) { 231 mOriginalValues.add(index, object); 232 if (mNotifyOnChange) notifyDataSetChanged(); 233 } 234 } else { 235 mObjects.add(index, object); 236 if (mNotifyOnChange) notifyDataSetChanged(); 237 } 238 } 239 240 /** 241 * Removes the specified object from the array. 242 * 243 * @param object The object to remove. 244 */ 245 public void remove(T object) { 246 if (mOriginalValues != null) { 247 synchronized (mLock) { 248 mOriginalValues.remove(object); 249 } 250 } else { 251 mObjects.remove(object); 252 } 253 if (mNotifyOnChange) notifyDataSetChanged(); 254 } 255 256 /** 257 * Remove all elements from the list. 258 */ 259 public void clear() { 260 if (mOriginalValues != null) { 261 synchronized (mLock) { 262 mOriginalValues.clear(); 263 } 264 } else { 265 mObjects.clear(); 266 } 267 if (mNotifyOnChange) notifyDataSetChanged(); 268 } 269 270 /** 271 * Sorts the content of this adapter using the specified comparator. 272 * 273 * @param comparator The comparator used to sort the objects contained 274 * in this adapter. 275 */ 276 public void sort(Comparator<? super T> comparator) { 277 Collections.sort(mObjects, comparator); 278 if (mNotifyOnChange) notifyDataSetChanged(); 279 } 280 281 /** 282 * {@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 390 T item = getItem(position); 391 if (item instanceof CharSequence) { 392 text.setText((CharSequence)item); 393 } else { 394 text.setText(item.toString()); 395 } 396 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) { 461 synchronized (mLock) { 462 ArrayList<T> list = new ArrayList<T>(mOriginalValues); 463 results.values = list; 464 results.count = list.size(); 465 } 466 } else { 467 String prefixString = prefix.toString().toLowerCase(); 468 469 final ArrayList<T> values = mOriginalValues; 470 final int count = values.size(); 471 472 final ArrayList<T> newValues = new ArrayList<T>(count); 473 474 for (int i = 0; i < count; i++) { 475 final T value = values.get(i); 476 final String valueText = value.toString().toLowerCase(); 477 478 // First match against the whole, non-splitted value 479 if (valueText.startsWith(prefixString)) { 480 newValues.add(value); 481 } else { 482 final String[] words = valueText.split(" "); 483 final int wordCount = words.length; 484 485 for (int k = 0; k < wordCount; k++) { 486 if (words[k].startsWith(prefixString)) { 487 newValues.add(value); 488 break; 489 } 490 } 491 } 492 } 493 494 results.values = newValues; 495 results.count = newValues.size(); 496 } 497 498 return results; 499 } 500 501 @Override 502 protected void publishResults(CharSequence constraint, FilterResults results) { 503 //noinspection unchecked 504 mObjects = (List<T>) results.values; 505 if (results.count > 0) { 506 notifyDataSetChanged(); 507 } else { 508 notifyDataSetInvalidated(); 509 } 510 } 511 } 512} 513