blob: c184e3850cdbaecb5e0b893e538580e2fa0145db [file] [log] [blame]
Chiao Cheng87a36dc2012-11-07 18:20:17 -08001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Gary Mai69c182a2016-12-05 13:07:03 -080016package com.android.contacts.list;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080017
18import android.content.ContentUris;
19import android.content.Context;
20import android.content.res.Resources;
21import android.database.Cursor;
22import android.graphics.drawable.Drawable;
23import android.net.Uri;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080024import android.provider.ContactsContract.Contacts;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.BaseAdapter;
28import android.widget.FrameLayout;
Brian Attwell8cb7b462014-06-25 13:54:49 -070029import android.widget.TextView;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080030
Gary Mai0a49afa2016-12-05 15:53:58 -080031import com.android.contacts.ContactPhotoManager;
32import com.android.contacts.ContactPresenceIconUtil;
33import com.android.contacts.ContactStatusUtil;
34import com.android.contacts.ContactTileLoaderFactory;
35import com.android.contacts.MoreContactUtils;
Arthur Wang3f6a2442016-12-05 14:51:59 -080036import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080037import com.android.contacts.util.ViewUtil;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080038
39import java.util.ArrayList;
40
41/**
42 * Arranges contacts favorites according to provided {@link DisplayType}.
43 * Also allows for a configurable number of columns and {@link DisplayType}
44 */
45public class ContactTileAdapter extends BaseAdapter {
46 private static final String TAG = ContactTileAdapter.class.getSimpleName();
47
48 private DisplayType mDisplayType;
49 private ContactTileView.Listener mListener;
50 private Context mContext;
51 private Resources mResources;
52 protected Cursor mContactCursor = null;
53 private ContactPhotoManager mPhotoManager;
54 protected int mNumFrequents;
55
56 /**
57 * Index of the first NON starred contact in the {@link Cursor}
58 * Only valid when {@link DisplayType#STREQUENT} is true
59 */
60 private int mDividerPosition;
61 protected int mColumnCount;
62 private int mStarredIndex;
63
64 protected int mIdIndex;
65 protected int mLookupIndex;
66 protected int mPhotoUriIndex;
67 protected int mNameIndex;
68 protected int mPresenceIndex;
69 protected int mStatusIndex;
70
Chiao Cheng87a36dc2012-11-07 18:20:17 -080071 private boolean mIsQuickContactEnabled = false;
72 private final int mPaddingInPixels;
Brian Attwell88ebfb92014-09-08 15:52:18 -070073 private final int mWhitespaceStartEnd;
Chiao Cheng87a36dc2012-11-07 18:20:17 -080074
75 /**
76 * Configures the adapter to filter and display contacts using different view types.
77 * TODO: Create Uris to support getting Starred_only and Frequent_only cursors.
78 */
79 public enum DisplayType {
80 /**
81 * Displays a mixed view type of starred and frequent contacts
82 */
83 STREQUENT,
84
85 /**
Chiao Cheng87a36dc2012-11-07 18:20:17 -080086 * Display only starred contacts
87 */
88 STARRED_ONLY,
89
90 /**
91 * Display only most frequently contacted
92 */
93 FREQUENT_ONLY,
94
95 /**
96 * Display all contacts from a group in the cursor
Chiao Cheng87a36dc2012-11-07 18:20:17 -080097 */
98 GROUP_MEMBERS
99 }
100
101 public ContactTileAdapter(Context context, ContactTileView.Listener listener, int numCols,
102 DisplayType displayType) {
103 mListener = listener;
104 mContext = context;
105 mResources = context.getResources();
106 mColumnCount = (displayType == DisplayType.FREQUENT_ONLY ? 1 : numCols);
107 mDisplayType = displayType;
108 mNumFrequents = 0;
109
110 // Converting padding in dips to padding in pixels
111 mPaddingInPixels = mContext.getResources()
112 .getDimensionPixelSize(R.dimen.contact_tile_divider_padding);
Brian Attwell88ebfb92014-09-08 15:52:18 -0700113 mWhitespaceStartEnd = mContext.getResources()
114 .getDimensionPixelSize(R.dimen.contact_tile_start_end_whitespace);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800115
116 bindColumnIndices();
117 }
118
119 public void setPhotoLoader(ContactPhotoManager photoLoader) {
120 mPhotoManager = photoLoader;
121 }
122
123 public void setColumnCount(int columnCount) {
124 mColumnCount = columnCount;
125 }
126
127 public void setDisplayType(DisplayType displayType) {
128 mDisplayType = displayType;
129 }
130
131 public void enableQuickContact(boolean enableQuickContact) {
132 mIsQuickContactEnabled = enableQuickContact;
133 }
134
135 /**
136 * Sets the column indices for expected {@link Cursor}
137 * based on {@link DisplayType}.
138 */
139 protected void bindColumnIndices() {
140 mIdIndex = ContactTileLoaderFactory.CONTACT_ID;
141 mLookupIndex = ContactTileLoaderFactory.LOOKUP_KEY;
142 mPhotoUriIndex = ContactTileLoaderFactory.PHOTO_URI;
143 mNameIndex = ContactTileLoaderFactory.DISPLAY_NAME;
144 mStarredIndex = ContactTileLoaderFactory.STARRED;
145 mPresenceIndex = ContactTileLoaderFactory.CONTACT_PRESENCE;
146 mStatusIndex = ContactTileLoaderFactory.CONTACT_STATUS;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800147 }
148
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700149 private static boolean cursorIsValid(Cursor cursor) {
150 return cursor != null && !cursor.isClosed();
151 }
152
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800153 /**
154 * Gets the number of frequents from the passed in cursor.
155 *
156 * This methods is needed so the GroupMemberTileAdapter can override this.
157 *
158 * @param cursor The cursor to get number of frequents from.
159 */
160 protected void saveNumFrequentsFromCursor(Cursor cursor) {
161
162 // count the number of frequents
163 switch (mDisplayType) {
164 case STARRED_ONLY:
165 mNumFrequents = 0;
166 break;
167 case STREQUENT:
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700168 mNumFrequents = cursorIsValid(cursor) ?
169 cursor.getCount() - mDividerPosition : 0;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800170 break;
171 case FREQUENT_ONLY:
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700172 mNumFrequents = cursorIsValid(cursor) ? cursor.getCount() : 0;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800173 break;
174 default:
175 throw new IllegalArgumentException("Unrecognized DisplayType " + mDisplayType);
176 }
177 }
178
179 /**
180 * Creates {@link ContactTileView}s for each item in {@link Cursor}.
181 *
182 * Else use {@link ContactTileLoaderFactory}
183 */
184 public void setContactCursor(Cursor cursor) {
185 mContactCursor = cursor;
186 mDividerPosition = getDividerPosition(cursor);
187
188 saveNumFrequentsFromCursor(cursor);
189
190 // cause a refresh of any views that rely on this data
191 notifyDataSetChanged();
192 }
193
194 /**
195 * Iterates over the {@link Cursor}
196 * Returns position of the first NON Starred Contact
197 * Returns -1 if {@link DisplayType#STARRED_ONLY}
198 * Returns 0 if {@link DisplayType#FREQUENT_ONLY}
199 */
200 protected int getDividerPosition(Cursor cursor) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800201 switch (mDisplayType) {
202 case STREQUENT:
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700203 if (!cursorIsValid(cursor)) {
204 return 0;
205 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800206 cursor.moveToPosition(-1);
207 while (cursor.moveToNext()) {
208 if (cursor.getInt(mStarredIndex) == 0) {
209 return cursor.getPosition();
210 }
211 }
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700212
213 // There are not NON Starred contacts in cursor
214 // Set divider positon to end
215 return cursor.getCount();
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800216 case STARRED_ONLY:
217 // There is no divider
218 return -1;
219 case FREQUENT_ONLY:
220 // Divider is first
221 return 0;
222 default:
223 throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
224 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800225 }
226
227 protected ContactEntry createContactEntryFromCursor(Cursor cursor, int position) {
228 // If the loader was canceled we will be given a null cursor.
229 // In that case, show an empty list of contacts.
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700230 if (!cursorIsValid(cursor) || cursor.getCount() <= position) {
231 return null;
232 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800233
234 cursor.moveToPosition(position);
235 long id = cursor.getLong(mIdIndex);
236 String photoUri = cursor.getString(mPhotoUriIndex);
237 String lookupKey = cursor.getString(mLookupIndex);
238
239 ContactEntry contact = new ContactEntry();
240 String name = cursor.getString(mNameIndex);
Brandon Maxwellff1be302015-10-28 15:33:43 -0700241 contact.namePrimary = (name != null) ? name : mResources.getString(R.string.missing_name);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800242 contact.status = cursor.getString(mStatusIndex);
243 contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null);
Yorke Lee9df5e192014-02-12 14:58:25 -0800244 contact.lookupKey = lookupKey;
245 contact.lookupUri = ContentUris.withAppendedId(
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800246 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id);
Christine Chenb1935002013-09-20 18:04:44 -0700247 contact.isFavorite = cursor.getInt(mStarredIndex) > 0;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800248
Brian Attwell77faaf52014-05-28 17:44:18 -0700249 // Set presence icon and status message
250 Drawable icon = null;
251 int presence = 0;
252 if (!cursor.isNull(mPresenceIndex)) {
253 presence = cursor.getInt(mPresenceIndex);
254 icon = ContactPresenceIconUtil.getPresenceIcon(mContext, presence);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800255 }
Brian Attwell77faaf52014-05-28 17:44:18 -0700256 contact.presenceIcon = icon;
257
258 String statusMessage = null;
259 if (mStatusIndex != 0 && !cursor.isNull(mStatusIndex)) {
260 statusMessage = cursor.getString(mStatusIndex);
261 }
262 // If there is no status message from the contact, but there was a presence value,
263 // then use the default status message string
264 if (statusMessage == null && presence != 0) {
265 statusMessage = ContactStatusUtil.getStatusString(mContext, presence);
266 }
267 contact.status = statusMessage;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800268
269 return contact;
270 }
271
272 /**
273 * Returns the number of frequents that will be displayed in the list.
274 */
275 public int getNumFrequents() {
276 return mNumFrequents;
277 }
278
279 @Override
280 public int getCount() {
Jay Shrauner8f09bf52014-04-18 15:27:45 -0700281 if (!cursorIsValid(mContactCursor)) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800282 return 0;
283 }
284
285 switch (mDisplayType) {
286 case STARRED_ONLY:
287 return getRowCount(mContactCursor.getCount());
288 case STREQUENT:
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800289 // Takes numbers of rows the Starred Contacts Occupy
290 int starredRowCount = getRowCount(mDividerPosition);
291
292 // Compute the frequent row count which is 1 plus the number of frequents
293 // (to account for the divider) or 0 if there are no frequents.
294 int frequentRowCount = mNumFrequents == 0 ? 0 : mNumFrequents + 1;
295
296 // Return the number of starred plus frequent rows
297 return starredRowCount + frequentRowCount;
298 case FREQUENT_ONLY:
299 // Number of frequent contacts
300 return mContactCursor.getCount();
301 default:
302 throw new IllegalArgumentException("Unrecognized DisplayType " + mDisplayType);
303 }
304 }
305
306 /**
307 * Returns the number of rows required to show the provided number of entries
308 * with the current number of columns.
309 */
310 protected int getRowCount(int entryCount) {
311 return entryCount == 0 ? 0 : ((entryCount - 1) / mColumnCount) + 1;
312 }
313
314 public int getColumnCount() {
315 return mColumnCount;
316 }
317
318 /**
319 * Returns an ArrayList of the {@link ContactEntry}s that are to appear
320 * on the row for the given position.
321 */
322 @Override
323 public ArrayList<ContactEntry> getItem(int position) {
324 ArrayList<ContactEntry> resultList = new ArrayList<ContactEntry>(mColumnCount);
325 int contactIndex = position * mColumnCount;
326
327 switch (mDisplayType) {
328 case FREQUENT_ONLY:
329 resultList.add(createContactEntryFromCursor(mContactCursor, position));
330 break;
331 case STARRED_ONLY:
332 for (int columnCounter = 0; columnCounter < mColumnCount; columnCounter++) {
333 resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex));
334 contactIndex++;
335 }
336 break;
337 case STREQUENT:
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800338 if (position < getRowCount(mDividerPosition)) {
339 for (int columnCounter = 0; columnCounter < mColumnCount &&
340 contactIndex != mDividerPosition; columnCounter++) {
341 resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex));
342 contactIndex++;
343 }
344 } else {
345 /*
346 * Current position minus how many rows are before the divider and
347 * Minus 1 for the divider itself provides the relative index of the frequent
348 * contact being displayed. Then add the dividerPostion to give the offset
349 * into the contacts cursor to get the absoulte index.
350 */
351 contactIndex = position - getRowCount(mDividerPosition) - 1 + mDividerPosition;
352 resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex));
353 }
354 break;
355 default:
356 throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
357 }
358 return resultList;
359 }
360
361 @Override
362 public long getItemId(int position) {
363 // As we show several selectable items for each ListView row,
364 // we can not determine a stable id. But as we don't rely on ListView's selection,
365 // this should not be a problem.
366 return position;
367 }
368
369 @Override
370 public boolean areAllItemsEnabled() {
Brian Attwell77faaf52014-05-28 17:44:18 -0700371 return (mDisplayType != DisplayType.STREQUENT);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800372 }
373
374 @Override
375 public boolean isEnabled(int position) {
376 return position != getRowCount(mDividerPosition);
377 }
378
379 @Override
380 public View getView(int position, View convertView, ViewGroup parent) {
381 int itemViewType = getItemViewType(position);
382
383 if (itemViewType == ViewTypes.DIVIDER) {
384 // Checking For Divider First so not to cast convertView
Brian Attwell8cb7b462014-06-25 13:54:49 -0700385 final TextView textView = (TextView) (convertView == null ? getDivider() : convertView);
386 setDividerPadding(textView, position == 0);
387 return textView;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800388 }
389
390 ContactTileRow contactTileRowView = (ContactTileRow) convertView;
391 ArrayList<ContactEntry> contactList = getItem(position);
392
393 if (contactTileRowView == null) {
394 // Creating new row if needed
395 contactTileRowView = new ContactTileRow(mContext, itemViewType);
396 }
397
398 contactTileRowView.configureRow(contactList, position == getCount() - 1);
399 return contactTileRowView;
400 }
401
402 /**
403 * Divider uses a list_seperator.xml along with text to denote
404 * the most frequently contacted contacts.
405 */
Brian Attwell8cb7b462014-06-25 13:54:49 -0700406 private TextView getDivider() {
Brian Attwell77faaf52014-05-28 17:44:18 -0700407 return MoreContactUtils.createHeaderView(mContext, R.string.favoritesFrequentContacted);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800408 }
409
Brian Attwell8cb7b462014-06-25 13:54:49 -0700410 private void setDividerPadding(TextView headerTextView, boolean isFirstRow) {
411 MoreContactUtils.setHeaderViewBottomPadding(mContext, headerTextView, isFirstRow);
412 }
413
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800414 private int getLayoutResourceId(int viewType) {
415 switch (viewType) {
416 case ViewTypes.STARRED:
417 return mIsQuickContactEnabled ?
418 R.layout.contact_tile_starred_quick_contact : R.layout.contact_tile_starred;
419 case ViewTypes.FREQUENT:
Brian Attwell77faaf52014-05-28 17:44:18 -0700420 return R.layout.contact_tile_frequent;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800421 default:
422 throw new IllegalArgumentException("Unrecognized viewType " + viewType);
423 }
424 }
425 @Override
426 public int getViewTypeCount() {
427 return ViewTypes.COUNT;
428 }
429
430 @Override
431 public int getItemViewType(int position) {
432 /*
433 * Returns view type based on {@link DisplayType}.
434 * {@link DisplayType#STARRED_ONLY} and {@link DisplayType#GROUP_MEMBERS}
435 * are {@link ViewTypes#STARRED}.
436 * {@link DisplayType#FREQUENT_ONLY} is {@link ViewTypes#FREQUENT}.
437 * {@link DisplayType#STREQUENT} mixes both {@link ViewTypes}
438 * and also adds in {@link ViewTypes#DIVIDER}.
439 */
440 switch (mDisplayType) {
441 case STREQUENT:
442 if (position < getRowCount(mDividerPosition)) {
443 return ViewTypes.STARRED;
444 } else if (position == getRowCount(mDividerPosition)) {
445 return ViewTypes.DIVIDER;
446 } else {
447 return ViewTypes.FREQUENT;
448 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800449 case STARRED_ONLY:
450 return ViewTypes.STARRED;
451 case FREQUENT_ONLY:
452 return ViewTypes.FREQUENT;
453 default:
454 throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
455 }
456 }
457
458 /**
459 * Returns the "frequent header" position. Only available when STREQUENT or
460 * STREQUENT_PHONE_ONLY is used for its display type.
461 */
462 public int getFrequentHeaderPosition() {
463 return getRowCount(mDividerPosition);
464 }
465
466 /**
467 * Acts as a row item composed of {@link ContactTileView}
468 *
469 * TODO: FREQUENT doesn't really need it. Just let {@link #getView} return
470 */
471 private class ContactTileRow extends FrameLayout {
472 private int mItemViewType;
473 private int mLayoutResId;
474
475 public ContactTileRow(Context context, int itemViewType) {
476 super(context);
477 mItemViewType = itemViewType;
478 mLayoutResId = getLayoutResourceId(mItemViewType);
Chiao Chengad16a8e2012-11-13 12:51:13 -0800479
480 // Remove row (but not children) from accessibility node tree.
481 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800482 }
483
484 /**
485 * Configures the row to add {@link ContactEntry}s information to the views
486 */
487 public void configureRow(ArrayList<ContactEntry> list, boolean isLastRow) {
488 int columnCount = mItemViewType == ViewTypes.FREQUENT ? 1 : mColumnCount;
489
490 // Adding tiles to row and filling in contact information
491 for (int columnCounter = 0; columnCounter < columnCount; columnCounter++) {
492 ContactEntry entry =
493 columnCounter < list.size() ? list.get(columnCounter) : null;
494 addTileFromEntry(entry, columnCounter, isLastRow);
495 }
496 }
497
498 private void addTileFromEntry(ContactEntry entry, int childIndex, boolean isLastRow) {
499 final ContactTileView contactTile;
500
501 if (getChildCount() <= childIndex) {
502 contactTile = (ContactTileView) inflate(mContext, mLayoutResId, null);
503 // Note: the layoutparam set here is only actually used for FREQUENT.
504 // We override onMeasure() for STARRED and we don't care the layout param there.
505 Resources resources = mContext.getResources();
506 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
Brian Attwellefd210a2014-05-29 13:45:56 -0700507 ViewGroup.LayoutParams.MATCH_PARENT,
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800508 ViewGroup.LayoutParams.WRAP_CONTENT);
509 params.setMargins(
Brian Attwell88ebfb92014-09-08 15:52:18 -0700510 mWhitespaceStartEnd,
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800511 0,
Brian Attwell88ebfb92014-09-08 15:52:18 -0700512 mWhitespaceStartEnd,
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800513 0);
514 contactTile.setLayoutParams(params);
515 contactTile.setPhotoManager(mPhotoManager);
516 contactTile.setListener(mListener);
517 addView(contactTile);
518 } else {
519 contactTile = (ContactTileView) getChildAt(childIndex);
520 }
521 contactTile.loadFromContact(entry);
522
523 switch (mItemViewType) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800524 case ViewTypes.STARRED:
Brian Attwelle3baab92014-06-04 19:44:20 -0700525 // Set padding between tiles. Divide mPaddingInPixels between left and right
526 // tiles as evenly as possible.
527 contactTile.setPaddingRelative(
Brian Attwell88ebfb92014-09-08 15:52:18 -0700528 (mPaddingInPixels + 1) / 2, 0,
529 mPaddingInPixels
Brian Attwelle3baab92014-06-04 19:44:20 -0700530 / 2, 0);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800531 break;
532 case ViewTypes.FREQUENT:
533 contactTile.setHorizontalDividerVisibility(
534 isLastRow ? View.GONE : View.VISIBLE);
535 break;
536 default:
537 break;
538 }
539 }
540
541 @Override
542 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
543 switch (mItemViewType) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800544 case ViewTypes.STARRED:
545 onLayoutForTiles();
546 return;
547 default:
548 super.onLayout(changed, left, top, right, bottom);
549 return;
550 }
551 }
552
553 private void onLayoutForTiles() {
554 final int count = getChildCount();
555
Brian Attwell88ebfb92014-09-08 15:52:18 -0700556 // Amount of margin needed on the left is based on difference between offset and padding
557 int childLeft = mWhitespaceStartEnd - (mPaddingInPixels + 1) / 2;
558
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800559 // Just line up children horizontally.
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800560 for (int i = 0; i < count; i++) {
Brian Attwell81352892015-01-26 14:48:14 -0800561 final int rtlAdjustedIndex = ViewUtil.isViewLayoutRtl(this) ? count - i - 1 : i;
Brian Attwellc946eca2014-10-16 10:35:24 -0700562 final View child = getChildAt(rtlAdjustedIndex);
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800563
564 // Note MeasuredWidth includes the padding.
565 final int childWidth = child.getMeasuredWidth();
566 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
567 childLeft += childWidth;
568 }
569 }
570
571 @Override
572 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
573 switch (mItemViewType) {
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800574 case ViewTypes.STARRED:
575 onMeasureForTiles(widthMeasureSpec);
576 return;
577 default:
578 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
579 return;
580 }
581 }
582
583 private void onMeasureForTiles(int widthMeasureSpec) {
584 final int width = MeasureSpec.getSize(widthMeasureSpec);
585
586 final int childCount = getChildCount();
587 if (childCount == 0) {
588 // Just in case...
589 setMeasuredDimension(width, 0);
590 return;
591 }
592
593 // 1. Calculate image size.
Brian Attwell88ebfb92014-09-08 15:52:18 -0700594 // = ([total width] - [total whitespace]) / [child count]
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800595 //
596 // 2. Set it to width/height of each children.
597 // If we have a remainder, some tiles will have 1 pixel larger width than its height.
598 //
599 // 3. Set the dimensions of itself.
600 // Let width = given width.
Brian Attwelld3217b22014-05-28 18:08:36 -0700601 // Let height = wrap content.
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800602
Brian Attwell88ebfb92014-09-08 15:52:18 -0700603 final int totalWhitespaceInPixels = (mColumnCount - 1) * mPaddingInPixels
604 + mWhitespaceStartEnd * 2;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800605
606 // Preferred width / height for images (excluding the padding).
607 // The actual width may be 1 pixel larger than this if we have a remainder.
Brian Attwell88ebfb92014-09-08 15:52:18 -0700608 final int imageSize = (width - totalWhitespaceInPixels) / mColumnCount;
609 final int remainder = width - (imageSize * mColumnCount) - totalWhitespaceInPixels;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800610
611 for (int i = 0; i < childCount; i++) {
612 final View child = getChildAt(i);
Brian Attwelld3217b22014-05-28 18:08:36 -0700613 final int childWidth = imageSize + child.getPaddingRight() + child.getPaddingLeft()
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800614 // Compensate for the remainder
615 + (i < remainder ? 1 : 0);
Brian Attwelld3217b22014-05-28 18:08:36 -0700616
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800617 child.measure(
618 MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
Brian Attwelld3217b22014-05-28 18:08:36 -0700619 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800620 );
621 }
Brian Attwelld3217b22014-05-28 18:08:36 -0700622 setMeasuredDimension(width, getChildAt(0).getMeasuredHeight());
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800623 }
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800624 }
625
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800626 protected static class ViewTypes {
627 public static final int COUNT = 4;
628 public static final int STARRED = 0;
629 public static final int DIVIDER = 1;
630 public static final int FREQUENT = 2;
Chiao Cheng87a36dc2012-11-07 18:20:17 -0800631 }
632}