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.database.Cursor;
     21 import android.net.Uri;
     22 import android.view.View;
     23 
     24 /**
     25  * An easy adapter to map columns from a cursor to TextViews or ImageViews
     26  * defined in an XML file. You can specify which columns you want, which
     27  * views you want to display the columns, and the XML file that defines
     28  * the appearance of these views.
     29  *
     30  * Binding occurs in two phases. First, if a
     31  * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
     32  * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
     33  * is invoked. If the returned value is true, binding has occured. If the
     34  * returned value is false and the view to bind is a TextView,
     35  * {@link #setViewText(TextView, String)} is invoked. If the returned value
     36  * is false and the view to bind is an ImageView,
     37  * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
     38  * binding can be found, an {@link IllegalStateException} is thrown.
     39  *
     40  * If this adapter is used with filtering, for instance in an
     41  * {@link android.widget.AutoCompleteTextView}, you can use the
     42  * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
     43  * {@link android.widget.FilterQueryProvider} interfaces
     44  * to get control over the filtering process. You can refer to
     45  * {@link #convertToString(android.database.Cursor)} and
     46  * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
     47  */
     48 public class SimpleCursorAdapter extends ResourceCursorAdapter {
     49     /**
     50      * A list of columns containing the data to bind to the UI.
     51      * This field should be made private, so it is hidden from the SDK.
     52      * {@hide}
     53      */
     54     protected int[] mFrom;
     55     /**
     56      * A list of View ids representing the views to which the data must be bound.
     57      * This field should be made private, so it is hidden from the SDK.
     58      * {@hide}
     59      */
     60     protected int[] mTo;
     61 
     62     private int mStringConversionColumn = -1;
     63     private CursorToStringConverter mCursorToStringConverter;
     64     private ViewBinder mViewBinder;
     65 
     66     String[] mOriginalFrom;
     67 
     68     /**
     69      * Constructor the enables auto-requery.
     70      *
     71      * @deprecated This option is discouraged, as it results in Cursor queries
     72      * being performed on the application's UI thread and thus can cause poor
     73      * responsiveness or even Application Not Responding errors.  As an alternative,
     74      * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
     75      */
     76     @Deprecated
     77     public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
     78         super(context, layout, c);
     79         mTo = to;
     80         mOriginalFrom = from;
     81         findColumns(from);
     82     }
     83 
     84     /**
     85      * Standard constructor.
     86      *
     87      * @param context The context where the ListView associated with this
     88      *            SimpleListItemFactory is running
     89      * @param layout resource identifier of a layout file that defines the views
     90      *            for this list item. The layout file should include at least
     91      *            those named views defined in "to"
     92      * @param c The database cursor.  Can be null if the cursor is not available yet.
     93      * @param from A list of column names representing the data to bind to the UI.  Can be null
     94      *            if the cursor is not available yet.
     95      * @param to The views that should display column in the "from" parameter.
     96      *            These should all be TextViews. The first N views in this list
     97      *            are given the values of the first N columns in the from
     98      *            parameter.  Can be null if the cursor is not available yet.
     99      * @param flags Flags used to determine the behavior of the adapter,
    100      * as per {@link CursorAdapter#CursorAdapter(Context, Cursor, int)}.
    101      */
    102     public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from,
    103             int[] to, int flags) {
    104         super(context, layout, c, flags);
    105         mTo = to;
    106         mOriginalFrom = from;
    107         findColumns(from);
    108     }
    109 
    110     /**
    111      * Binds all of the field names passed into the "to" parameter of the
    112      * constructor with their corresponding cursor columns as specified in the
    113      * "from" parameter.
    114      *
    115      * Binding occurs in two phases. First, if a
    116      * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
    117      * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
    118      * is invoked. If the returned value is true, binding has occured. If the
    119      * returned value is false and the view to bind is a TextView,
    120      * {@link #setViewText(TextView, String)} is invoked. If the returned value is
    121      * false and the view to bind is an ImageView,
    122      * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
    123      * binding can be found, an {@link IllegalStateException} is thrown.
    124      *
    125      * @throws IllegalStateException if binding cannot occur
    126      *
    127      * @see android.widget.CursorAdapter#bindView(android.view.View,
    128      *      android.content.Context, android.database.Cursor)
    129      * @see #getViewBinder()
    130      * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
    131      * @see #setViewImage(ImageView, String)
    132      * @see #setViewText(TextView, String)
    133      */
    134     @Override
    135     public void bindView(View view, Context context, Cursor cursor) {
    136         final ViewBinder binder = mViewBinder;
    137         final int count = mTo.length;
    138         final int[] from = mFrom;
    139         final int[] to = mTo;
    140 
    141         for (int i = 0; i < count; i++) {
    142             final View v = view.findViewById(to[i]);
    143             if (v != null) {
    144                 boolean bound = false;
    145                 if (binder != null) {
    146                     bound = binder.setViewValue(v, cursor, from[i]);
    147                 }
    148 
    149                 if (!bound) {
    150                     String text = cursor.getString(from[i]);
    151                     if (text == null) {
    152                         text = "";
    153                     }
    154 
    155                     if (v instanceof TextView) {
    156                         setViewText((TextView) v, text);
    157                     } else if (v instanceof ImageView) {
    158                         setViewImage((ImageView) v, text);
    159                     } else {
    160                         throw new IllegalStateException(v.getClass().getName() + " is not a " +
    161                                 " view that can be bounds by this SimpleCursorAdapter");
    162                     }
    163                 }
    164             }
    165         }
    166     }
    167 
    168     /**
    169      * Returns the {@link ViewBinder} used to bind data to views.
    170      *
    171      * @return a ViewBinder or null if the binder does not exist
    172      *
    173      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
    174      * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
    175      */
    176     public ViewBinder getViewBinder() {
    177         return mViewBinder;
    178     }
    179 
    180     /**
    181      * Sets the binder used to bind data to views.
    182      *
    183      * @param viewBinder the binder used to bind data to views, can be null to
    184      *        remove the existing binder
    185      *
    186      * @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
    187      * @see #getViewBinder()
    188      */
    189     public void setViewBinder(ViewBinder viewBinder) {
    190         mViewBinder = viewBinder;
    191     }
    192 
    193     /**
    194      * Called by bindView() to set the image for an ImageView but only if
    195      * there is no existing ViewBinder or if the existing ViewBinder cannot
    196      * handle binding to an ImageView.
    197      *
    198      * By default, the value will be treated as an image resource. If the
    199      * value cannot be used as an image resource, the value is used as an
    200      * image Uri.
    201      *
    202      * Intended to be overridden by Adapters that need to filter strings
    203      * retrieved from the database.
    204      *
    205      * @param v ImageView to receive an image
    206      * @param value the value retrieved from the cursor
    207      */
    208     public void setViewImage(ImageView v, String value) {
    209         try {
    210             v.setImageResource(Integer.parseInt(value));
    211         } catch (NumberFormatException nfe) {
    212             v.setImageURI(Uri.parse(value));
    213         }
    214     }
    215 
    216     /**
    217      * Called by bindView() to set the text for a TextView but only if
    218      * there is no existing ViewBinder or if the existing ViewBinder cannot
    219      * handle binding to an TextView.
    220      *
    221      * Intended to be overridden by Adapters that need to filter strings
    222      * retrieved from the database.
    223      *
    224      * @param v TextView to receive text
    225      * @param text the text to be set for the TextView
    226      */
    227     public void setViewText(TextView v, String text) {
    228         v.setText(text);
    229     }
    230 
    231     /**
    232      * Return the index of the column used to get a String representation
    233      * of the Cursor.
    234      *
    235      * @return a valid index in the current Cursor or -1
    236      *
    237      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
    238      * @see #setStringConversionColumn(int)
    239      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
    240      * @see #getCursorToStringConverter()
    241      */
    242     public int getStringConversionColumn() {
    243         return mStringConversionColumn;
    244     }
    245 
    246     /**
    247      * Defines the index of the column in the Cursor used to get a String
    248      * representation of that Cursor. The column is used to convert the
    249      * Cursor to a String only when the current CursorToStringConverter
    250      * is null.
    251      *
    252      * @param stringConversionColumn a valid index in the current Cursor or -1 to use the default
    253      *        conversion mechanism
    254      *
    255      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
    256      * @see #getStringConversionColumn()
    257      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
    258      * @see #getCursorToStringConverter()
    259      */
    260     public void setStringConversionColumn(int stringConversionColumn) {
    261         mStringConversionColumn = stringConversionColumn;
    262     }
    263 
    264     /**
    265      * Returns the converter used to convert the filtering Cursor
    266      * into a String.
    267      *
    268      * @return null if the converter does not exist or an instance of
    269      *         {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
    270      *
    271      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
    272      * @see #getStringConversionColumn()
    273      * @see #setStringConversionColumn(int)
    274      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
    275      */
    276     public CursorToStringConverter getCursorToStringConverter() {
    277         return mCursorToStringConverter;
    278     }
    279 
    280     /**
    281      * Sets the converter  used to convert the filtering Cursor
    282      * into a String.
    283      *
    284      * @param cursorToStringConverter the Cursor to String converter, or
    285      *        null to remove the converter
    286      *
    287      * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
    288      * @see #getStringConversionColumn()
    289      * @see #setStringConversionColumn(int)
    290      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
    291      */
    292     public void setCursorToStringConverter(CursorToStringConverter cursorToStringConverter) {
    293         mCursorToStringConverter = cursorToStringConverter;
    294     }
    295 
    296     /**
    297      * Returns a CharSequence representation of the specified Cursor as defined
    298      * by the current CursorToStringConverter. If no CursorToStringConverter
    299      * has been set, the String conversion column is used instead. If the
    300      * conversion column is -1, the returned String is empty if the cursor
    301      * is null or Cursor.toString().
    302      *
    303      * @param cursor the Cursor to convert to a CharSequence
    304      *
    305      * @return a non-null CharSequence representing the cursor
    306      */
    307     @Override
    308     public CharSequence convertToString(Cursor cursor) {
    309         if (mCursorToStringConverter != null) {
    310             return mCursorToStringConverter.convertToString(cursor);
    311         } else if (mStringConversionColumn > -1) {
    312             return cursor.getString(mStringConversionColumn);
    313         }
    314 
    315         return super.convertToString(cursor);
    316     }
    317 
    318     /**
    319      * Create a map from an array of strings to an array of column-id integers in mCursor.
    320      * If mCursor is null, the array will be discarded.
    321      *
    322      * @param from the Strings naming the columns of interest
    323      */
    324     private void findColumns(String[] from) {
    325         if (mCursor != null) {
    326             int i;
    327             int count = from.length;
    328             if (mFrom == null || mFrom.length != count) {
    329                 mFrom = new int[count];
    330             }
    331             for (i = 0; i < count; i++) {
    332                 mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
    333             }
    334         } else {
    335             mFrom = null;
    336         }
    337     }
    338 
    339     @Override
    340     public Cursor swapCursor(Cursor c) {
    341         // super.swapCursor() will notify observers before we have
    342         // a valid mapping, make sure we have a mapping before this
    343         // happens
    344         if (mFrom == null) {
    345             findColumns(mOriginalFrom);
    346         }
    347         Cursor res = super.swapCursor(c);
    348         // rescan columns in case cursor layout is different
    349         findColumns(mOriginalFrom);
    350         return res;
    351     }
    352 
    353     /**
    354      * Change the cursor and change the column-to-view mappings at the same time.
    355      *
    356      * @param c The database cursor.  Can be null if the cursor is not available yet.
    357      * @param from A list of column names representing the data to bind to the UI.  Can be null
    358      *            if the cursor is not available yet.
    359      * @param to The views that should display column in the "from" parameter.
    360      *            These should all be TextViews. The first N views in this list
    361      *            are given the values of the first N columns in the from
    362      *            parameter.  Can be null if the cursor is not available yet.
    363      */
    364     public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
    365         mOriginalFrom = from;
    366         mTo = to;
    367         // super.changeCursor() will notify observers before we have
    368         // a valid mapping, make sure we have a mapping before this
    369         // happens
    370         if (mFrom == null) {
    371             findColumns(mOriginalFrom);
    372         }
    373         super.changeCursor(c);
    374         findColumns(mOriginalFrom);
    375     }
    376 
    377     /**
    378      * This class can be used by external clients of SimpleCursorAdapter
    379      * to bind values fom the Cursor to views.
    380      *
    381      * You should use this class to bind values from the Cursor to views
    382      * that are not directly supported by SimpleCursorAdapter or to
    383      * change the way binding occurs for views supported by
    384      * SimpleCursorAdapter.
    385      *
    386      * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
    387      * @see SimpleCursorAdapter#setViewImage(ImageView, String)
    388      * @see SimpleCursorAdapter#setViewText(TextView, String)
    389      */
    390     public static interface ViewBinder {
    391         /**
    392          * Binds the Cursor column defined by the specified index to the specified view.
    393          *
    394          * When binding is handled by this ViewBinder, this method must return true.
    395          * If this method returns false, SimpleCursorAdapter will attempts to handle
    396          * the binding on its own.
    397          *
    398          * @param view the view to bind the data to
    399          * @param cursor the cursor to get the data from
    400          * @param columnIndex the column at which the data can be found in the cursor
    401          *
    402          * @return true if the data was bound to the view, false otherwise
    403          */
    404         boolean setViewValue(View view, Cursor cursor, int columnIndex);
    405     }
    406 
    407     /**
    408      * This class can be used by external clients of SimpleCursorAdapter
    409      * to define how the Cursor should be converted to a String.
    410      *
    411      * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
    412      */
    413     public static interface CursorToStringConverter {
    414         /**
    415          * Returns a CharSequence representing the specified Cursor.
    416          *
    417          * @param cursor the cursor for which a CharSequence representation
    418          *        is requested
    419          *
    420          * @return a non-null CharSequence representing the cursor
    421          */
    422         CharSequence convertToString(Cursor cursor);
    423     }
    424 
    425 }
    426