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