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