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