blob: c1bcd4219c58eb6ffd413eb94b15cb079a2bc606 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.widget;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.net.Uri;
22import android.view.View;
23import android.view.ViewGroup;
24
25/**
26 * An easy adapter to map columns from a cursor to TextViews or ImageViews
27 * defined in an XML file. You can specify which columns you want, which
28 * views you want to display the columns, and the XML file that defines
29 * the appearance of these views.
30 *
31 * Binding occurs in two phases. First, if a
32 * {@link android.widget.SimpleCursorAdapter.ViewBinder} is available,
33 * {@link ViewBinder#setViewValue(android.view.View, android.database.Cursor, int)}
34 * is invoked. If the returned value is true, binding has occured. If the
35 * returned value is false and the view to bind is a TextView,
36 * {@link #setViewText(TextView, String)} is invoked. If the returned value
37 * is false and the view to bind is an ImageView,
38 * {@link #setViewImage(ImageView, String)} is invoked. If no appropriate
39 * binding can be found, an {@link IllegalStateException} is thrown.
40 *
41 * If this adapter is used with filtering, for instance in an
42 * {@link android.widget.AutoCompleteTextView}, you can use the
43 * {@link android.widget.SimpleCursorAdapter.CursorToStringConverter} and the
44 * {@link android.widget.FilterQueryProvider} interfaces
45 * to get control over the filtering process. You can refer to
46 * {@link #convertToString(android.database.Cursor)} and
47 * {@link #runQueryOnBackgroundThread(CharSequence)} for more information.
48 */
49public class SimpleCursorAdapter extends ResourceCursorAdapter {
50 /**
51 * A list of columns containing the data to bind to the UI.
52 * This field should be made private, so it is hidden from the SDK.
53 * {@hide}
54 */
55 protected int[] mFrom;
56 /**
57 * A list of View ids representing the views to which the data must be bound.
58 * This field should be made private, so it is hidden from the SDK.
59 * {@hide}
60 */
61 protected int[] mTo;
62
63 private int mStringConversionColumn = -1;
64 private CursorToStringConverter mCursorToStringConverter;
65 private ViewBinder mViewBinder;
66 private String[] mOriginalFrom;
67
68 /**
69 * Constructor.
70 *
71 * @param context The context where the ListView associated with this
72 * SimpleListItemFactory is running
73 * @param layout resource identifier of a layout file that defines the views
74 * for this list item. Thelayout file should include at least
75 * those named views defined in "to"
76 * @param c The database cursor. Can be null if the cursor is not available yet.
77 * @param from A list of column names representing the data to bind to the UI. Can be null
78 * if the cursor is not available yet.
79 * @param to The views that should display column in the "from" parameter.
80 * These should all be TextViews. The first N views in this list
81 * are given the values of the first N columns in the from
82 * parameter. Can be null if the cursor is not available yet.
83 */
84 public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
85 super(context, layout, c);
86 mTo = to;
87 mOriginalFrom = from;
88 findColumns(from);
89 }
90
91 @Override
92 public View newView(Context context, Cursor cursor, ViewGroup parent) {
93 return generateViewHolder(super.newView(context, cursor, parent));
94 }
95
96 @Override
97 public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
98 return generateViewHolder(super.newDropDownView(context, cursor, parent));
99 }
100
101 private View generateViewHolder(View v) {
102 final int[] to = mTo;
103 final int count = to.length;
104 final View[] holder = new View[count];
105
106 for (int i = 0; i < count; i++) {
107 holder[i] = v.findViewById(to[i]);
108 }
109 v.setTag(holder);
110
111 return v;
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
139 public void bindView(View view, Context context, Cursor cursor) {
140 final View[] holder = (View[]) view.getTag();
141 final ViewBinder binder = mViewBinder;
142 final int count = mTo.length;
143 final int[] from = mFrom;
144
145 for (int i = 0; i < count; i++) {
146 final View v = holder[i];
147 if (v != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 boolean bound = false;
149 if (binder != null) {
150 bound = binder.setViewValue(v, cursor, from[i]);
151 }
152
153 if (!bound) {
Romain Guy604ed542009-03-27 16:58:23 -0700154 String text = cursor.getString(from[i]);
155 if (text == null) {
156 text = "";
157 }
158
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 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 */
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 */
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 */
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 an 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 */
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 */
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 */
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 */
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 */
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
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 mCursor.
324 * If mCursor is null, the array will be discarded.
325 *
326 * @param from the Strings naming the columns of interest
327 */
328 private void findColumns(String[] from) {
329 if (mCursor != null) {
330 int i;
331 int count = from.length;
332 if (mFrom == null || mFrom.length != count) {
333 mFrom = new int[count];
334 }
335 for (i = 0; i < count; i++) {
336 mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
337 }
338 } else {
339 mFrom = null;
340 }
341 }
342
343 @Override
344 public void changeCursor(Cursor c) {
345 super.changeCursor(c);
346 // rescan columns in case cursor layout is different
347 findColumns(mOriginalFrom);
348 }
349
350 /**
351 * Change the cursor and change the column-to-view mappings at the same time.
352 *
353 * @param c The database cursor. Can be null if the cursor is not available yet.
354 * @param from A list of column names representing the data to bind to the UI. Can be null
355 * if the cursor is not available yet.
356 * @param to The views that should display column in the "from" parameter.
357 * These should all be TextViews. The first N views in this list
358 * are given the values of the first N columns in the from
359 * parameter. Can be null if the cursor is not available yet.
360 */
361 public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
362 mOriginalFrom = from;
363 mTo = to;
364 super.changeCursor(c);
365 findColumns(mOriginalFrom);
366 }
367
368 /**
369 * This class can be used by external clients of SimpleCursorAdapter
370 * to bind values fom the Cursor to views.
371 *
372 * You should use this class to bind values from the Cursor to views
373 * that are not directly supported by SimpleCursorAdapter or to
374 * change the way binding occurs for views supported by
375 * SimpleCursorAdapter.
376 *
377 * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
378 * @see SimpleCursorAdapter#setViewImage(ImageView, String)
379 * @see SimpleCursorAdapter#setViewText(TextView, String)
380 */
381 public static interface ViewBinder {
382 /**
383 * Binds the Cursor column defined by the specified index to the specified view.
384 *
385 * When binding is handled by this ViewBinder, this method must return true.
386 * If this method returns false, SimpleCursorAdapter will attempts to handle
387 * the binding on its own.
388 *
389 * @param view the view to bind the data to
390 * @param cursor the cursor to get the data from
391 * @param columnIndex the column at which the data can be found in the cursor
392 *
393 * @return true if the data was bound to the view, false otherwise
394 */
395 boolean setViewValue(View view, Cursor cursor, int columnIndex);
396 }
397
398 /**
399 * This class can be used by external clients of SimpleCursorAdapter
400 * to define how the Cursor should be converted to a String.
401 *
402 * @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
403 */
404 public static interface CursorToStringConverter {
405 /**
406 * Returns a CharSequence representing the specified Cursor.
407 *
408 * @param cursor the cursor for which a CharSequence representation
409 * is requested
410 *
411 * @return a non-null CharSequence representing the cursor
412 */
413 CharSequence convertToString(Cursor cursor);
414 }
415
416}