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