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