1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package androidx.leanback.widget;
15 
16 import android.util.Log;
17 import android.view.View;
18 import android.view.ViewGroup;
19 
20 import androidx.recyclerview.widget.RecyclerView;
21 
22 import java.util.ArrayList;
23 import java.util.List;
24 
25 /**
26  * Bridge from {@link Presenter} to {@link RecyclerView.Adapter}. Public to allow use by third
27  * party Presenters.
28  */
29 public class ItemBridgeAdapter extends RecyclerView.Adapter implements FacetProviderAdapter {
30     static final String TAG = "ItemBridgeAdapter";
31     static final boolean DEBUG = false;
32 
33     /**
34      * Interface for listening to ViewHolder operations.
35      */
36     public static class AdapterListener {
onAddPresenter(Presenter presenter, int type)37         public void onAddPresenter(Presenter presenter, int type) {
38         }
39 
onCreate(ViewHolder viewHolder)40         public void onCreate(ViewHolder viewHolder) {
41         }
42 
onBind(ViewHolder viewHolder)43         public void onBind(ViewHolder viewHolder) {
44         }
45 
onBind(ViewHolder viewHolder, List payloads)46         public void onBind(ViewHolder viewHolder, List payloads) {
47             onBind(viewHolder);
48         }
49 
onUnbind(ViewHolder viewHolder)50         public void onUnbind(ViewHolder viewHolder) {
51         }
52 
onAttachedToWindow(ViewHolder viewHolder)53         public void onAttachedToWindow(ViewHolder viewHolder) {
54         }
55 
onDetachedFromWindow(ViewHolder viewHolder)56         public void onDetachedFromWindow(ViewHolder viewHolder) {
57         }
58     }
59 
60     /**
61      * Interface for wrapping a view created by a Presenter into another view.
62      * The wrapper must be the immediate parent of the wrapped view.
63      */
64     public static abstract class Wrapper {
createWrapper(View root)65         public abstract View createWrapper(View root);
66 
wrap(View wrapper, View wrapped)67         public abstract void wrap(View wrapper, View wrapped);
68     }
69 
70     private ObjectAdapter mAdapter;
71     Wrapper mWrapper;
72     private PresenterSelector mPresenterSelector;
73     FocusHighlightHandler mFocusHighlight;
74     private AdapterListener mAdapterListener;
75     private ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
76 
77     final class OnFocusChangeListener implements View.OnFocusChangeListener {
78         View.OnFocusChangeListener mChainedListener;
79 
80         @Override
onFocusChange(View view, boolean hasFocus)81         public void onFocusChange(View view, boolean hasFocus) {
82             if (DEBUG) {
83                 Log.v(TAG, "onFocusChange " + hasFocus + " " + view
84                         + " mFocusHighlight" + mFocusHighlight);
85             }
86             if (mWrapper != null) {
87                 view = (View) view.getParent();
88             }
89             if (mFocusHighlight != null) {
90                 mFocusHighlight.onItemFocused(view, hasFocus);
91             }
92             if (mChainedListener != null) {
93                 mChainedListener.onFocusChange(view, hasFocus);
94             }
95         }
96     }
97 
98     /**
99      * ViewHolder for the ItemBridgeAdapter.
100      */
101     public class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
102         final Presenter mPresenter;
103         final Presenter.ViewHolder mHolder;
104         final OnFocusChangeListener mFocusChangeListener = new OnFocusChangeListener();
105         Object mItem;
106         Object mExtraObject;
107 
108         /**
109          * Get {@link Presenter}.
110          */
getPresenter()111         public final Presenter getPresenter() {
112             return mPresenter;
113         }
114 
115         /**
116          * Get {@link Presenter.ViewHolder}.
117          */
getViewHolder()118         public final Presenter.ViewHolder getViewHolder() {
119             return mHolder;
120         }
121 
122         /**
123          * Get currently bound object.
124          */
getItem()125         public final Object getItem() {
126             return mItem;
127         }
128 
129         /**
130          * Get extra object associated with the view.  Developer can attach
131          * any customized UI object in addition to {@link Presenter.ViewHolder}.
132          * A typical use case is attaching an animator object.
133          */
getExtraObject()134         public final Object getExtraObject() {
135             return mExtraObject;
136         }
137 
138         /**
139          * Set extra object associated with the view.  Developer can attach
140          * any customized UI object in addition to {@link Presenter.ViewHolder}.
141          * A typical use case is attaching an animator object.
142          */
setExtraObject(Object object)143         public void setExtraObject(Object object) {
144             mExtraObject = object;
145         }
146 
147         @Override
getFacet(Class<?> facetClass)148         public Object getFacet(Class<?> facetClass) {
149             return mHolder.getFacet(facetClass);
150         }
151 
ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder)152         ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
153             super(view);
154             mPresenter = presenter;
155             mHolder = holder;
156         }
157     }
158 
159     private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() {
160         @Override
161         public void onChanged() {
162             ItemBridgeAdapter.this.notifyDataSetChanged();
163         }
164 
165         @Override
166         public void onItemRangeChanged(int positionStart, int itemCount) {
167             ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
168         }
169 
170         @Override
171         public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
172             ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount, payload);
173         }
174 
175         @Override
176         public void onItemRangeInserted(int positionStart, int itemCount) {
177             ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
178         }
179 
180         @Override
181         public void onItemRangeRemoved(int positionStart, int itemCount) {
182             ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
183         }
184 
185         @Override
186         public void onItemMoved(int fromPosition, int toPosition) {
187             ItemBridgeAdapter.this.notifyItemMoved(fromPosition, toPosition);
188         }
189     };
190 
ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector)191     public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
192         setAdapter(adapter);
193         mPresenterSelector = presenterSelector;
194     }
195 
ItemBridgeAdapter(ObjectAdapter adapter)196     public ItemBridgeAdapter(ObjectAdapter adapter) {
197         this(adapter, null);
198     }
199 
ItemBridgeAdapter()200     public ItemBridgeAdapter() {
201     }
202 
203     /**
204      * Sets the {@link ObjectAdapter}.
205      */
setAdapter(ObjectAdapter adapter)206     public void setAdapter(ObjectAdapter adapter) {
207         if (adapter == mAdapter) {
208             return;
209         }
210         if (mAdapter != null) {
211             mAdapter.unregisterObserver(mDataObserver);
212         }
213         mAdapter = adapter;
214         if (mAdapter == null) {
215             notifyDataSetChanged();
216             return;
217         }
218 
219         mAdapter.registerObserver(mDataObserver);
220         if (hasStableIds() != mAdapter.hasStableIds()) {
221             setHasStableIds(mAdapter.hasStableIds());
222         }
223         notifyDataSetChanged();
224     }
225 
226     /**
227      * Changes Presenter that creates and binds the view.
228      *
229      * @param presenterSelector Presenter that creates and binds the view.
230      */
setPresenter(PresenterSelector presenterSelector)231     public void setPresenter(PresenterSelector presenterSelector) {
232         mPresenterSelector = presenterSelector;
233         notifyDataSetChanged();
234     }
235 
236     /**
237      * Sets the {@link Wrapper}.
238      */
setWrapper(Wrapper wrapper)239     public void setWrapper(Wrapper wrapper) {
240         mWrapper = wrapper;
241     }
242 
243     /**
244      * Returns the {@link Wrapper}.
245      */
getWrapper()246     public Wrapper getWrapper() {
247         return mWrapper;
248     }
249 
setFocusHighlight(FocusHighlightHandler listener)250     void setFocusHighlight(FocusHighlightHandler listener) {
251         mFocusHighlight = listener;
252         if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight);
253     }
254 
255     /**
256      * Clears the adapter.
257      */
clear()258     public void clear() {
259         setAdapter(null);
260     }
261 
262     /**
263      * Sets the presenter mapper array.
264      */
setPresenterMapper(ArrayList<Presenter> presenters)265     public void setPresenterMapper(ArrayList<Presenter> presenters) {
266         mPresenters = presenters;
267     }
268 
269     /**
270      * Returns the presenter mapper array.
271      */
getPresenterMapper()272     public ArrayList<Presenter> getPresenterMapper() {
273         return mPresenters;
274     }
275 
276     @Override
getItemCount()277     public int getItemCount() {
278         return mAdapter != null ? mAdapter.size() : 0;
279     }
280 
281     @Override
getItemViewType(int position)282     public int getItemViewType(int position) {
283         PresenterSelector presenterSelector = mPresenterSelector != null
284                 ? mPresenterSelector : mAdapter.getPresenterSelector();
285         Object item = mAdapter.get(position);
286         Presenter presenter = presenterSelector.getPresenter(item);
287         int type = mPresenters.indexOf(presenter);
288         if (type < 0) {
289             mPresenters.add(presenter);
290             type = mPresenters.indexOf(presenter);
291             if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
292             onAddPresenter(presenter, type);
293             if (mAdapterListener != null) {
294                 mAdapterListener.onAddPresenter(presenter, type);
295             }
296         }
297         return type;
298     }
299 
300     /**
301      * Called when presenter is added to Adapter.
302      */
onAddPresenter(Presenter presenter, int type)303     protected void onAddPresenter(Presenter presenter, int type) {
304     }
305 
306     /**
307      * Called when ViewHolder is created.
308      */
onCreate(ViewHolder viewHolder)309     protected void onCreate(ViewHolder viewHolder) {
310     }
311 
312     /**
313      * Called when ViewHolder has been bound to data.
314      */
onBind(ViewHolder viewHolder)315     protected void onBind(ViewHolder viewHolder) {
316     }
317 
318     /**
319      * Called when ViewHolder has been unbound from data.
320      */
onUnbind(ViewHolder viewHolder)321     protected void onUnbind(ViewHolder viewHolder) {
322     }
323 
324     /**
325      * Called when ViewHolder has been attached to window.
326      */
onAttachedToWindow(ViewHolder viewHolder)327     protected void onAttachedToWindow(ViewHolder viewHolder) {
328     }
329 
330     /**
331      * Called when ViewHolder has been detached from window.
332      */
onDetachedFromWindow(ViewHolder viewHolder)333     protected void onDetachedFromWindow(ViewHolder viewHolder) {
334     }
335 
336     /**
337      * {@link View.OnFocusChangeListener} that assigned in
338      * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
339      * {@link View.OnFocusChangeListener} after that.
340      */
341     @Override
onCreateViewHolder(ViewGroup parent, int viewType)342     public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
343         if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
344         Presenter presenter = mPresenters.get(viewType);
345         Presenter.ViewHolder presenterVh;
346         View view;
347         if (mWrapper != null) {
348             view = mWrapper.createWrapper(parent);
349             presenterVh = presenter.onCreateViewHolder(parent);
350             mWrapper.wrap(view, presenterVh.view);
351         } else {
352             presenterVh = presenter.onCreateViewHolder(parent);
353             view = presenterVh.view;
354         }
355         ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
356         onCreate(viewHolder);
357         if (mAdapterListener != null) {
358             mAdapterListener.onCreate(viewHolder);
359         }
360         View presenterView = viewHolder.mHolder.view;
361         if (presenterView != null) {
362             viewHolder.mFocusChangeListener.mChainedListener =
363                     presenterView.getOnFocusChangeListener();
364             presenterView.setOnFocusChangeListener(viewHolder.mFocusChangeListener);
365         }
366         if (mFocusHighlight != null) {
367             mFocusHighlight.onInitializeView(view);
368         }
369         return viewHolder;
370     }
371 
372     /**
373      * Sets the AdapterListener.
374      */
setAdapterListener(AdapterListener listener)375     public void setAdapterListener(AdapterListener listener) {
376         mAdapterListener = listener;
377     }
378 
379     @Override
onBindViewHolder(RecyclerView.ViewHolder holder, int position)380     public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
381         if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
382         ViewHolder viewHolder = (ViewHolder) holder;
383         viewHolder.mItem = mAdapter.get(position);
384 
385         viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
386 
387         onBind(viewHolder);
388         if (mAdapterListener != null) {
389             mAdapterListener.onBind(viewHolder);
390         }
391     }
392 
393     @Override
onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)394     public final  void onBindViewHolder(RecyclerView.ViewHolder holder, int position,
395             List payloads) {
396         if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
397         ViewHolder viewHolder = (ViewHolder) holder;
398         viewHolder.mItem = mAdapter.get(position);
399 
400         viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem, payloads);
401 
402         onBind(viewHolder);
403         if (mAdapterListener != null) {
404             mAdapterListener.onBind(viewHolder, payloads);
405         }
406     }
407 
408     @Override
onViewRecycled(RecyclerView.ViewHolder holder)409     public final void onViewRecycled(RecyclerView.ViewHolder holder) {
410         ViewHolder viewHolder = (ViewHolder) holder;
411         viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
412         onUnbind(viewHolder);
413         if (mAdapterListener != null) {
414             mAdapterListener.onUnbind(viewHolder);
415         }
416         viewHolder.mItem = null;
417     }
418 
419     @Override
onFailedToRecycleView(RecyclerView.ViewHolder holder)420     public final boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
421         onViewRecycled(holder);
422         return false;
423     }
424 
425     @Override
onViewAttachedToWindow(RecyclerView.ViewHolder holder)426     public final void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
427         ViewHolder viewHolder = (ViewHolder) holder;
428         onAttachedToWindow(viewHolder);
429         if (mAdapterListener != null) {
430             mAdapterListener.onAttachedToWindow(viewHolder);
431         }
432         viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
433     }
434 
435     @Override
onViewDetachedFromWindow(RecyclerView.ViewHolder holder)436     public final void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
437         ViewHolder viewHolder = (ViewHolder) holder;
438         viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
439         onDetachedFromWindow(viewHolder);
440         if (mAdapterListener != null) {
441             mAdapterListener.onDetachedFromWindow(viewHolder);
442         }
443     }
444 
445     @Override
getItemId(int position)446     public long getItemId(int position) {
447         return mAdapter.getId(position);
448     }
449 
450     @Override
getFacetProvider(int type)451     public FacetProvider getFacetProvider(int type) {
452         return mPresenters.get(type);
453     }
454 }
455