blob: 98bcfff6b0e59cbae3868b2313199bd7ade209e4 [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.view.View;
21import android.view.ViewGroup;
22import android.view.LayoutInflater;
23import android.net.Uri;
24
25import java.util.ArrayList;
26import java.util.List;
27import java.util.Map;
28
29/**
30 * An easy adapter to map static data to views defined in an XML file. You can specify the data
31 * backing the list as an ArrayList of Maps. Each entry in the ArrayList corresponds to one row
32 * in the list. The Maps contain the data for each row. You also specify an XML file that
33 * defines the views used to display the row, and a mapping from keys in the Map to specific
34 * views.
35 *
36 * Binding data to views occurs in two phases. First, if a
37 * {@link android.widget.SimpleAdapter.ViewBinder} is available,
38 * {@link ViewBinder#setViewValue(android.view.View, Object, String)}
39 * is invoked. If the returned value is true, binding has occurred.
40 * If the returned value is false, the following views are then tried in order:
41 * <ul>
42 * <li> A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean.
43 * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)}
44 * is invoked.
45 * <li> ImageView. The expected bind value is a resource id or a string and
46 * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked.
47 * </ul>
48 * If no appropriate binding can be found, an {@link IllegalStateException} is thrown.
49 */
50public class SimpleAdapter extends BaseAdapter implements Filterable {
51 private int[] mTo;
52 private String[] mFrom;
53 private ViewBinder mViewBinder;
54
55 private List<? extends Map<String, ?>> mData;
56
57 private int mResource;
58 private int mDropDownResource;
59 private LayoutInflater mInflater;
60
61 private SimpleFilter mFilter;
62 private ArrayList<Map<String, ?>> mUnfilteredData;
63
64 /**
65 * Constructor
66 *
67 * @param context The context where the View associated with this SimpleAdapter is running
68 * @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
69 * Maps contain the data for each row, and should include all the entries specified in
70 * "from"
71 * @param resource Resource identifier of a view layout that defines the views for this list
72 * item. The layout file should include at least those named views defined in "to"
73 * @param from A list of column names that will be added to the Map associated with each
74 * item.
75 * @param to The views that should display column in the "from" parameter. These should all be
76 * TextViews. The first N views in this list are given the values of the first N columns
77 * in the from parameter.
78 */
79 public SimpleAdapter(Context context, List<? extends Map<String, ?>> data,
80 int resource, String[] from, int[] to) {
81 mData = data;
82 mResource = mDropDownResource = resource;
83 mFrom = from;
84 mTo = to;
85 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
86 }
87
88
89 /**
90 * @see android.widget.Adapter#getCount()
91 */
92 public int getCount() {
93 return mData.size();
94 }
95
96 /**
97 * @see android.widget.Adapter#getItem(int)
98 */
99 public Object getItem(int position) {
100 return mData.get(position);
101 }
102
103 /**
104 * @see android.widget.Adapter#getItemId(int)
105 */
106 public long getItemId(int position) {
107 return position;
108 }
109
110 /**
111 * @see android.widget.Adapter#getView(int, View, ViewGroup)
112 */
113 public View getView(int position, View convertView, ViewGroup parent) {
114 return createViewFromResource(position, convertView, parent, mResource);
115 }
116
117 private View createViewFromResource(int position, View convertView,
118 ViewGroup parent, int resource) {
119 View v;
120 if (convertView == null) {
121 v = mInflater.inflate(resource, parent, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800122 } else {
123 v = convertView;
124 }
125
126 bindView(position, v);
127
128 return v;
129 }
130
131 /**
132 * <p>Sets the layout resource to create the drop down views.</p>
133 *
134 * @param resource the layout resource defining the drop down views
135 * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
136 */
137 public void setDropDownViewResource(int resource) {
138 this.mDropDownResource = resource;
139 }
140
141 @Override
142 public View getDropDownView(int position, View convertView, ViewGroup parent) {
143 return createViewFromResource(position, convertView, parent, mDropDownResource);
144 }
145
146 private void bindView(int position, View view) {
147 final Map dataSet = mData.get(position);
148 if (dataSet == null) {
149 return;
150 }
151
152 final ViewBinder binder = mViewBinder;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800153 final String[] from = mFrom;
154 final int[] to = mTo;
155 final int count = to.length;
156
157 for (int i = 0; i < count; i++) {
Romain Guyc8ca2a32010-01-04 14:52:38 -0800158 final View v = view.findViewById(to[i]);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 if (v != null) {
160 final Object data = dataSet.get(from[i]);
161 String text = data == null ? "" : data.toString();
162 if (text == null) {
163 text = "";
164 }
165
166 boolean bound = false;
167 if (binder != null) {
168 bound = binder.setViewValue(v, data, text);
169 }
170
171 if (!bound) {
172 if (v instanceof Checkable) {
173 if (data instanceof Boolean) {
174 ((Checkable) v).setChecked((Boolean) data);
Romain Guyb187a842010-03-23 12:42:51 -0700175 } else if (v instanceof TextView) {
176 // Note: keep the instanceof TextView check at the bottom of these
177 // ifs since a lot of views are TextViews (e.g. CheckBoxes).
178 setViewText((TextView) v, text);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 } else {
180 throw new IllegalStateException(v.getClass().getName() +
Romain Guyc8ca2a32010-01-04 14:52:38 -0800181 " should be bound to a Boolean, not a " +
182 (data == null ? "<unknown type>" : data.getClass()));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800183 }
184 } else if (v instanceof TextView) {
185 // Note: keep the instanceof TextView check at the bottom of these
186 // ifs since a lot of views are TextViews (e.g. CheckBoxes).
187 setViewText((TextView) v, text);
188 } else if (v instanceof ImageView) {
189 if (data instanceof Integer) {
190 setViewImage((ImageView) v, (Integer) data);
191 } else {
192 setViewImage((ImageView) v, text);
193 }
194 } else {
195 throw new IllegalStateException(v.getClass().getName() + " is not a " +
196 " view that can be bounds by this SimpleAdapter");
197 }
198 }
199 }
200 }
201 }
202
203 /**
204 * Returns the {@link ViewBinder} used to bind data to views.
205 *
206 * @return a ViewBinder or null if the binder does not exist
207 *
208 * @see #setViewBinder(android.widget.SimpleAdapter.ViewBinder)
209 */
210 public ViewBinder getViewBinder() {
211 return mViewBinder;
212 }
213
214 /**
215 * Sets the binder used to bind data to views.
216 *
217 * @param viewBinder the binder used to bind data to views, can be null to
218 * remove the existing binder
219 *
220 * @see #getViewBinder()
221 */
222 public void setViewBinder(ViewBinder viewBinder) {
223 mViewBinder = viewBinder;
224 }
225
226 /**
227 * Called by bindView() to set the image for an ImageView but only if
228 * there is no existing ViewBinder or if the existing ViewBinder cannot
229 * handle binding to an ImageView.
230 *
231 * This method is called instead of {@link #setViewImage(ImageView, String)}
232 * if the supplied data is an int or Integer.
233 *
234 * @param v ImageView to receive an image
235 * @param value the value retrieved from the data set
236 *
237 * @see #setViewImage(ImageView, String)
238 */
239 public void setViewImage(ImageView v, int value) {
240 v.setImageResource(value);
241 }
242
243 /**
244 * Called by bindView() to set the image for an ImageView but only if
245 * there is no existing ViewBinder or if the existing ViewBinder cannot
246 * handle binding to an ImageView.
247 *
248 * By default, the value will be treated as an image resource. If the
249 * value cannot be used as an image resource, the value is used as an
250 * image Uri.
251 *
252 * This method is called instead of {@link #setViewImage(ImageView, int)}
253 * if the supplied data is not an int or Integer.
254 *
255 * @param v ImageView to receive an image
256 * @param value the value retrieved from the data set
257 *
258 * @see #setViewImage(ImageView, int)
259 */
260 public void setViewImage(ImageView v, String value) {
261 try {
262 v.setImageResource(Integer.parseInt(value));
263 } catch (NumberFormatException nfe) {
264 v.setImageURI(Uri.parse(value));
265 }
266 }
267
268 /**
269 * Called by bindView() to set the text for a TextView but only if
270 * there is no existing ViewBinder or if the existing ViewBinder cannot
Ken Wakasaf76a50c2012-03-09 19:56:35 +0900271 * handle binding to a TextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 *
273 * @param v TextView to receive text
274 * @param text the text to be set for the TextView
275 */
276 public void setViewText(TextView v, String text) {
277 v.setText(text);
278 }
279
280 public Filter getFilter() {
281 if (mFilter == null) {
282 mFilter = new SimpleFilter();
283 }
284 return mFilter;
285 }
286
287 /**
288 * This class can be used by external clients of SimpleAdapter to bind
289 * values to views.
290 *
291 * You should use this class to bind values to views that are not
292 * directly supported by SimpleAdapter or to change the way binding
293 * occurs for views supported by SimpleAdapter.
294 *
295 * @see SimpleAdapter#setViewImage(ImageView, int)
296 * @see SimpleAdapter#setViewImage(ImageView, String)
297 * @see SimpleAdapter#setViewText(TextView, String)
298 */
299 public static interface ViewBinder {
300 /**
301 * Binds the specified data to the specified view.
302 *
303 * When binding is handled by this ViewBinder, this method must return true.
304 * If this method returns false, SimpleAdapter will attempts to handle
305 * the binding on its own.
306 *
307 * @param view the view to bind the data to
308 * @param data the data to bind to the view
309 * @param textRepresentation a safe String representation of the supplied data:
310 * it is either the result of data.toString() or an empty String but it
311 * is never null
312 *
313 * @return true if the data was bound to the view, false otherwise
314 */
315 boolean setViewValue(View view, Object data, String textRepresentation);
316 }
317
318 /**
319 * <p>An array filters constrains the content of the array adapter with
320 * a prefix. Each item that does not start with the supplied prefix
321 * is removed from the list.</p>
322 */
323 private class SimpleFilter extends Filter {
324
325 @Override
326 protected FilterResults performFiltering(CharSequence prefix) {
327 FilterResults results = new FilterResults();
328
329 if (mUnfilteredData == null) {
330 mUnfilteredData = new ArrayList<Map<String, ?>>(mData);
331 }
332
333 if (prefix == null || prefix.length() == 0) {
334 ArrayList<Map<String, ?>> list = mUnfilteredData;
335 results.values = list;
336 results.count = list.size();
337 } else {
338 String prefixString = prefix.toString().toLowerCase();
339
340 ArrayList<Map<String, ?>> unfilteredValues = mUnfilteredData;
341 int count = unfilteredValues.size();
342
343 ArrayList<Map<String, ?>> newValues = new ArrayList<Map<String, ?>>(count);
344
345 for (int i = 0; i < count; i++) {
346 Map<String, ?> h = unfilteredValues.get(i);
347 if (h != null) {
348
349 int len = mTo.length;
350
351 for (int j=0; j<len; j++) {
352 String str = (String)h.get(mFrom[j]);
353
354 String[] words = str.split(" ");
355 int wordCount = words.length;
356
357 for (int k = 0; k < wordCount; k++) {
358 String word = words[k];
359
360 if (word.toLowerCase().startsWith(prefixString)) {
361 newValues.add(h);
362 break;
363 }
364 }
365 }
366 }
367 }
368
369 results.values = newValues;
370 results.count = newValues.size();
371 }
372
373 return results;
374 }
375
376 @Override
377 protected void publishResults(CharSequence constraint, FilterResults results) {
378 //noinspection unchecked
379 mData = (List<Map<String, ?>>) results.values;
380 if (results.count > 0) {
381 notifyDataSetChanged();
382 } else {
383 notifyDataSetInvalidated();
384 }
385 }
386 }
387}