Home | History | Annotate | Download | only in widget
      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.Collection;
     28 import java.util.Collections;
     29 import java.util.Comparator;
     30 import java.util.List;
     31 
     32 /**
     33  * A concrete BaseAdapter that is 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  * <p>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  * <p>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  */
     47 public 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     // 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.
     91     private ArrayList<T> mOriginalValues;
     92     private ArrayFilter mFilter;
     93 
     94     private LayoutInflater mInflater;
     95 
     96     /**
     97      * Constructor
     98      *
     99      * @param context The current context.
    100      * @param resource The resource ID for a layout file containing a TextView to use when
    101      *                 instantiating views.
    102      */
    103     public ArrayAdapter(Context context, int resource) {
    104         init(context, resource, 0, new ArrayList<T>());
    105     }
    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.
    123      * @param resource The resource ID for a layout file containing a TextView to use when
    124      *                 instantiating views.
    125      * @param objects The objects to represent in the ListView.
    126      */
    127     public ArrayAdapter(Context context, int resource, T[] objects) {
    128         init(context, resource, 0, Arrays.asList(objects));
    129     }
    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.
    148      * @param resource The resource ID for a layout file containing a TextView to use when
    149      *                 instantiating views.
    150      * @param objects The objects to represent in the ListView.
    151      */
    152     public ArrayAdapter(Context context, int resource, List<T> objects) {
    153         init(context, resource, 0, objects);
    154     }
    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) {
    175         synchronized (mLock) {
    176             if (mOriginalValues != null) {
    177                 mOriginalValues.add(object);
    178             } else {
    179                 mObjects.add(object);
    180             }
    181         }
    182         if (mNotifyOnChange) notifyDataSetChanged();
    183     }
    184 
    185     /**
    186      * 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) {
    191         synchronized (mLock) {
    192             if (mOriginalValues != null) {
    193                 mOriginalValues.addAll(collection);
    194             } else {
    195                 mObjects.addAll(collection);
    196             }
    197         }
    198         if (mNotifyOnChange) notifyDataSetChanged();
    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         synchronized (mLock) {
    208             if (mOriginalValues != null) {
    209                 Collections.addAll(mOriginalValues, items);
    210             } else {
    211                 Collections.addAll(mObjects, items);
    212             }
    213         }
    214         if (mNotifyOnChange) notifyDataSetChanged();
    215     }
    216 
    217     /**
    218      * 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) {
    224         synchronized (mLock) {
    225             if (mOriginalValues != null) {
    226                 mOriginalValues.add(index, object);
    227             } else {
    228                 mObjects.add(index, object);
    229             }
    230         }
    231         if (mNotifyOnChange) notifyDataSetChanged();
    232     }
    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) {
    240         synchronized (mLock) {
    241             if (mOriginalValues != null) {
    242                 mOriginalValues.remove(object);
    243             } else {
    244                 mObjects.remove(object);
    245             }
    246         }
    247         if (mNotifyOnChange) notifyDataSetChanged();
    248     }
    249 
    250     /**
    251      * Remove all elements from the list.
    252      */
    253     public void clear() {
    254         synchronized (mLock) {
    255             if (mOriginalValues != null) {
    256                 mOriginalValues.clear();
    257             } else {
    258                 mObjects.clear();
    259             }
    260         }
    261         if (mNotifyOnChange) notifyDataSetChanged();
    262     }
    263 
    264     /**
    265      * 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) {
    271         synchronized (mLock) {
    272             if (mOriginalValues != null) {
    273                 Collections.sort(mOriginalValues, comparator);
    274             } else {
    275                 Collections.sort(mObjects, comparator);
    276             }
    277         }
    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                 ArrayList<T> list;
    462                 synchronized (mLock) {
    463                     list = new ArrayList<T>(mOriginalValues);
    464                 }
    465                 results.values = list;
    466                 results.count = list.size();
    467             } else {
    468                 String prefixString = prefix.toString().toLowerCase();
    469 
    470                 ArrayList<T> values;
    471                 synchronized (mLock) {
    472                     values = new ArrayList<T>(mOriginalValues);
    473                 }
    474 
    475                 final int count = values.size();
    476                 final ArrayList<T> newValues = new ArrayList<T>();
    477 
    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 
    489                         // Start at index 0, in case valueText starts with space(s)
    490                         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 }
    518