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