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