SimpleAdapter.java revision d24b8183b93e781080b2c16c487e60d51c12da31
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
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);
122
123            final int[] to = mTo;
124            final int count = to.length;
125            final View[] holder = new View[count];
126
127            for (int i = 0; i < count; i++) {
128                holder[i] = v.findViewById(to[i]);
129            }
130
131            v.setTag(holder);
132        } else {
133            v = convertView;
134        }
135
136        bindView(position, v);
137
138        return v;
139    }
140
141    /**
142     * <p>Sets the layout resource to create the drop down views.</p>
143     *
144     * @param resource the layout resource defining the drop down views
145     * @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
146     */
147    public void setDropDownViewResource(int resource) {
148        this.mDropDownResource = resource;
149    }
150
151    @Override
152    public View getDropDownView(int position, View convertView, ViewGroup parent) {
153        return createViewFromResource(position, convertView, parent, mDropDownResource);
154    }
155
156    private void bindView(int position, View view) {
157        final Map dataSet = mData.get(position);
158        if (dataSet == null) {
159            return;
160        }
161
162        final ViewBinder binder = mViewBinder;
163        final View[] holder = (View[]) view.getTag();
164        final String[] from = mFrom;
165        final int[] to = mTo;
166        final int count = to.length;
167
168        for (int i = 0; i < count; i++) {
169            final View v = holder[i];
170            if (v != null) {
171                final Object data = dataSet.get(from[i]);
172                String text = data == null ? "" : data.toString();
173                if (text == null) {
174                    text = "";
175                }
176
177                boolean bound = false;
178                if (binder != null) {
179                    bound = binder.setViewValue(v, data, text);
180                }
181
182                if (!bound) {
183                    if (v instanceof Checkable) {
184                        if (data instanceof Boolean) {
185                            ((Checkable) v).setChecked((Boolean) data);
186                        } else {
187                            throw new IllegalStateException(v.getClass().getName() +
188                                    " should be bound to a Boolean, not a " + data.getClass());
189                        }
190                    } else if (v instanceof TextView) {
191                        // Note: keep the instanceof TextView check at the bottom of these
192                        // ifs since a lot of views are TextViews (e.g. CheckBoxes).
193                        setViewText((TextView) v, text);
194                    } else if (v instanceof ImageView) {
195                        if (data instanceof Integer) {
196                            setViewImage((ImageView) v, (Integer) data);
197                        } else {
198                            setViewImage((ImageView) v, text);
199                        }
200                    } else {
201                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
202                                " view that can be bounds by this SimpleAdapter");
203                    }
204                }
205            }
206        }
207    }
208
209    /**
210     * Returns the {@link ViewBinder} used to bind data to views.
211     *
212     * @return a ViewBinder or null if the binder does not exist
213     *
214     * @see #setViewBinder(android.widget.SimpleAdapter.ViewBinder)
215     */
216    public ViewBinder getViewBinder() {
217        return mViewBinder;
218    }
219
220    /**
221     * Sets the binder used to bind data to views.
222     *
223     * @param viewBinder the binder used to bind data to views, can be null to
224     *        remove the existing binder
225     *
226     * @see #getViewBinder()
227     */
228    public void setViewBinder(ViewBinder viewBinder) {
229        mViewBinder = viewBinder;
230    }
231
232    /**
233     * Called by bindView() to set the image for an ImageView but only if
234     * there is no existing ViewBinder or if the existing ViewBinder cannot
235     * handle binding to an ImageView.
236     *
237     * This method is called instead of {@link #setViewImage(ImageView, String)}
238     * if the supplied data is an int or Integer.
239     *
240     * @param v ImageView to receive an image
241     * @param value the value retrieved from the data set
242     *
243     * @see #setViewImage(ImageView, String)
244     */
245    public void setViewImage(ImageView v, int value) {
246        v.setImageResource(value);
247    }
248
249    /**
250     * Called by bindView() to set the image for an ImageView but only if
251     * there is no existing ViewBinder or if the existing ViewBinder cannot
252     * handle binding to an ImageView.
253     *
254     * By default, the value will be treated as an image resource. If the
255     * value cannot be used as an image resource, the value is used as an
256     * image Uri.
257     *
258     * This method is called instead of {@link #setViewImage(ImageView, int)}
259     * if the supplied data is not an int or Integer.
260     *
261     * @param v ImageView to receive an image
262     * @param value the value retrieved from the data set
263     *
264     * @see #setViewImage(ImageView, int)
265     */
266    public void setViewImage(ImageView v, String value) {
267        try {
268            v.setImageResource(Integer.parseInt(value));
269        } catch (NumberFormatException nfe) {
270            v.setImageURI(Uri.parse(value));
271        }
272    }
273
274    /**
275     * Called by bindView() to set the text for a TextView but only if
276     * there is no existing ViewBinder or if the existing ViewBinder cannot
277     * handle binding to an TextView.
278     *
279     * @param v TextView to receive text
280     * @param text the text to be set for the TextView
281     */
282    public void setViewText(TextView v, String text) {
283        v.setText(text);
284    }
285
286    public Filter getFilter() {
287        if (mFilter == null) {
288            mFilter = new SimpleFilter();
289        }
290        return mFilter;
291    }
292
293    /**
294     * This class can be used by external clients of SimpleAdapter to bind
295     * values to views.
296     *
297     * You should use this class to bind values to views that are not
298     * directly supported by SimpleAdapter or to change the way binding
299     * occurs for views supported by SimpleAdapter.
300     *
301     * @see SimpleAdapter#setViewImage(ImageView, int)
302     * @see SimpleAdapter#setViewImage(ImageView, String)
303     * @see SimpleAdapter#setViewText(TextView, String)
304     */
305    public static interface ViewBinder {
306        /**
307         * Binds the specified data to the specified view.
308         *
309         * When binding is handled by this ViewBinder, this method must return true.
310         * If this method returns false, SimpleAdapter will attempts to handle
311         * the binding on its own.
312         *
313         * @param view the view to bind the data to
314         * @param data the data to bind to the view
315         * @param textRepresentation a safe String representation of the supplied data:
316         *        it is either the result of data.toString() or an empty String but it
317         *        is never null
318         *
319         * @return true if the data was bound to the view, false otherwise
320         */
321        boolean setViewValue(View view, Object data, String textRepresentation);
322    }
323
324    /**
325     * <p>An array filters constrains the content of the array adapter with
326     * a prefix. Each item that does not start with the supplied prefix
327     * is removed from the list.</p>
328     */
329    private class SimpleFilter extends Filter {
330
331        @Override
332        protected FilterResults performFiltering(CharSequence prefix) {
333            FilterResults results = new FilterResults();
334
335            if (mUnfilteredData == null) {
336                mUnfilteredData = new ArrayList<Map<String, ?>>(mData);
337            }
338
339            if (prefix == null || prefix.length() == 0) {
340                ArrayList<Map<String, ?>> list = mUnfilteredData;
341                results.values = list;
342                results.count = list.size();
343            } else {
344                String prefixString = prefix.toString().toLowerCase();
345
346                ArrayList<Map<String, ?>> unfilteredValues = mUnfilteredData;
347                int count = unfilteredValues.size();
348
349                ArrayList<Map<String, ?>> newValues = new ArrayList<Map<String, ?>>(count);
350
351                for (int i = 0; i < count; i++) {
352                    Map<String, ?> h = unfilteredValues.get(i);
353                    if (h != null) {
354
355                        int len = mTo.length;
356
357                        for (int j=0; j<len; j++) {
358                            String str =  (String)h.get(mFrom[j]);
359
360                            String[] words = str.split(" ");
361                            int wordCount = words.length;
362
363                            for (int k = 0; k < wordCount; k++) {
364                                String word = words[k];
365
366                                if (word.toLowerCase().startsWith(prefixString)) {
367                                    newValues.add(h);
368                                    break;
369                                }
370                            }
371                        }
372                    }
373                }
374
375                results.values = newValues;
376                results.count = newValues.size();
377            }
378
379            return results;
380        }
381
382        @Override
383        protected void publishResults(CharSequence constraint, FilterResults results) {
384            //noinspection unchecked
385            mData = (List<Map<String, ?>>) results.values;
386            if (results.count > 0) {
387                notifyDataSetChanged();
388            } else {
389                notifyDataSetInvalidated();
390            }
391        }
392    }
393}
394