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