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