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