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