OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.widget; | 5 package org.chromium.chrome.browser.widget; |
6 | 6 |
7 import android.os.AsyncTask; | 7 import android.os.AsyncTask; |
8 import android.support.annotation.Nullable; | |
8 import android.support.v7.widget.RecyclerView; | 9 import android.support.v7.widget.RecyclerView; |
9 import android.support.v7.widget.RecyclerView.Adapter; | 10 import android.support.v7.widget.RecyclerView.Adapter; |
10 import android.support.v7.widget.RecyclerView.ViewHolder; | 11 import android.support.v7.widget.RecyclerView.ViewHolder; |
11 import android.text.format.DateUtils; | 12 import android.text.format.DateUtils; |
12 import android.util.Pair; | 13 import android.util.Pair; |
13 import android.view.LayoutInflater; | 14 import android.view.LayoutInflater; |
14 import android.view.View; | 15 import android.view.View; |
15 import android.view.ViewGroup; | 16 import android.view.ViewGroup; |
16 import android.widget.TextView; | 17 import android.widget.TextView; |
17 | 18 |
(...skipping 124 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
142 mTextView.setText(builder); | 143 mTextView.setText(builder); |
143 } | 144 } |
144 } | 145 } |
145 | 146 |
146 protected static class BasicViewHolder extends RecyclerView.ViewHolder { | 147 protected static class BasicViewHolder extends RecyclerView.ViewHolder { |
147 public BasicViewHolder(View itemView) { | 148 public BasicViewHolder(View itemView) { |
148 super(itemView); | 149 super(itemView); |
149 } | 150 } |
150 } | 151 } |
151 | 152 |
153 protected static class SubsectionHeaderViewHolder extends RecyclerView.ViewH older { | |
154 private View mView; | |
155 | |
156 public SubsectionHeaderViewHolder(View itemView) { | |
157 super(itemView); | |
158 mView = itemView; | |
159 } | |
160 | |
161 public View getView() { | |
162 return mView; | |
163 } | |
164 } | |
165 | |
152 /** | 166 /** |
153 * A bucket of items with the same date. | 167 * A bucket of items with the same date. |
154 */ | 168 */ |
155 protected static class ItemGroup { | 169 public static class ItemGroup { |
156 private final Date mDate; | 170 private final Date mDate; |
157 private final List<TimedItem> mItems = new ArrayList<>(); | 171 private final List<TimedItem> mItems = new ArrayList<>(); |
158 | 172 |
159 /** Index of the header, relative to the full list. Must be set only on ce.*/ | 173 /** Index of the header, relative to the full list. Must be set only on ce.*/ |
160 private int mIndex; | 174 private int mIndex; |
161 | 175 |
162 private boolean mIsSorted; | 176 private boolean mIsSorted; |
163 private boolean mIsListHeader; | 177 private boolean mIsListHeader; |
164 private boolean mIsListFooter; | 178 private boolean mIsListFooter; |
165 | 179 |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
220 if (index == 0 || mIsListHeader || mIsListFooter) return null; | 234 if (index == 0 || mIsListHeader || mIsListFooter) return null; |
221 | 235 |
222 sortIfNeeded(); | 236 sortIfNeeded(); |
223 return mItems.get(index - 1); | 237 return mItems.get(index - 1); |
224 } | 238 } |
225 | 239 |
226 /** | 240 /** |
227 * Rather than sorting the list each time a new item is added, the list is sorted when | 241 * Rather than sorting the list each time a new item is added, the list is sorted when |
228 * something requires a correct ordering of the items. | 242 * something requires a correct ordering of the items. |
229 */ | 243 */ |
230 private void sortIfNeeded() { | 244 protected void sortIfNeeded() { |
231 if (mIsSorted) return; | 245 if (mIsSorted) return; |
232 mIsSorted = true; | 246 mIsSorted = true; |
233 | 247 |
234 Collections.sort(mItems, new Comparator<TimedItem>() { | 248 Collections.sort(mItems, new Comparator<TimedItem>() { |
235 @Override | 249 @Override |
236 public int compare(TimedItem lhs, TimedItem rhs) { | 250 public int compare(TimedItem lhs, TimedItem rhs) { |
237 // More recent items are listed first. Ideally we'd use Lon g.compare, but that | 251 return compareItem(lhs, rhs); |
238 // is an API level 19 call for some inexplicable reason. | |
239 long timeDelta = lhs.getTimestamp() - rhs.getTimestamp(); | |
240 if (timeDelta > 0) { | |
241 return -1; | |
242 } else if (timeDelta == 0) { | |
243 return 0; | |
244 } else { | |
245 return 1; | |
246 } | |
247 } | 252 } |
248 }); | 253 }); |
249 } | 254 } |
255 | |
256 protected int compareItem(TimedItem lhs, TimedItem rhs) { | |
257 // More recent items are listed first. Ideally we'd use Long.compar e, but that | |
258 // is an API level 19 call for some inexplicable reason. | |
259 long timeDelta = lhs.getTimestamp() - rhs.getTimestamp(); | |
260 if (timeDelta > 0) { | |
261 return -1; | |
262 } else if (timeDelta == 0) { | |
263 return 0; | |
264 } else { | |
265 return 1; | |
266 } | |
267 } | |
250 } | 268 } |
251 | 269 |
252 // Cached async tasks to get the two Calendar objects, which are used when c omparing dates. | 270 // Cached async tasks to get the two Calendar objects, which are used when c omparing dates. |
253 private static final AsyncTask<Void, Void, Calendar> sCal1 = createCalendar( ); | 271 private static final AsyncTask<Void, Void, Calendar> sCal1 = createCalendar( ); |
254 private static final AsyncTask<Void, Void, Calendar> sCal2 = createCalendar( ); | 272 private static final AsyncTask<Void, Void, Calendar> sCal2 = createCalendar( ); |
255 | 273 |
256 public static final int TYPE_FOOTER = -2; | 274 public static final int TYPE_FOOTER = -2; |
257 public static final int TYPE_HEADER = -1; | 275 public static final int TYPE_HEADER = -1; |
258 public static final int TYPE_DATE = 0; | 276 public static final int TYPE_DATE = 0; |
259 public static final int TYPE_NORMAL = 1; | 277 public static final int TYPE_NORMAL = 1; |
278 public static final int TYPE_SUBSECTION_HEADER = 2; | |
260 | 279 |
261 private int mSize; | 280 private int mSize; |
262 private boolean mHasListHeader; | 281 private boolean mHasListHeader; |
263 private boolean mHasListFooter; | 282 private boolean mHasListFooter; |
264 | 283 |
265 private SortedSet<ItemGroup> mGroups = new TreeSet<>(new Comparator<ItemGrou p>() { | 284 private SortedSet<ItemGroup> mGroups = new TreeSet<>(new Comparator<ItemGrou p>() { |
266 @Override | 285 @Override |
267 public int compare(ItemGroup lhs, ItemGroup rhs) { | 286 public int compare(ItemGroup lhs, ItemGroup rhs) { |
268 if (lhs == rhs) return 0; | 287 if (lhs == rhs) return 0; |
269 | 288 |
270 // There should only be at most one list header and one list footer in the SortedSet. | 289 // There should only be at most one list header and one list footer in the SortedSet. |
271 if (lhs.mIsListHeader || rhs.mIsListFooter) return -1; | 290 if (lhs.mIsListHeader || rhs.mIsListFooter) return -1; |
272 if (lhs.mIsListFooter || rhs.mIsListHeader) return 1; | 291 if (lhs.mIsListFooter || rhs.mIsListHeader) return 1; |
273 | 292 |
274 return compareDate(lhs.mDate, rhs.mDate); | 293 return compareDate(lhs.mDate, rhs.mDate); |
275 } | 294 } |
276 }); | 295 }); |
277 | 296 |
278 /** | 297 /** |
279 * Creates a {@link ViewHolder} in the given view parent. | 298 * Creates a {@link ViewHolder} in the given view parent. |
280 * @see #onCreateViewHolder(ViewGroup, int) | 299 * @see #onCreateViewHolder(ViewGroup, int) |
281 */ | 300 */ |
282 protected abstract ViewHolder createViewHolder(ViewGroup parent); | 301 protected abstract ViewHolder createViewHolder(ViewGroup parent); |
283 | 302 |
284 /** | 303 /** |
285 * Creates a {@link BasicViewHolder} in the given view parent for the header . | 304 * Creates a {@link BasicViewHolder} in the given view parent for the header . |
286 * @see #onCreateViewHolder(ViewGroup, int) | 305 * @see #onCreateViewHolder(ViewGroup, int) |
287 */ | 306 */ |
307 @Nullable | |
288 protected BasicViewHolder createHeader(ViewGroup parent) { | 308 protected BasicViewHolder createHeader(ViewGroup parent) { |
289 return null; | 309 return null; |
290 } | 310 } |
291 | 311 |
292 /** | 312 /** |
293 * Creates a {@link BasicViewHolder} in the given view parent for the footer . | 313 * Creates a {@link BasicViewHolder} in the given view parent for the footer . |
294 * See {@link #onCreateViewHolder(ViewGroup, int)}. | 314 * See {@link #onCreateViewHolder(ViewGroup, int)}. |
295 */ | 315 */ |
316 @Nullable | |
296 protected BasicViewHolder createFooter(ViewGroup parent) { | 317 protected BasicViewHolder createFooter(ViewGroup parent) { |
297 return null; | 318 return null; |
298 } | 319 } |
299 | 320 |
300 /** | 321 /** |
301 * Creates a {@link DateViewHolder} in the given view parent. | 322 * Creates a {@link DateViewHolder} in the given view parent. |
302 * @see #onCreateViewHolder(ViewGroup, int) | 323 * @see #onCreateViewHolder(ViewGroup, int) |
303 */ | 324 */ |
304 protected DateViewHolder createDateViewHolder(ViewGroup parent) { | 325 protected DateViewHolder createDateViewHolder(ViewGroup parent) { |
305 return new DateViewHolder(LayoutInflater.from(parent.getContext()).infla te( | 326 return new DateViewHolder(LayoutInflater.from(parent.getContext()).infla te( |
306 getTimedItemViewResId(), parent, false)); | 327 getTimedItemViewResId(), parent, false)); |
307 } | 328 } |
308 | 329 |
309 /** | 330 /** |
331 * Creates a {@link ViewHolder} for a subsection in the given view parent. | |
332 * @see #onCreateViewHolder(ViewGroup, int) | |
333 */ | |
334 @Nullable | |
335 protected SubsectionHeaderViewHolder createSubsectionHeader(ViewGroup parent ) { | |
336 return null; | |
337 } | |
338 | |
339 /** | |
340 * Helper function to determine whether an item is a subsection header. | |
341 * @param timedItem The item | |
Theresa
2017/02/17 23:56:35
nit: add a period at the end of this line
shaktisahu
2017/02/18 00:39:46
Done.
| |
342 * @return Whether the item is a subsection header. | |
343 */ | |
344 protected boolean isSubsectionHeader(TimedItem timedItem) { | |
345 return false; | |
346 } | |
347 | |
348 /** | |
310 * Binds the {@link ViewHolder} with the given {@link TimedItem}. | 349 * Binds the {@link ViewHolder} with the given {@link TimedItem}. |
311 * @see #onBindViewHolder(ViewHolder, int) | 350 * @see #onBindViewHolder(ViewHolder, int) |
312 */ | 351 */ |
313 protected abstract void bindViewHolderForTimedItem(ViewHolder viewHolder, Ti medItem item); | 352 protected abstract void bindViewHolderForTimedItem(ViewHolder viewHolder, Ti medItem item); |
314 | 353 |
315 /** | 354 /** |
355 * Binds the {@link SubsectionHeaderViewHolder} with the given {@link TimedI tem}. | |
356 * @see #onBindViewHolder(ViewHolder, int) | |
357 */ | |
358 protected void bindViewHolderForSubsectionHeader( | |
359 SubsectionHeaderViewHolder holder, TimedItem timedItem) {} | |
360 | |
361 /** | |
316 * Gets the resource id of the view showing the date header. | 362 * Gets the resource id of the view showing the date header. |
317 * Contract for subclasses: this view should be a {@link TextView}. | 363 * Contract for subclasses: this view should be a {@link TextView}. |
318 */ | 364 */ |
319 protected abstract int getTimedItemViewResId(); | 365 protected abstract int getTimedItemViewResId(); |
320 | 366 |
321 /** | 367 /** |
322 * Loads a list of {@link TimedItem}s to this adapter. Previous data will no t be removed. Call | 368 * Loads a list of {@link TimedItem}s to this adapter. Previous data will no t be removed. Call |
323 * {@link #clear(boolean)} to remove previous items. | 369 * {@link #clear(boolean)} to remove previous items. |
324 */ | 370 */ |
325 public void loadItems(List<? extends TimedItem> timedItems) { | 371 public void loadItems(List<? extends TimedItem> timedItems) { |
326 for (TimedItem timedItem : timedItems) { | 372 for (TimedItem timedItem : timedItems) { |
327 Date date = new Date(timedItem.getTimestamp()); | 373 Date date = new Date(timedItem.getTimestamp()); |
328 boolean found = false; | 374 boolean found = false; |
329 for (ItemGroup group : mGroups) { | 375 for (ItemGroup group : mGroups) { |
330 if (group.isSameDay(date)) { | 376 if (group.isSameDay(date)) { |
331 found = true; | 377 found = true; |
332 group.addItem(timedItem); | 378 group.addItem(timedItem); |
333 mSize++; | 379 mSize++; |
334 break; | 380 break; |
335 } | 381 } |
336 } | 382 } |
337 if (!found) { | 383 if (!found) { |
338 // Create a new ItemGroup with the date for the new item. This i ncreases the | 384 // Create a new ItemGroup with the date for the new item. This i ncreases the |
339 // size by two because we add new views for the date and the ite m itself. | 385 // size by two because we add new views for the date and the ite m itself. |
340 ItemGroup newGroup = new ItemGroup(timedItem.getTimestamp()); | 386 ItemGroup newGroup = createGroup(timedItem.getTimestamp()); |
341 newGroup.addItem(timedItem); | 387 newGroup.addItem(timedItem); |
342 mGroups.add(newGroup); | 388 mGroups.add(newGroup); |
343 mSize += 2; | 389 mSize += 2; |
344 } | 390 } |
345 } | 391 } |
346 | 392 |
347 setGroupPositions(); | 393 setGroupPositions(); |
348 notifyDataSetChanged(); | 394 notifyDataSetChanged(); |
349 } | 395 } |
350 | 396 |
351 /** | 397 /** |
398 * Creates and returns an item group for a given day. | |
399 * @param timestamp A timestamp from which the date is determined. | |
400 * @return The item group. | |
401 */ | |
402 protected ItemGroup createGroup(long timestamp) { | |
403 return new ItemGroup(timestamp); | |
404 } | |
405 | |
406 /** | |
352 * Tells each group where they start in the list. | 407 * Tells each group where they start in the list. |
353 */ | 408 */ |
354 private void setGroupPositions() { | 409 private void setGroupPositions() { |
355 int startIndex = 0; | 410 int startIndex = 0; |
356 for (ItemGroup group : mGroups) { | 411 for (ItemGroup group : mGroups) { |
357 group.resetPosition(); | 412 group.resetPosition(); |
358 group.setPosition(startIndex); | 413 group.setPosition(startIndex); |
359 startIndex += group.size(); | 414 startIndex += group.size(); |
360 } | 415 } |
361 } | 416 } |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
450 */ | 505 */ |
451 public Pair<Date, TimedItem> getItemAt(int position) { | 506 public Pair<Date, TimedItem> getItemAt(int position) { |
452 Pair<ItemGroup, Integer> pair = getGroupAt(position); | 507 Pair<ItemGroup, Integer> pair = getGroupAt(position); |
453 ItemGroup group = pair.first; | 508 ItemGroup group = pair.first; |
454 return new Pair<>(group.mDate, group.getItemAt(pair.second)); | 509 return new Pair<>(group.mDate, group.getItemAt(pair.second)); |
455 } | 510 } |
456 | 511 |
457 @Override | 512 @Override |
458 public final int getItemViewType(int position) { | 513 public final int getItemViewType(int position) { |
459 Pair<ItemGroup, Integer> pair = getGroupAt(position); | 514 Pair<ItemGroup, Integer> pair = getGroupAt(position); |
515 ItemGroup group = pair.first; | |
460 if (pair.second == TYPE_HEADER) { | 516 if (pair.second == TYPE_HEADER) { |
461 return TYPE_HEADER; | 517 return TYPE_HEADER; |
462 } else if (pair.second == TYPE_FOOTER) { | 518 } else if (pair.second == TYPE_FOOTER) { |
463 return TYPE_FOOTER; | 519 return TYPE_FOOTER; |
464 } else if (pair.second == 0) { | 520 } else if (pair.second == 0) { |
465 return TYPE_DATE; | 521 return TYPE_DATE; |
522 } else if (isSubsectionHeader(group.getItemAt(pair.second))) { | |
523 return TYPE_SUBSECTION_HEADER; | |
466 } else { | 524 } else { |
467 return TYPE_NORMAL; | 525 return TYPE_NORMAL; |
468 } | 526 } |
469 } | 527 } |
470 | 528 |
471 @Override | 529 @Override |
472 public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, in t viewType) { | 530 public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, in t viewType) { |
473 if (viewType == TYPE_DATE) { | 531 if (viewType == TYPE_DATE) { |
474 return createDateViewHolder(parent); | 532 return createDateViewHolder(parent); |
475 } else if (viewType == TYPE_NORMAL) { | 533 } else if (viewType == TYPE_NORMAL) { |
476 return createViewHolder(parent); | 534 return createViewHolder(parent); |
477 } else if (viewType == TYPE_HEADER) { | 535 } else if (viewType == TYPE_HEADER) { |
478 return createHeader(parent); | 536 return createHeader(parent); |
479 } else if (viewType == TYPE_FOOTER) { | 537 } else if (viewType == TYPE_FOOTER) { |
480 return createFooter(parent); | 538 return createFooter(parent); |
539 } else if (viewType == TYPE_SUBSECTION_HEADER) { | |
540 return createSubsectionHeader(parent); | |
481 } | 541 } |
482 assert false; | 542 assert false; |
483 return null; | 543 return null; |
484 } | 544 } |
485 | 545 |
486 @Override | 546 @Override |
487 public final void onBindViewHolder(RecyclerView.ViewHolder holder, int posit ion) { | 547 public final void onBindViewHolder(RecyclerView.ViewHolder holder, int posit ion) { |
488 Pair<Date, TimedItem> pair = getItemAt(position); | 548 Pair<Date, TimedItem> pair = getItemAt(position); |
489 if (holder instanceof DateViewHolder) { | 549 if (holder instanceof DateViewHolder) { |
490 ((DateViewHolder) holder).setDate(pair.first); | 550 ((DateViewHolder) holder).setDate(pair.first); |
551 } else if (holder instanceof SubsectionHeaderViewHolder) { | |
552 bindViewHolderForSubsectionHeader((SubsectionHeaderViewHolder) holde r, pair.second); | |
491 } else if (!(holder instanceof BasicViewHolder)) { | 553 } else if (!(holder instanceof BasicViewHolder)) { |
492 bindViewHolderForTimedItem(holder, pair.second); | 554 bindViewHolderForTimedItem(holder, pair.second); |
493 } | 555 } |
494 } | 556 } |
495 | 557 |
496 @Override | 558 @Override |
497 public final int getItemCount() { | 559 public final int getItemCount() { |
498 return mSize; | 560 return mSize; |
499 } | 561 } |
500 | 562 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
556 long dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); | 618 long dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); |
557 long year = calendar.get(Calendar.YEAR); | 619 long year = calendar.get(Calendar.YEAR); |
558 return (year << 16) + dayOfYear; | 620 return (year << 16) + dayOfYear; |
559 } | 621 } |
560 | 622 |
561 /** | 623 /** |
562 * Compares two {@link Date}s. Note if you already have two {@link Calendar} objects, use | 624 * Compares two {@link Date}s. Note if you already have two {@link Calendar} objects, use |
563 * {@link #compareCalendar(Calendar, Calendar)} instead. | 625 * {@link #compareCalendar(Calendar, Calendar)} instead. |
564 * @return 0 if date1 and date2 are in the same day; 1 if date1 is before da te2; -1 otherwise. | 626 * @return 0 if date1 and date2 are in the same day; 1 if date1 is before da te2; -1 otherwise. |
565 */ | 627 */ |
566 private static int compareDate(Date date1, Date date2) { | 628 protected static int compareDate(Date date1, Date date2) { |
567 Pair<Calendar, Calendar> pair = getCachedCalendars(); | 629 Pair<Calendar, Calendar> pair = getCachedCalendars(); |
568 Calendar cal1 = pair.first, cal2 = pair.second; | 630 Calendar cal1 = pair.first, cal2 = pair.second; |
569 cal1.setTime(date1); | 631 cal1.setTime(date1); |
570 cal2.setTime(date2); | 632 cal2.setTime(date2); |
571 return compareCalendar(cal1, cal2); | 633 return compareCalendar(cal1, cal2); |
572 } | 634 } |
573 | 635 |
574 /** | 636 /** |
575 * @return 0 if cal1 and cal2 are in the same day; 1 if cal1 happens before cal2; -1 otherwise. | 637 * @return 0 if cal1 and cal2 are in the same day; 1 if cal1 happens before cal2; -1 otherwise. |
576 */ | 638 */ |
(...skipping 30 matching lines...) Expand all Loading... | |
607 */ | 669 */ |
608 private static AsyncTask<Void, Void, Calendar> createCalendar() { | 670 private static AsyncTask<Void, Void, Calendar> createCalendar() { |
609 return new AsyncTask<Void, Void, Calendar>() { | 671 return new AsyncTask<Void, Void, Calendar>() { |
610 @Override | 672 @Override |
611 protected Calendar doInBackground(Void... unused) { | 673 protected Calendar doInBackground(Void... unused) { |
612 return Calendar.getInstance(); | 674 return Calendar.getInstance(); |
613 } | 675 } |
614 }.execute(); | 676 }.execute(); |
615 } | 677 } |
616 } | 678 } |
OLD | NEW |