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