1b3756c53c5be55e8c8a2f6e2cda264407be84881susnatapackage android.support.v17.leanback.app; 2b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 3b3756c53c5be55e8c8a2f6e2cda264407be84881susnataimport android.support.v17.leanback.widget.ObjectAdapter; 4b3756c53c5be55e8c8a2f6e2cda264407be84881susnataimport android.support.v17.leanback.widget.Row; 5b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 6b3756c53c5be55e8c8a2f6e2cda264407be84881susnata/** 7b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * Wrapper class for {@link ObjectAdapter} used by {@link BrowseFragment} to initialize 8b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * {@link RowsFragment}. We use invisible rows to represent 9b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * {@link android.support.v17.leanback.widget.DividerRow}, 10b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * {@link android.support.v17.leanback.widget.SectionRow} and 11b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * {@link android.support.v17.leanback.widget.PageRow} in RowsFragment. In case we have an 12b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * invisible row at the end of a RowsFragment, it creates a jumping effect as the layout manager 13b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * thinks there are items even though they're invisible. This class takes care of filtering out 14b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * the invisible rows at the end. In case the data inside the adapter changes, it adjusts the 15b3756c53c5be55e8c8a2f6e2cda264407be84881susnata * bounds to reflect the latest data. 16b3756c53c5be55e8c8a2f6e2cda264407be84881susnata */ 17b3756c53c5be55e8c8a2f6e2cda264407be84881susnataclass ListRowDataAdapter extends ObjectAdapter { 18b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public static final int ON_ITEM_RANGE_CHANGED = 2; 19b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public static final int ON_ITEM_RANGE_INSERTED = 4; 20b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public static final int ON_ITEM_RANGE_REMOVED = 8; 21b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public static final int ON_CHANGED = 16; 22b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 23b3756c53c5be55e8c8a2f6e2cda264407be84881susnata private final ObjectAdapter mAdapter; 2499ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas int mLastVisibleRowIndex; 25b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 26b3756c53c5be55e8c8a2f6e2cda264407be84881susnata public ListRowDataAdapter(ObjectAdapter adapter) { 27b3756c53c5be55e8c8a2f6e2cda264407be84881susnata super(adapter.getPresenterSelector()); 28b3756c53c5be55e8c8a2f6e2cda264407be84881susnata this.mAdapter = adapter; 29b3756c53c5be55e8c8a2f6e2cda264407be84881susnata initialize(); 30b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 31b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata // If an user implements its own ObjectAdapter, notification corresponding to data 32b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata // updates can be batched e.g. remove, add might be followed by notifyRemove, notifyAdd. 33b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata // But underlying data would have changed during the notifyRemove call by the previous add 340674181fa96779197f965dd4efc47bfd90cd085esusnata // operation. To handle this case, we use QueueBasedDataObserver which forces 350674181fa96779197f965dd4efc47bfd90cd085esusnata // recyclerview to do a full data refresh after each update operation. 361f55c2c933d1085a097b00598aef18de029fe0d2susnata if (adapter.isImmediateNotifySupported()) { 37b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata mAdapter.registerObserver(new SimpleDataObserver()); 38b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } else { 39b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata mAdapter.registerObserver(new QueueBasedDataObserver()); 40b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 41b3756c53c5be55e8c8a2f6e2cda264407be84881susnata } 42b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 4399ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas void initialize() { 445d196663056735d8f1453ed3aeab75e8d922a159Dake Gu mLastVisibleRowIndex = -1; 45dbac8f49866ec226152849e7d3ace3e8263de1e4susnata int i = mAdapter.size() - 1; 46b3756c53c5be55e8c8a2f6e2cda264407be84881susnata while (i >= 0) { 47b3756c53c5be55e8c8a2f6e2cda264407be84881susnata Row item = (Row) mAdapter.get(i); 48b3756c53c5be55e8c8a2f6e2cda264407be84881susnata if (item.isRenderedAsRowView()) { 49b3756c53c5be55e8c8a2f6e2cda264407be84881susnata mLastVisibleRowIndex = i; 50b3756c53c5be55e8c8a2f6e2cda264407be84881susnata break; 51b3756c53c5be55e8c8a2f6e2cda264407be84881susnata } 52b3756c53c5be55e8c8a2f6e2cda264407be84881susnata i--; 53b3756c53c5be55e8c8a2f6e2cda264407be84881susnata } 54b3756c53c5be55e8c8a2f6e2cda264407be84881susnata } 55b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 56b3756c53c5be55e8c8a2f6e2cda264407be84881susnata @Override 57b3756c53c5be55e8c8a2f6e2cda264407be84881susnata public int size() { 58b3756c53c5be55e8c8a2f6e2cda264407be84881susnata return mLastVisibleRowIndex + 1; 59b3756c53c5be55e8c8a2f6e2cda264407be84881susnata } 60b3756c53c5be55e8c8a2f6e2cda264407be84881susnata 61b3756c53c5be55e8c8a2f6e2cda264407be84881susnata @Override 62b3756c53c5be55e8c8a2f6e2cda264407be84881susnata public Object get(int index) { 63b3756c53c5be55e8c8a2f6e2cda264407be84881susnata return mAdapter.get(index); 64b3756c53c5be55e8c8a2f6e2cda264407be84881susnata } 65b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 6699ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas void doNotify(int eventType, int positionStart, int itemCount) { 67b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata switch (eventType) { 68b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata case ON_ITEM_RANGE_CHANGED: 69b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata notifyItemRangeChanged(positionStart, itemCount); 70b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata break; 71b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata case ON_ITEM_RANGE_INSERTED: 72b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata notifyItemRangeInserted(positionStart, itemCount); 73b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata break; 74b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata case ON_ITEM_RANGE_REMOVED: 75b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata notifyItemRangeRemoved(positionStart, itemCount); 76b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata break; 77b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata case ON_CHANGED: 78b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata notifyChanged(); 791a7f5e94fb0f7183151be193ff0106ea19b65749susnata break; 80b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata default: 81b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata throw new IllegalArgumentException("Invalid event type " + eventType); 82b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 83b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 84b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 85b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata private class SimpleDataObserver extends DataObserver { 86b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 8799ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas SimpleDataObserver() { 8899ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas } 8999ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas 90b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata @Override 91b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public void onItemRangeChanged(int positionStart, int itemCount) { 92b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata if (positionStart <= mLastVisibleRowIndex) { 93b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata onEventFired(ON_ITEM_RANGE_CHANGED, positionStart, 94b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata Math.min(itemCount, mLastVisibleRowIndex - positionStart + 1)); 95b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 96b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 97b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 98b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata @Override 99b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public void onItemRangeInserted(int positionStart, int itemCount) { 100b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata if (positionStart <= mLastVisibleRowIndex) { 101b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata mLastVisibleRowIndex += itemCount; 102b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata onEventFired(ON_ITEM_RANGE_INSERTED, positionStart, itemCount); 103b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata return; 104b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 105b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 106b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata int lastVisibleRowIndex = mLastVisibleRowIndex; 107b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata initialize(); 108b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata if (mLastVisibleRowIndex > lastVisibleRowIndex) { 109b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata int totalItems = mLastVisibleRowIndex - lastVisibleRowIndex; 110b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata onEventFired(ON_ITEM_RANGE_INSERTED, lastVisibleRowIndex + 1, totalItems); 111b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 112b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 113b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 114b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata @Override 115b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public void onItemRangeRemoved(int positionStart, int itemCount) { 116b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata if (positionStart + itemCount - 1 < mLastVisibleRowIndex) { 117b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata mLastVisibleRowIndex -= itemCount; 118b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata onEventFired(ON_ITEM_RANGE_REMOVED, positionStart, itemCount); 119b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata return; 120b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 121b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 122b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata int lastVisibleRowIndex = mLastVisibleRowIndex; 123b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata initialize(); 124b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex; 125b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata if (totalItems > 0) { 126b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata onEventFired(ON_ITEM_RANGE_REMOVED, 12709cc61cbcf87063fb5f540a1f2e6c52773ca2cb2Dake Gu Math.min(mLastVisibleRowIndex + 1, positionStart), 128b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata totalItems); 129b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 130b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 131b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 132b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata @Override 133b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public void onChanged() { 134b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata initialize(); 135b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata onEventFired(ON_CHANGED, -1, -1); 136b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 137b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 138b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata protected void onEventFired(int eventType, int positionStart, int itemCount) { 139b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata doNotify(eventType, positionStart, itemCount); 140b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 141b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 142b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 1430674181fa96779197f965dd4efc47bfd90cd085esusnata 1440674181fa96779197f965dd4efc47bfd90cd085esusnata /** 1450674181fa96779197f965dd4efc47bfd90cd085esusnata * When using custom {@link ObjectAdapter}, it's possible that the user may make multiple 1460674181fa96779197f965dd4efc47bfd90cd085esusnata * changes to the underlying data at once. The notifications about those updates may be 1470674181fa96779197f965dd4efc47bfd90cd085esusnata * batched and the underlying data would have changed to reflect latest updates as opposed 1480674181fa96779197f965dd4efc47bfd90cd085esusnata * to intermediate changes. In order to force RecyclerView to refresh the view with access 1490674181fa96779197f965dd4efc47bfd90cd085esusnata * only to the final data, we call notifyChange(). 1500674181fa96779197f965dd4efc47bfd90cd085esusnata */ 1511f55c2c933d1085a097b00598aef18de029fe0d2susnata private class QueueBasedDataObserver extends DataObserver { 152b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata 15399ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas QueueBasedDataObserver() { 15499ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas } 15599ec8b0cb375f7e5577ea3ec9f09e6ff7a95de0dAurimas Liutikas 156b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata @Override 157b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata public void onChanged() { 1581f55c2c933d1085a097b00598aef18de029fe0d2susnata initialize(); 1590674181fa96779197f965dd4efc47bfd90cd085esusnata notifyChanged(); 160b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 161b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata } 162b84ed59ccba5fb8ebe3fe4316690d313234e035dsusnata} 163