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