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