DropdownChipLayouter.java revision b58c9a621a250c6119be2d5a77164b940a8559b0
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 116 // For BASE_RECIPIENT set all top dividers except for the first one to be GONE. 117 if (viewHolder.topDivider != null) { 118 viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE); 119 } 120 break; 121 case RECIPIENT_ALTERNATES: 122 if (position != 0) { 123 displayName = null; 124 showImage = false; 125 } 126 break; 127 case SINGLE_RECIPIENT: 128 destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress(); 129 destinationType = null; 130 } 131 132 // Bind the information to the view 133 bindTextToView(displayName, viewHolder.displayNameView); 134 bindTextToView(destination, viewHolder.destinationView); 135 bindTextToView(destinationType, viewHolder.destinationTypeView); 136 bindIconToView(showImage, entry, viewHolder.imageView, type); 137 bindDrawableToDeleteView(deleteDrawable, viewHolder.deleteView); 138 139 return itemView; 140 } 141 142 /** 143 * Returns a new view with {@link #getItemLayoutResId(AdapterType)}. 144 */ 145 public View newView(AdapterType type) { 146 return mInflater.inflate(getItemLayoutResId(type), null); 147 } 148 149 /** 150 * Returns the same view, or inflates a new one if the given view was null. 151 */ 152 protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) { 153 int itemLayout = getItemLayoutResId(type); 154 switch (type) { 155 case BASE_RECIPIENT: 156 case RECIPIENT_ALTERNATES: 157 break; 158 case SINGLE_RECIPIENT: 159 itemLayout = getAlternateItemLayoutResId(type); 160 break; 161 } 162 return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false); 163 } 164 165 /** 166 * Binds the text to the given text view. If the text was null, hides the text view. 167 */ 168 protected void bindTextToView(CharSequence text, TextView view) { 169 if (view == null) { 170 return; 171 } 172 173 if (text != null) { 174 view.setText(text); 175 view.setVisibility(View.VISIBLE); 176 } else { 177 view.setVisibility(View.GONE); 178 } 179 } 180 181 /** 182 * Binds the avatar icon to the image view. If we don't want to show the image, hides the 183 * image view. 184 */ 185 protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view, 186 AdapterType type) { 187 if (view == null) { 188 return; 189 } 190 191 if (showImage) { 192 switch (type) { 193 case BASE_RECIPIENT: 194 byte[] photoBytes = entry.getPhotoBytes(); 195 if (photoBytes != null && photoBytes.length > 0) { 196 final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0, 197 photoBytes.length); 198 view.setImageBitmap(photo); 199 } else { 200 view.setImageResource(getDefaultPhotoResId()); 201 } 202 break; 203 case RECIPIENT_ALTERNATES: 204 Uri thumbnailUri = entry.getPhotoThumbnailUri(); 205 if (thumbnailUri != null) { 206 // TODO: see if this needs to be done outside the main thread 207 // as it may be too slow to get immediately. 208 view.setImageURI(thumbnailUri); 209 } else { 210 view.setImageResource(getDefaultPhotoResId()); 211 } 212 break; 213 case SINGLE_RECIPIENT: 214 default: 215 break; 216 } 217 view.setVisibility(View.VISIBLE); 218 } else { 219 view.setVisibility(View.GONE); 220 } 221 } 222 223 protected void bindDrawableToDeleteView(final StateListDrawable drawable, ImageView view) { 224 if (view == null) { 225 return; 226 } 227 if (drawable == null) { 228 view.setVisibility(View.GONE); 229 } 230 231 view.setImageDrawable(drawable); 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 protected CharSequence getDestinationType(RecipientEntry entry) { 245 return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(), 246 entry.getDestinationLabel()).toString().toUpperCase(); 247 } 248 249 /** 250 * Returns a layout id for each item inside auto-complete list. 251 * 252 * Each View must contain two TextViews (for display name and destination) and one ImageView 253 * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, 254 * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. 255 */ 256 protected @LayoutRes int getItemLayoutResId(AdapterType type) { 257 switch (type) { 258 case BASE_RECIPIENT: 259 return R.layout.chips_autocomplete_recipient_dropdown_item; 260 case RECIPIENT_ALTERNATES: 261 return R.layout.chips_recipient_dropdown_item; 262 default: 263 return R.layout.chips_recipient_dropdown_item; 264 } 265 } 266 267 /** 268 * Returns a layout id for each item inside alternate auto-complete list. 269 * 270 * Each View must contain two TextViews (for display name and destination) and one ImageView 271 * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, 272 * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. 273 */ 274 protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) { 275 switch (type) { 276 case BASE_RECIPIENT: 277 return R.layout.chips_autocomplete_recipient_dropdown_item; 278 case RECIPIENT_ALTERNATES: 279 return R.layout.chips_recipient_dropdown_item; 280 default: 281 return R.layout.chips_recipient_dropdown_item; 282 } 283 } 284 285 /** 286 * Returns a resource ID representing an image which should be shown when ther's no relevant 287 * photo is available. 288 */ 289 protected @DrawableRes int getDefaultPhotoResId() { 290 return R.drawable.ic_contact_picture; 291 } 292 293 /** 294 * Returns an id for TextView in an item View for showing a display name. By default 295 * {@link android.R.id#title} is returned. 296 */ 297 protected @IdRes int getDisplayNameResId() { 298 return android.R.id.title; 299 } 300 301 /** 302 * Returns an id for TextView in an item View for showing a destination 303 * (an email address or a phone number). 304 * By default {@link android.R.id#text1} is returned. 305 */ 306 protected @IdRes int getDestinationResId() { 307 return android.R.id.text1; 308 } 309 310 /** 311 * Returns an id for TextView in an item View for showing the type of the destination. 312 * By default {@link android.R.id#text2} is returned. 313 */ 314 protected @IdRes int getDestinationTypeResId() { 315 return android.R.id.text2; 316 } 317 318 /** 319 * Returns an id for ImageView in an item View for showing photo image for a person. In default 320 * {@link android.R.id#icon} is returned. 321 */ 322 protected @IdRes int getPhotoResId() { 323 return android.R.id.icon; 324 } 325 326 /** 327 * Returns an id for ImageView in an item View for showing the delete button. In default 328 * {@link android.R.id#icon1} is returned. 329 */ 330 protected @IdRes int getDeleteResId() { return android.R.id.icon1; } 331 332 /** 333 * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the 334 * corresponding views. 335 */ 336 protected class ViewHolder { 337 public final TextView displayNameView; 338 public final TextView destinationView; 339 public final TextView destinationTypeView; 340 public final ImageView imageView; 341 public final ImageView deleteView; 342 public final View topDivider; 343 344 public ViewHolder(View view) { 345 displayNameView = (TextView) view.findViewById(getDisplayNameResId()); 346 destinationView = (TextView) view.findViewById(getDestinationResId()); 347 destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId()); 348 imageView = (ImageView) view.findViewById(getPhotoResId()); 349 deleteView = (ImageView) view.findViewById(getDeleteResId()); 350 topDivider = view.findViewById(R.id.chip_autocomplete_top_divider); 351 } 352 } 353} 354