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