Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 1 | package com.android.ex.chips; |
| 2 | |
| 3 | import android.content.Context; |
Jin Cao | 6c3f99e | 2015-01-22 11:35:29 -0800 | [diff] [blame] | 4 | import android.content.res.Resources; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 5 | import android.graphics.Bitmap; |
| 6 | import android.graphics.BitmapFactory; |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 7 | import android.graphics.drawable.StateListDrawable; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 8 | import android.net.Uri; |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 9 | import android.support.annotation.DrawableRes; |
| 10 | import android.support.annotation.IdRes; |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 11 | import android.support.annotation.LayoutRes; |
Scott Kennedy | e5d17d5 | 2015-02-10 15:52:30 -0800 | [diff] [blame] | 12 | import android.support.annotation.Nullable; |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 13 | import android.support.v4.view.MarginLayoutParamsCompat; |
Scott Kennedy | e5d17d5 | 2015-02-10 15:52:30 -0800 | [diff] [blame] | 14 | import android.text.SpannableStringBuilder; |
| 15 | import android.text.Spanned; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 16 | import android.text.TextUtils; |
Scott Kennedy | e5d17d5 | 2015-02-10 15:52:30 -0800 | [diff] [blame] | 17 | import android.text.style.ForegroundColorSpan; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 18 | import android.text.util.Rfc822Tokenizer; |
| 19 | import android.view.LayoutInflater; |
| 20 | import android.view.View; |
| 21 | import android.view.ViewGroup; |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 22 | import android.view.ViewGroup.MarginLayoutParams; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 23 | import android.widget.ImageView; |
| 24 | import android.widget.TextView; |
| 25 | |
| 26 | import com.android.ex.chips.Queries.Query; |
| 27 | |
| 28 | /** |
| 29 | * A class that inflates and binds the views in the dropdown list from |
| 30 | * RecipientEditTextView. |
| 31 | */ |
| 32 | public class DropdownChipLayouter { |
| 33 | /** |
| 34 | * The type of adapter that is requesting a chip layout. |
| 35 | */ |
| 36 | public enum AdapterType { |
| 37 | BASE_RECIPIENT, |
| 38 | RECIPIENT_ALTERNATES, |
| 39 | SINGLE_RECIPIENT |
| 40 | } |
| 41 | |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 42 | public interface ChipDeleteListener { |
| 43 | void onChipDelete(); |
| 44 | } |
| 45 | |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 46 | private final LayoutInflater mInflater; |
| 47 | private final Context mContext; |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 48 | private ChipDeleteListener mDeleteListener; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 49 | private Query mQuery; |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 50 | private int mAutocompleteDividerMarginStart; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 51 | |
| 52 | public DropdownChipLayouter(LayoutInflater inflater, Context context) { |
| 53 | mInflater = inflater; |
| 54 | mContext = context; |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 55 | mAutocompleteDividerMarginStart = |
| 56 | context.getResources().getDimensionPixelOffset(R.dimen.chip_wrapper_start_padding); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | public void setQuery(Query query) { |
| 60 | mQuery = query; |
| 61 | } |
| 62 | |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 63 | public void setDeleteListener(ChipDeleteListener listener) { |
| 64 | mDeleteListener = listener; |
| 65 | } |
| 66 | |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 67 | public void setAutocompleteDividerMarginStart(int autocompleteDividerMarginStart) { |
| 68 | mAutocompleteDividerMarginStart = autocompleteDividerMarginStart; |
| 69 | } |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 70 | |
| 71 | /** |
| 72 | * Layouts and binds recipient information to the view. If convertView is null, inflates a new |
| 73 | * view with getItemLaytout(). |
| 74 | * |
| 75 | * @param convertView The view to bind information to. |
| 76 | * @param parent The parent to bind the view to if we inflate a new view. |
| 77 | * @param entry The recipient entry to get information from. |
| 78 | * @param position The position in the list. |
| 79 | * @param type The adapter type that is requesting the bind. |
| 80 | * @param constraint The constraint typed in the auto complete view. |
| 81 | * |
| 82 | * @return A view ready to be shown in the drop down list. |
| 83 | */ |
| 84 | public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, |
| 85 | AdapterType type, String constraint) { |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 86 | return bindView(convertView, parent, entry, position, type, constraint, null); |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * See {@link #bindView(View, ViewGroup, RecipientEntry, int, AdapterType, String)} |
Jin Cao | 6c3f99e | 2015-01-22 11:35:29 -0800 | [diff] [blame] | 91 | * @param deleteDrawable a {@link android.graphics.drawable.StateListDrawable} representing |
| 92 | * the delete icon. android.R.attr.state_activated should map to the delete icon, and the |
| 93 | * default state can map to a drawable of your choice (or null for no drawable). |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 94 | */ |
| 95 | public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position, |
| 96 | AdapterType type, String constraint, StateListDrawable deleteDrawable) { |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 97 | // Default to show all the information |
Scott Kennedy | e5d17d5 | 2015-02-10 15:52:30 -0800 | [diff] [blame] | 98 | CharSequence[] styledResults = |
| 99 | getStyledResults(constraint, entry.getDisplayName(), entry.getDestination()); |
| 100 | CharSequence displayName = styledResults[0]; |
| 101 | CharSequence destination = styledResults[1]; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 102 | boolean showImage = true; |
| 103 | CharSequence destinationType = getDestinationType(entry); |
| 104 | |
| 105 | final View itemView = reuseOrInflateView(convertView, parent, type); |
| 106 | |
| 107 | final ViewHolder viewHolder = new ViewHolder(itemView); |
| 108 | |
| 109 | // Hide some information depending on the entry type and adapter type |
| 110 | switch (type) { |
| 111 | case BASE_RECIPIENT: |
| 112 | if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) { |
| 113 | displayName = destination; |
| 114 | |
| 115 | // We only show the destination for secondary entries, so clear it only for the |
| 116 | // first level. |
| 117 | if (entry.isFirstLevel()) { |
| 118 | destination = null; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | if (!entry.isFirstLevel()) { |
| 123 | displayName = null; |
| 124 | showImage = false; |
| 125 | } |
Jin Cao | b58c9a6 | 2014-08-06 14:29:14 -0700 | [diff] [blame] | 126 | |
| 127 | // For BASE_RECIPIENT set all top dividers except for the first one to be GONE. |
| 128 | if (viewHolder.topDivider != null) { |
| 129 | viewHolder.topDivider.setVisibility(position == 0 ? View.VISIBLE : View.GONE); |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 130 | MarginLayoutParamsCompat.setMarginStart( |
| 131 | (MarginLayoutParams) viewHolder.topDivider.getLayoutParams(), |
| 132 | mAutocompleteDividerMarginStart); |
| 133 | } |
| 134 | if (viewHolder.bottomDivider != null) { |
| 135 | MarginLayoutParamsCompat.setMarginStart( |
| 136 | (MarginLayoutParams) viewHolder.bottomDivider.getLayoutParams(), |
| 137 | mAutocompleteDividerMarginStart); |
Jin Cao | b58c9a6 | 2014-08-06 14:29:14 -0700 | [diff] [blame] | 138 | } |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 139 | break; |
| 140 | case RECIPIENT_ALTERNATES: |
| 141 | if (position != 0) { |
| 142 | displayName = null; |
| 143 | showImage = false; |
| 144 | } |
| 145 | break; |
| 146 | case SINGLE_RECIPIENT: |
| 147 | destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress(); |
| 148 | destinationType = null; |
| 149 | } |
| 150 | |
| 151 | // Bind the information to the view |
| 152 | bindTextToView(displayName, viewHolder.displayNameView); |
| 153 | bindTextToView(destination, viewHolder.destinationView); |
| 154 | bindTextToView(destinationType, viewHolder.destinationTypeView); |
| 155 | bindIconToView(showImage, entry, viewHolder.imageView, type); |
Jin Cao | 6c3f99e | 2015-01-22 11:35:29 -0800 | [diff] [blame] | 156 | bindDrawableToDeleteView(deleteDrawable, entry.getDisplayName(), viewHolder.deleteView); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 157 | |
| 158 | return itemView; |
| 159 | } |
| 160 | |
| 161 | /** |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 162 | * Returns a new view with {@link #getItemLayoutResId(AdapterType)}. |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 163 | */ |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 164 | public View newView(AdapterType type) { |
| 165 | return mInflater.inflate(getItemLayoutResId(type), null); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 166 | } |
| 167 | |
| 168 | /** |
| 169 | * Returns the same view, or inflates a new one if the given view was null. |
| 170 | */ |
| 171 | protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) { |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 172 | int itemLayout = getItemLayoutResId(type); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 173 | switch (type) { |
| 174 | case BASE_RECIPIENT: |
| 175 | case RECIPIENT_ALTERNATES: |
| 176 | break; |
| 177 | case SINGLE_RECIPIENT: |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 178 | itemLayout = getAlternateItemLayoutResId(type); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 179 | break; |
| 180 | } |
| 181 | return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false); |
| 182 | } |
| 183 | |
| 184 | /** |
| 185 | * Binds the text to the given text view. If the text was null, hides the text view. |
| 186 | */ |
| 187 | protected void bindTextToView(CharSequence text, TextView view) { |
| 188 | if (view == null) { |
| 189 | return; |
| 190 | } |
| 191 | |
| 192 | if (text != null) { |
| 193 | view.setText(text); |
| 194 | view.setVisibility(View.VISIBLE); |
| 195 | } else { |
| 196 | view.setVisibility(View.GONE); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Binds the avatar icon to the image view. If we don't want to show the image, hides the |
| 202 | * image view. |
| 203 | */ |
| 204 | protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view, |
| 205 | AdapterType type) { |
| 206 | if (view == null) { |
| 207 | return; |
| 208 | } |
| 209 | |
| 210 | if (showImage) { |
| 211 | switch (type) { |
| 212 | case BASE_RECIPIENT: |
| 213 | byte[] photoBytes = entry.getPhotoBytes(); |
| 214 | if (photoBytes != null && photoBytes.length > 0) { |
| 215 | final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0, |
| 216 | photoBytes.length); |
| 217 | view.setImageBitmap(photo); |
| 218 | } else { |
| 219 | view.setImageResource(getDefaultPhotoResId()); |
| 220 | } |
| 221 | break; |
| 222 | case RECIPIENT_ALTERNATES: |
| 223 | Uri thumbnailUri = entry.getPhotoThumbnailUri(); |
| 224 | if (thumbnailUri != null) { |
| 225 | // TODO: see if this needs to be done outside the main thread |
| 226 | // as it may be too slow to get immediately. |
Scott Kennedy | 7a4e677 | 2013-11-21 14:31:33 -0800 | [diff] [blame] | 227 | view.setImageURI(thumbnailUri); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 228 | } else { |
| 229 | view.setImageResource(getDefaultPhotoResId()); |
| 230 | } |
| 231 | break; |
| 232 | case SINGLE_RECIPIENT: |
| 233 | default: |
| 234 | break; |
| 235 | } |
| 236 | view.setVisibility(View.VISIBLE); |
| 237 | } else { |
| 238 | view.setVisibility(View.GONE); |
| 239 | } |
| 240 | } |
| 241 | |
Jin Cao | 6c3f99e | 2015-01-22 11:35:29 -0800 | [diff] [blame] | 242 | protected void bindDrawableToDeleteView(final StateListDrawable drawable, String recipient, |
| 243 | ImageView view) { |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 244 | if (view == null) { |
| 245 | return; |
| 246 | } |
| 247 | if (drawable == null) { |
| 248 | view.setVisibility(View.GONE); |
Jin Cao | ffc0111 | 2014-12-15 16:24:37 -0800 | [diff] [blame] | 249 | } else { |
Jin Cao | 6c3f99e | 2015-01-22 11:35:29 -0800 | [diff] [blame] | 250 | final Resources res = mContext.getResources(); |
Jin Cao | ffc0111 | 2014-12-15 16:24:37 -0800 | [diff] [blame] | 251 | view.setImageDrawable(drawable); |
Jin Cao | 6c3f99e | 2015-01-22 11:35:29 -0800 | [diff] [blame] | 252 | view.setContentDescription( |
| 253 | res.getString(R.string.dropdown_delete_button_desc, recipient)); |
Jin Cao | ffc0111 | 2014-12-15 16:24:37 -0800 | [diff] [blame] | 254 | if (mDeleteListener != null) { |
| 255 | view.setOnClickListener(new View.OnClickListener() { |
| 256 | @Override |
| 257 | public void onClick(View view) { |
| 258 | if (drawable.getCurrent() != null) { |
| 259 | mDeleteListener.onChipDelete(); |
| 260 | } |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 261 | } |
Jin Cao | ffc0111 | 2014-12-15 16:24:37 -0800 | [diff] [blame] | 262 | }); |
| 263 | } |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 264 | } |
| 265 | } |
| 266 | |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 267 | protected CharSequence getDestinationType(RecipientEntry entry) { |
| 268 | return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(), |
| 269 | entry.getDestinationLabel()).toString().toUpperCase(); |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Returns a layout id for each item inside auto-complete list. |
| 274 | * |
| 275 | * Each View must contain two TextViews (for display name and destination) and one ImageView |
| 276 | * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, |
| 277 | * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. |
| 278 | */ |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 279 | protected @LayoutRes int getItemLayoutResId(AdapterType type) { |
| 280 | switch (type) { |
| 281 | case BASE_RECIPIENT: |
| 282 | return R.layout.chips_autocomplete_recipient_dropdown_item; |
| 283 | case RECIPIENT_ALTERNATES: |
| 284 | return R.layout.chips_recipient_dropdown_item; |
| 285 | default: |
| 286 | return R.layout.chips_recipient_dropdown_item; |
| 287 | } |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 288 | } |
| 289 | |
| 290 | /** |
| 291 | * Returns a layout id for each item inside alternate auto-complete list. |
| 292 | * |
| 293 | * Each View must contain two TextViews (for display name and destination) and one ImageView |
| 294 | * (for photo). Ids for those should be available via {@link #getDisplayNameResId()}, |
| 295 | * {@link #getDestinationResId()}, and {@link #getPhotoResId()}. |
| 296 | */ |
Jin Cao | 4ddcdae | 2014-07-28 19:03:56 -0700 | [diff] [blame] | 297 | protected @LayoutRes int getAlternateItemLayoutResId(AdapterType type) { |
| 298 | switch (type) { |
| 299 | case BASE_RECIPIENT: |
| 300 | return R.layout.chips_autocomplete_recipient_dropdown_item; |
| 301 | case RECIPIENT_ALTERNATES: |
| 302 | return R.layout.chips_recipient_dropdown_item; |
| 303 | default: |
| 304 | return R.layout.chips_recipient_dropdown_item; |
| 305 | } |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 306 | } |
| 307 | |
| 308 | /** |
| 309 | * Returns a resource ID representing an image which should be shown when ther's no relevant |
| 310 | * photo is available. |
| 311 | */ |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 312 | protected @DrawableRes int getDefaultPhotoResId() { |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 313 | return R.drawable.ic_contact_picture; |
| 314 | } |
| 315 | |
| 316 | /** |
| 317 | * Returns an id for TextView in an item View for showing a display name. By default |
| 318 | * {@link android.R.id#title} is returned. |
| 319 | */ |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 320 | protected @IdRes int getDisplayNameResId() { |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 321 | return android.R.id.title; |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * Returns an id for TextView in an item View for showing a destination |
| 326 | * (an email address or a phone number). |
| 327 | * By default {@link android.R.id#text1} is returned. |
| 328 | */ |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 329 | protected @IdRes int getDestinationResId() { |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 330 | return android.R.id.text1; |
| 331 | } |
| 332 | |
| 333 | /** |
| 334 | * Returns an id for TextView in an item View for showing the type of the destination. |
| 335 | * By default {@link android.R.id#text2} is returned. |
| 336 | */ |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 337 | protected @IdRes int getDestinationTypeResId() { |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 338 | return android.R.id.text2; |
| 339 | } |
| 340 | |
| 341 | /** |
| 342 | * Returns an id for ImageView in an item View for showing photo image for a person. In default |
| 343 | * {@link android.R.id#icon} is returned. |
| 344 | */ |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 345 | protected @IdRes int getPhotoResId() { |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 346 | return android.R.id.icon; |
| 347 | } |
| 348 | |
| 349 | /** |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 350 | * Returns an id for ImageView in an item View for showing the delete button. In default |
| 351 | * {@link android.R.id#icon1} is returned. |
| 352 | */ |
| 353 | protected @IdRes int getDeleteResId() { return android.R.id.icon1; } |
| 354 | |
| 355 | /** |
Scott Kennedy | e5d17d5 | 2015-02-10 15:52:30 -0800 | [diff] [blame] | 356 | * Given a constraint and results, tries to find the constraint in those results, one at a time. |
| 357 | * A foreground font color style will be applied to the section that matches the constraint. As |
| 358 | * soon as a match has been found, no further matches are attempted. |
| 359 | * |
| 360 | * @param constraint A string that we will attempt to find within the results. |
| 361 | * @param results Strings that may contain the constraint. The order given is the order used to |
| 362 | * search for the constraint. |
| 363 | * |
| 364 | * @return An array of CharSequences, the length determined by the length of results. Each |
| 365 | * CharSequence will either be a styled SpannableString or just the input String. |
| 366 | */ |
| 367 | protected CharSequence[] getStyledResults(@Nullable String constraint, String... results) { |
| 368 | if (isAllWhitespace(constraint)) { |
| 369 | return results; |
| 370 | } |
| 371 | |
| 372 | CharSequence[] styledResults = new CharSequence[results.length]; |
| 373 | boolean foundMatch = false; |
| 374 | for (int i = 0; i < results.length; i++) { |
| 375 | String result = results[i]; |
| 376 | if (result == null) { |
| 377 | continue; |
| 378 | } |
| 379 | |
| 380 | if (!foundMatch) { |
| 381 | int index = result.toLowerCase().indexOf(constraint.toLowerCase()); |
| 382 | if (index != -1) { |
| 383 | SpannableStringBuilder styled = SpannableStringBuilder.valueOf(result); |
| 384 | ForegroundColorSpan highlightSpan = |
| 385 | new ForegroundColorSpan(mContext.getResources().getColor( |
| 386 | R.color.chips_dropdown_text_highlighted)); |
| 387 | styled.setSpan(highlightSpan, |
| 388 | index, index + constraint.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
| 389 | styledResults[i] = styled; |
| 390 | foundMatch = true; |
| 391 | continue; |
| 392 | } |
| 393 | } |
| 394 | styledResults[i] = result; |
| 395 | } |
| 396 | return styledResults; |
| 397 | } |
| 398 | |
| 399 | private static boolean isAllWhitespace(@Nullable String string) { |
| 400 | if (TextUtils.isEmpty(string)) { |
| 401 | return true; |
| 402 | } |
| 403 | |
| 404 | for (int i = 0; i < string.length(); ++i) { |
| 405 | if (!Character.isWhitespace(string.charAt(i))) { |
| 406 | return false; |
| 407 | } |
| 408 | } |
| 409 | |
| 410 | return true; |
| 411 | } |
| 412 | |
| 413 | /** |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 414 | * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the |
| 415 | * corresponding views. |
| 416 | */ |
| 417 | protected class ViewHolder { |
| 418 | public final TextView displayNameView; |
| 419 | public final TextView destinationView; |
| 420 | public final TextView destinationTypeView; |
| 421 | public final ImageView imageView; |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 422 | public final ImageView deleteView; |
Jin Cao | b58c9a6 | 2014-08-06 14:29:14 -0700 | [diff] [blame] | 423 | public final View topDivider; |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 424 | public final View bottomDivider; |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 425 | |
| 426 | public ViewHolder(View view) { |
| 427 | displayNameView = (TextView) view.findViewById(getDisplayNameResId()); |
| 428 | destinationView = (TextView) view.findViewById(getDestinationResId()); |
| 429 | destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId()); |
| 430 | imageView = (ImageView) view.findViewById(getPhotoResId()); |
Jin Cao | 4db8ccc | 2014-07-30 10:11:07 -0700 | [diff] [blame] | 431 | deleteView = (ImageView) view.findViewById(getDeleteResId()); |
Jin Cao | b58c9a6 | 2014-08-06 14:29:14 -0700 | [diff] [blame] | 432 | topDivider = view.findViewById(R.id.chip_autocomplete_top_divider); |
Scott Kennedy | 6f86e3b | 2015-02-10 20:13:40 -0800 | [diff] [blame] | 433 | bottomDivider = view.findViewById(R.id.chip_autocomplete_bottom_divider); |
Kevin Lin | b10d1c6 | 2014-01-24 12:45:00 -0800 | [diff] [blame] | 434 | } |
| 435 | } |
| 436 | } |