DropdownChipLayouter.java revision 6c3f99e2e9f0f1ada622c7ab25876b69fa82b146
1package com.android.ex.chips; 2 3import android.content.Context; 4import android.content.res.Resources; 5import android.graphics.Bitmap; 6import android.graphics.BitmapFactory; 7import android.graphics.drawable.StateListDrawable; 8import android.net.Uri; 9import android.support.annotation.DrawableRes; 10import android.support.annotation.IdRes; 11import android.support.annotation.LayoutRes; 12import android.text.TextUtils; 13import android.text.util.Rfc822Tokenizer; 14import android.view.LayoutInflater; 15import android.view.View; 16import android.view.ViewGroup; 17import android.widget.ImageView; 18import android.widget.TextView; 19 20import com.android.ex.chips.Queries.Query; 21 22/** 23 * A class that inflates and binds the views in the dropdown list from 24 * RecipientEditTextView. 25 */ 26public class DropdownChipLayouter { 27 /** 28 * The type of adapter that is requesting a chip layout. 29 */ 30 public enum AdapterType { 31 BASE_RECIPIENT, 32 RECIPIENT_ALTERNATES, 33 SINGLE_RECIPIENT 34 } 35 36 public interface ChipDeleteListener { 37 void onChipDelete(); 38 } 39 40 private final LayoutInflater mInflater; 41 private final Context mContext; 42 private ChipDeleteListener mDeleteListener; 43 private Query mQuery; 44 45 public DropdownChipLayouter(LayoutInflater inflater, Context context) { 46 mInflater = inflater; 47 mContext = context; 48 } 49 50 public void setQuery(Query query) { 51 mQuery = query; 52 } 53 54 public void setDeleteListener(ChipDeleteListener listener) { 55 mDeleteListener = listener; 56 } 57 58 59 /** 60 * Layouts and binds recipient information to the view. If convertView is null, inflates a new 61 * view with getItemLaytout(). 62 * 63 * @param convertView The view to bind information to. 64 * @param parent The parent to bind the view to if we inflate a new view. 65 * @param entry The recipient entry to get information from. 66 * @param position The position in the list. 67 * @param type The adapter type that is requesting the bind. 68 * @param constraint The constraint typed in the auto complete view. 69 * 70 * @return A view ready to be shown in the drop down list. 71 */ 72 public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, 73 AdapterType type, String constraint) { 74 return bindView(convertView, parent, entry, position, type, constraint, null); 75 } 76 77 /** 78 * See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)} 79 * @param deleteDrawable a {@link android.graphics.drawable.StateListDrawable} representing 80 * the delete icon. android.R.attr.state_activated should map to the delete icon, and the 81 * default state can map to a drawable of your choice (or null for no drawable). 82 */ 83 public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, 84 AdapterType type, String constraint, StateListDrawable deleteDrawable) { 85 // Default to show all the information 86 String displayName = entry.getDisplayName(); 87 String destination = entry.getDestination(); 88 boolean showImage = true; 89 CharSequence destinationType = getDestinationType(entry); 90 91 final View itemView = reuseOrInflateView(convertView, parent, type); 92 93 final ViewHolder viewHolder = new ViewHolder(itemView); 94 95 // Hide some information depending on the entry type and adapter type 96 switch (type) { 97 case BASE_RECIPIENT: 98 if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) { 99 displayName = destination; 100 101 // We only show the destination for secondary entries, so clear it only for the 102 // first level. 103 if (entry.isFirstLevel()) { 104 destination = null; 105 } 106 } 107 108 if (!entry.isFirstLevel()) { 109 displayName = null; 110 showImage = false; 111 } 112 113 // For BASE_RECIPIENT set all top dividers except for the first one to be GONE. 114 if (viewHolder.topDivider != null) { 115 viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE); 116 } 117 break; 118 case RECIPIENT_ALTERNATES: 119 if (position != 0) { 120 displayName = null; 121 showImage = false; 122 } 123 break; 124 case SINGLE_RECIPIENT: 125 destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress(); 126 destinationType = null; 127 } 128 129 // Bind the information to the view 130 bindTextToView(displayName, viewHolder.displayNameView); 131 bindTextToView(destination, viewHolder.destinationView); 132 bindTextToView(destinationType, viewHolder.destinationTypeView); 133 bindIconToView(showImage, entry, viewHolder.imageView, type); 134 bindDrawableToDeleteView(deleteDrawable, entry.getDisplayName(), viewHolder.deleteView); 135 136 return itemView; 137 } 138 139 /** 140 * Returns a new view with {@link #getItemLayoutResId(AdapterType)}. 141 */ 142 public View newView(AdapterType type) { 143 return mInflater.inflate(getItemLayoutResId(type), null); 144 } 145 146 /** 147 * Returns the same view, or inflates a new one if the given view was null. 148 */ 149 protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) { 150 int itemLayout = getItemLayoutResId(type); 151 switch (type) { 152 case BASE_RECIPIENT: 153 case RECIPIENT_ALTERNATES: 154 break; 155 case SINGLE_RECIPIENT: 156 itemLayout = getAlternateItemLayoutResId(type); 157 break; 158 } 159 return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false); 160 } 161 162 /** 163 * Binds the text to the given text view. If the text was null, hides the text view. 164 */ 165 protected void bindTextToView(CharSequence text, TextView view) { 166 if (view == null) { 167 return; 168 } 169 170 if (text != null) { 171 view.setText(text); 172 view.setVisibility(View.VISIBLE); 173 } else { 174 view.setVisibility(View.GONE); 175 } 176 } 177 178 /** 179 * Binds the avatar icon to the image view. If we don't want to show the image, hides the 180 * image view. 181 */ 182 protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view, 183 AdapterType type) { 184 if (view == null) { 185 return; 186 } 187 188 if (showImage) { 189 switch (type) { 190 case BASE_RECIPIENT: 191 byte[] photoBytes = entry.getPhotoBytes(); 192 if (photoBytes != null && photoBytes.length > 0) { 193 final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0, 194 photoBytes.length); 195 view.setImageBitmap(photo); 196 } else { 197 view.setImageResource(getDefaultPhotoResId()); 198 } 199 break; 200 case RECIPIENT_ALTERNATES: 201 Uri thumbnailUri = entry.getPhotoThumbnailUri(); 202 if (thumbnailUri != null) { 203 // TODO: see if this needs to be done outside the main thread 204 // as it may be too slow to get immediately. 205 view.setImageURI(thumbnailUri); 206 } else { 207 view.setImageResource(getDefaultPhotoResId()); 208 } 209 break; 210 case SINGLE_RECIPIENT: 211 default: 212 break; 213 } 214 view.setVisibility(View.VISIBLE); 215 } else { 216 view.setVisibility(View.GONE); 217 } 218 } 219 220 protected void bindDrawableToDeleteView(final StateListDrawable drawable, String recipient, 221 ImageView view) { 222 if (view == null) { 223 return; 224 } 225 if (drawable == null) { 226 view.setVisibility(View.GONE); 227 } else { 228 final Resources res = mContext.getResources(); 229 view.setImageDrawable(drawable); 230 view.setContentDescription( 231 res.getString(R.string.dropdown_delete_button_desc, recipient)); 232 if (mDeleteListener != null) { 233 view.setOnClickListener(new View.OnClickListener() { 234 @Override 235 public void onClick(View view) { 236 if (drawable.getCurrent() != null) { 237 mDeleteListener.onChipDelete(); 238 } 239 } 240 }); 241 } 242 } 243 } 244 245 protected CharSequence getDestinationType(RecipientEntry entry) { 246 return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(), 247 entry.getDestinationLabel()).toString().toUpperCase(); 248 } 249 250 /** 251 * Returns a layout id for each item inside auto-complete list. 252 * 253 * Each View must contain two TextViews (for display name and destination) and one ImageView 254 * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, 255 * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. 256 */ 257 protected @LayoutRes int getItemLayoutResId(AdapterType type) { 258 switch (type) { 259 case BASE_RECIPIENT: 260 return R.layout.chips_autocomplete_recipient_dropdown_item; 261 case RECIPIENT_ALTERNATES: 262 return R.layout.chips_recipient_dropdown_item; 263 default: 264 return R.layout.chips_recipient_dropdown_item; 265 } 266 } 267 268 /** 269 * Returns a layout id for each item inside alternate auto-complete list. 270 * 271 * Each View must contain two TextViews (for display name and destination) and one ImageView 272 * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, 273 * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. 274 */ 275 protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) { 276 switch (type) { 277 case BASE_RECIPIENT: 278 return R.layout.chips_autocomplete_recipient_dropdown_item; 279 case RECIPIENT_ALTERNATES: 280 return R.layout.chips_recipient_dropdown_item; 281 default: 282 return R.layout.chips_recipient_dropdown_item; 283 } 284 } 285 286 /** 287 * Returns a resource ID representing an image which should be shown when ther's no relevant 288 * photo is available. 289 */ 290 protected @DrawableRes int getDefaultPhotoResId() { 291 return R.drawable.ic_contact_picture; 292 } 293 294 /** 295 * Returns an id for TextView in an item View for showing a display name. By default 296 * {@link android.R.id#title} is returned. 297 */ 298 protected @IdRes int getDisplayNameResId() { 299 return android.R.id.title; 300 } 301 302 /** 303 * Returns an id for TextView in an item View for showing a destination 304 * (an email address or a phone number). 305 * By default {@link android.R.id#text1} is returned. 306 */ 307 protected @IdRes int getDestinationResId() { 308 return android.R.id.text1; 309 } 310 311 /** 312 * Returns an id for TextView in an item View for showing the type of the destination. 313 * By default {@link android.R.id#text2} is returned. 314 */ 315 protected @IdRes int getDestinationTypeResId() { 316 return android.R.id.text2; 317 } 318 319 /** 320 * Returns an id for ImageView in an item View for showing photo image for a person. In default 321 * {@link android.R.id#icon} is returned. 322 */ 323 protected @IdRes int getPhotoResId() { 324 return android.R.id.icon; 325 } 326 327 /** 328 * Returns an id for ImageView in an item View for showing the delete button. In default 329 * {@link android.R.id#icon1} is returned. 330 */ 331 protected @IdRes int getDeleteResId() { return android.R.id.icon1; } 332 333 /** 334 * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the 335 * corresponding views. 336 */ 337 protected class ViewHolder { 338 public final TextView displayNameView; 339 public final TextView destinationView; 340 public final TextView destinationTypeView; 341 public final ImageView imageView; 342 public final ImageView deleteView; 343 public final View topDivider; 344 345 public ViewHolder(View view) { 346 displayNameView = (TextView) view.findViewById(getDisplayNameResId()); 347 destinationView = (TextView) view.findViewById(getDestinationResId()); 348 destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId()); 349 imageView = (ImageView) view.findViewById(getPhotoResId()); 350 deleteView = (ImageView) view.findViewById(getDeleteResId()); 351 topDivider = view.findViewById(R.id.chip_autocomplete_top_divider); 352 } 353 } 354} 355