ArrayAdapter.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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.List; 28 29/** 30 * A ListAdapter that manages a ListView backed by an array of arbitrary 31 * objects. By default this class expects that the provided resource id referecnes 32 * a single TextView. If you want to use a more complex layout, use the constructors that 33 * also takes a field id. That field id should reference a TextView in the larger layout 34 * resource. 35 * 36 * However the TextView is referenced, it will be filled with the toString() of each object in 37 * the array. You can add lists or arrays of custom objects. Override the toString() method 38 * of your objects to determine what text will be displayed for the item in the list. 39 * 40 * To use something other than TextViews for the array display, for instance, ImageViews, 41 * or to have some of data besides toString() results fill the views, 42 * override {@link #getView(int, View, ViewGroup)} to return the type of view you want. 43 */ 44public class ArrayAdapter<T> extends BaseAdapter implements Filterable { 45 /** 46 * Contains the list of objects that represent the data of this ArrayAdapter. 47 * The content of this list is referred to as "the array" in the documentation. 48 */ 49 private List<T> mObjects; 50 51 /** 52 * Lock used to modify the content of {@link #mObjects}. Any write operation 53 * performed on the array should be synchronized on this lock. This lock is also 54 * used by the filter (see {@link #getFilter()} to make a synchronized copy of 55 * the original array of data. 56 */ 57 private final Object mLock = new Object(); 58 59 /** 60 * The resource indicating what views to inflate to display the content of this 61 * array adapter. 62 */ 63 private int mResource; 64 65 /** 66 * The resource indicating what views to inflate to display the content of this 67 * array adapter in a drop down widget. 68 */ 69 private int mDropDownResource; 70 71 /** 72 * If the inflated resource is not a TextView, {@link #mFieldId} is used to find 73 * a TextView inside the inflated views hierarchy. This field must contain the 74 * identifier that matches the one defined in the resource file. 75 */ 76 private int mFieldId = 0; 77 78 /** 79 * Indicates whether or not {@link #notifyDataSetChanged()} must be called whenever 80 * {@link #mObjects} is modified. 81 */ 82 private boolean mNotifyOnChange = true; 83 84 private Context mContext; 85 86 private ArrayList<T> mOriginalValues; 87 private ArrayFilter mFilter; 88 89 private LayoutInflater mInflater; 90 91 /** 92 * Constructor 93 * 94 * @param context The current context. 95 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when 96 * instantiating views. 97 */ 98 public ArrayAdapter(Context context, int textViewResourceId) { 99 init(context, textViewResourceId, 0, new ArrayList<T>()); 100 } 101 102 /** 103 * Constructor 104 * 105 * @param context The current context. 106 * @param resource The resource ID for a layout file containing a layout to use when 107 * instantiating views. 108 * @param textViewResourceId The id of the TextView within the layout resource to be populated 109 */ 110 public ArrayAdapter(Context context, int resource, int textViewResourceId) { 111 init(context, resource, textViewResourceId, new ArrayList<T>()); 112 } 113 114 /** 115 * Constructor 116 * 117 * @param context The current context. 118 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when 119 * instantiating views. 120 * @param objects The objects to represent in the ListView. 121 */ 122 public ArrayAdapter(Context context, int textViewResourceId, T[] objects) { 123 init(context, textViewResourceId, 0, Arrays.asList(objects)); 124 } 125 126 /** 127 * Constructor 128 * 129 * @param context The current context. 130 * @param resource The resource ID for a layout file containing a layout to use when 131 * instantiating views. 132 * @param textViewResourceId The id of the TextView within the layout resource to be populated 133 * @param objects The objects to represent in the ListView. 134 */ 135 public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) { 136 init(context, resource, textViewResourceId, Arrays.asList(objects)); 137 } 138 139 /** 140 * Constructor 141 * 142 * @param context The current context. 143 * @param textViewResourceId The resource ID for a layout file containing a TextView to use when 144 * instantiating views. 145 * @param objects The objects to represent in the ListView. 146 */ 147 public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) { 148 init(context, textViewResourceId, 0, objects); 149 } 150 151 /** 152 * Constructor 153 * 154 * @param context The current context. 155 * @param resource The resource ID for a layout file containing a layout to use when 156 * instantiating views. 157 * @param textViewResourceId The id of the TextView within the layout resource to be populated 158 * @param objects The objects to represent in the ListView. 159 */ 160 public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) { 161 init(context, resource, textViewResourceId, objects); 162 } 163 164 /** 165 * Adds the specified object at the end of the array. 166 * 167 * @param object The object to add at the end of the array. 168 */ 169 public void add(T object) { 170 if (mOriginalValues != null) { 171 synchronized (mLock) { 172 mOriginalValues.add(object); 173 if (mNotifyOnChange) notifyDataSetChanged(); 174 } 175 } else { 176 mObjects.add(object); 177 if (mNotifyOnChange) notifyDataSetChanged(); 178 } 179 } 180 181 /** 182 * Inserts the spcified object at the specified index in the array. 183 * 184 * @param object The object to insert into the array. 185 * @param index The index at which the object must be inserted. 186 */ 187 public void insert(T object, int index) { 188 if (mOriginalValues != null) { 189 synchronized (mLock) { 190 mOriginalValues.add(index, object); 191 if (mNotifyOnChange) notifyDataSetChanged(); 192 } 193 } else { 194 mObjects.add(index, object); 195 if (mNotifyOnChange) notifyDataSetChanged(); 196 } 197 } 198 199 /** 200 * Removes the specified object from the array. 201 * 202 * @param object The object to remove. 203 */ 204 public void remove(T object) { 205 if (mOriginalValues != null) { 206 synchronized (mLock) { 207 mOriginalValues.remove(object); 208 } 209 } else { 210 mObjects.remove(object); 211 } 212 if (mNotifyOnChange) notifyDataSetChanged(); 213 } 214 215 /** 216 * Remove all elements from the list. 217 */ 218 public void clear() { 219 if (mOriginalValues != null) { 220 synchronized (mLock) { 221 mOriginalValues.clear(); 222 } 223 } else { 224 mObjects.clear(); 225 } 226 if (mNotifyOnChange) notifyDataSetChanged(); 227 } 228 229 /** 230 * {@inheritDoc} 231 */ 232 @Override 233 public void notifyDataSetChanged() { 234 super.notifyDataSetChanged(); 235 mNotifyOnChange = true; 236 } 237 238 /** 239 * Control whether methods that change the list ({@link #add}, 240 * {@link #insert}, {@link #remove}, {@link #clear}) automatically call 241 * {@link #notifyDataSetChanged}. If set to false, caller must 242 * manually call notifyDataSetChanged() to have the changes 243 * reflected in the attached view. 244 * 245 * The default is true, and calling notifyDataSetChanged() 246 * resets the flag to true. 247 * 248 * @param notifyOnChange if true, modifications to the list will 249 * automatically call {@link 250 * #notifyDataSetChanged} 251 */ 252 public void setNotifyOnChange(boolean notifyOnChange) { 253 mNotifyOnChange = notifyOnChange; 254 } 255 256 private void init(Context context, int resource, int textViewResourceId, List<T> objects) { 257 mContext = context; 258 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 259 mResource = mDropDownResource = resource; 260 mObjects = objects; 261 mFieldId = textViewResourceId; 262 } 263 264 /** 265 * Returns the context associated with this array adapter. The context is used 266 * to create views from the resource passed to the constructor. 267 * 268 * @return The Context associated with this adapter. 269 */ 270 public Context getContext() { 271 return mContext; 272 } 273 274 /** 275 * {@inheritDoc} 276 */ 277 public int getCount() { 278 return mObjects.size(); 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 public T getItem(int position) { 285 return mObjects.get(position); 286 } 287 288 /** 289 * Returns the position of the specified item in the array. 290 * 291 * @param item The item to retrieve the position of. 292 * 293 * @return The position of the specified item. 294 */ 295 public int getPosition(T item) { 296 return mObjects.indexOf(item); 297 } 298 299 /** 300 * {@inheritDoc} 301 */ 302 public long getItemId(int position) { 303 return position; 304 } 305 306 /** 307 * {@inheritDoc} 308 */ 309 public View getView(int position, View convertView, ViewGroup parent) { 310 return createViewFromResource(position, convertView, parent, mResource); 311 } 312 313 private View createViewFromResource(int position, View convertView, ViewGroup parent, 314 int resource) { 315 View view; 316 TextView text; 317 318 if (convertView == null) { 319 view = mInflater.inflate(resource, parent, false); 320 } else { 321 view = convertView; 322 } 323 324 try { 325 if (mFieldId == 0) { 326 // If no custom field is assigned, assume the whole resource is a TextView 327 text = (TextView) view; 328 } else { 329 // Otherwise, find the TextView field within the layout 330 text = (TextView) view.findViewById(mFieldId); 331 } 332 } catch (ClassCastException e) { 333 Log.e("ArrayAdapter", "You must supply a resource ID for a TextView"); 334 throw new IllegalStateException( 335 "ArrayAdapter requires the resource ID to be a TextView", e); 336 } 337 338 text.setText(getItem(position).toString()); 339 340 return view; 341 } 342 343 /** 344 * <p>Sets the layout resource to create the drop down views.</p> 345 * 346 * @param resource the layout resource defining the drop down views 347 * @see #getDropDownView(int, android.view.View, android.view.ViewGroup) 348 */ 349 public void setDropDownViewResource(int resource) { 350 this.mDropDownResource = resource; 351 } 352 353 /** 354 * {@inheritDoc} 355 */ 356 @Override 357 public View getDropDownView(int position, View convertView, ViewGroup parent) { 358 return createViewFromResource(position, convertView, parent, mDropDownResource); 359 } 360 361 /** 362 * Creates a new ArrayAdapter from external resources. The content of the array is 363 * obtained through {@link android.content.res.Resources#getTextArray(int)}. 364 * 365 * @param context The application's environment. 366 * @param textArrayResId The identifier of the array to use as the data source. 367 * @param textViewResId The identifier of the layout used to create views. 368 * 369 * @return An ArrayAdapter<CharSequence>. 370 */ 371 public static ArrayAdapter<CharSequence> createFromResource(Context context, 372 int textArrayResId, int textViewResId) { 373 CharSequence[] strings = context.getResources().getTextArray(textArrayResId); 374 return new ArrayAdapter<CharSequence>(context, textViewResId, strings); 375 } 376 377 /** 378 * {@inheritDoc} 379 */ 380 public Filter getFilter() { 381 if (mFilter == null) { 382 mFilter = new ArrayFilter(); 383 } 384 return mFilter; 385 } 386 387 /** 388 * <p>An array filters constrains the content of the array adapter with 389 * a prefix. Each item that does not start with the supplied prefix 390 * is removed from the list.</p> 391 */ 392 private class ArrayFilter extends Filter { 393 @Override 394 protected FilterResults performFiltering(CharSequence prefix) { 395 FilterResults results = new FilterResults(); 396 397 if (mOriginalValues == null) { 398 synchronized (mLock) { 399 mOriginalValues = new ArrayList<T>(mObjects); 400 } 401 } 402 403 if (prefix == null || prefix.length() == 0) { 404 synchronized (mLock) { 405 ArrayList<T> list = new ArrayList<T>(mOriginalValues); 406 results.values = list; 407 results.count = list.size(); 408 } 409 } else { 410 String prefixString = prefix.toString().toLowerCase(); 411 412 final ArrayList<T> values = mOriginalValues; 413 final int count = values.size(); 414 415 final ArrayList<T> newValues = new ArrayList<T>(count); 416 417 for (int i = 0; i < count; i++) { 418 final T value = values.get(i); 419 final String valueText = value.toString().toLowerCase(); 420 421 // First match against the whole, non-splitted value 422 if (valueText.startsWith(prefixString)) { 423 newValues.add(value); 424 } else { 425 final String[] words = valueText.split(" "); 426 final int wordCount = words.length; 427 428 for (int k = 0; k < wordCount; k++) { 429 if (words[k].startsWith(prefixString)) { 430 newValues.add(value); 431 break; 432 } 433 } 434 } 435 } 436 437 results.values = newValues; 438 results.count = newValues.size(); 439 } 440 441 return results; 442 } 443 444 @Override 445 protected void publishResults(CharSequence constraint, FilterResults results) { 446 //noinspection unchecked 447 mObjects = (List<T>) results.values; 448 if (results.count > 0) { 449 notifyDataSetChanged(); 450 } else { 451 notifyDataSetInvalidated(); 452 } 453 } 454 } 455} 456