• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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