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     static final class ChainingFocusChangeListener implements View.OnFocusChangeListener {
78         final View.OnFocusChangeListener mChainedListener;
79         boolean mHasWrapper;
80         FocusHighlightHandler mFocusHighlight;
81 
ChainingFocusChangeListener(View.OnFocusChangeListener chainedListener, boolean hasWrapper, FocusHighlightHandler focusHighlight)82         ChainingFocusChangeListener(View.OnFocusChangeListener chainedListener,
83                 boolean hasWrapper, FocusHighlightHandler focusHighlight) {
84             mChainedListener = chainedListener;
85             mHasWrapper = hasWrapper;
86             mFocusHighlight = focusHighlight;
87         }
88 
update(boolean hasWrapper, FocusHighlightHandler focusHighlight)89         void update(boolean hasWrapper, FocusHighlightHandler focusHighlight) {
90             mHasWrapper = hasWrapper;
91             mFocusHighlight = focusHighlight;
92         }
93 
94         @Override
onFocusChange(View view, boolean hasFocus)95         public void onFocusChange(View view, boolean hasFocus) {
96             if (DEBUG) {
97                 Log.v(TAG, "onFocusChange " + hasFocus + " " + view
98                         + " mFocusHighlight" + mFocusHighlight);
99             }
100             if (mHasWrapper) {
101                 view = (View) view.getParent();
102             }
103             mFocusHighlight.onItemFocused(view, hasFocus);
104             if (mChainedListener != null) {
105                 mChainedListener.onFocusChange(view, hasFocus);
106             }
107         }
108     }
109 
110     /**
111      * ViewHolder for the ItemBridgeAdapter.
112      */
113     public static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
114         final Presenter mPresenter;
115         final Presenter.ViewHolder mHolder;
116         Object mItem;
117         Object mExtraObject;
118 
119         /**
120          * Get {@link Presenter}.
121          */
getPresenter()122         public final Presenter getPresenter() {
123             return mPresenter;
124         }
125 
126         /**
127          * Get {@link Presenter.ViewHolder}.
128          */
getViewHolder()129         public final Presenter.ViewHolder getViewHolder() {
130             return mHolder;
131         }
132 
133         /**
134          * Get currently bound object.
135          */
getItem()136         public final Object getItem() {
137             return mItem;
138         }
139 
140         /**
141          * Get extra object associated with the view.  Developer can attach
142          * any customized UI object in addition to {@link Presenter.ViewHolder}.
143          * A typical use case is attaching an animator object.
144          */
getExtraObject()145         public final Object getExtraObject() {
146             return mExtraObject;
147         }
148 
149         /**
150          * Set extra object associated with the view.  Developer can attach
151          * any customized UI object in addition to {@link Presenter.ViewHolder}.
152          * A typical use case is attaching an animator object.
153          */
setExtraObject(Object object)154         public void setExtraObject(Object object) {
155             mExtraObject = object;
156         }
157 
158         @Override
getFacet(Class<?> facetClass)159         public Object getFacet(Class<?> facetClass) {
160             return mHolder.getFacet(facetClass);
161         }
162 
ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder)163         ViewHolder(Presenter presenter, View view, Presenter.ViewHolder holder) {
164             super(view);
165             mPresenter = presenter;
166             mHolder = holder;
167         }
168     }
169 
170     private ObjectAdapter.DataObserver mDataObserver = new ObjectAdapter.DataObserver() {
171         @Override
172         public void onChanged() {
173             ItemBridgeAdapter.this.notifyDataSetChanged();
174         }
175 
176         @Override
177         public void onItemRangeChanged(int positionStart, int itemCount) {
178             ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount);
179         }
180 
181         @Override
182         public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
183             ItemBridgeAdapter.this.notifyItemRangeChanged(positionStart, itemCount, payload);
184         }
185 
186         @Override
187         public void onItemRangeInserted(int positionStart, int itemCount) {
188             ItemBridgeAdapter.this.notifyItemRangeInserted(positionStart, itemCount);
189         }
190 
191         @Override
192         public void onItemRangeRemoved(int positionStart, int itemCount) {
193             ItemBridgeAdapter.this.notifyItemRangeRemoved(positionStart, itemCount);
194         }
195 
196         @Override
197         public void onItemMoved(int fromPosition, int toPosition) {
198             ItemBridgeAdapter.this.notifyItemMoved(fromPosition, toPosition);
199         }
200     };
201 
ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector)202     public ItemBridgeAdapter(ObjectAdapter adapter, PresenterSelector presenterSelector) {
203         setAdapter(adapter);
204         mPresenterSelector = presenterSelector;
205     }
206 
ItemBridgeAdapter(ObjectAdapter adapter)207     public ItemBridgeAdapter(ObjectAdapter adapter) {
208         this(adapter, null);
209     }
210 
ItemBridgeAdapter()211     public ItemBridgeAdapter() {
212     }
213 
214     /**
215      * Sets the {@link ObjectAdapter}.
216      */
setAdapter(ObjectAdapter adapter)217     public void setAdapter(ObjectAdapter adapter) {
218         if (adapter == mAdapter) {
219             return;
220         }
221         if (mAdapter != null) {
222             mAdapter.unregisterObserver(mDataObserver);
223         }
224         mAdapter = adapter;
225         if (mAdapter == null) {
226             notifyDataSetChanged();
227             return;
228         }
229 
230         mAdapter.registerObserver(mDataObserver);
231         if (hasStableIds() != mAdapter.hasStableIds()) {
232             setHasStableIds(mAdapter.hasStableIds());
233         }
234         notifyDataSetChanged();
235     }
236 
237     /**
238      * Changes Presenter that creates and binds the view.
239      *
240      * @param presenterSelector Presenter that creates and binds the view.
241      */
setPresenter(PresenterSelector presenterSelector)242     public void setPresenter(PresenterSelector presenterSelector) {
243         mPresenterSelector = presenterSelector;
244         notifyDataSetChanged();
245     }
246 
247     /**
248      * Sets the {@link Wrapper}.
249      */
setWrapper(Wrapper wrapper)250     public void setWrapper(Wrapper wrapper) {
251         mWrapper = wrapper;
252     }
253 
254     /**
255      * Returns the {@link Wrapper}.
256      */
getWrapper()257     public Wrapper getWrapper() {
258         return mWrapper;
259     }
260 
setFocusHighlight(FocusHighlightHandler listener)261     void setFocusHighlight(FocusHighlightHandler listener) {
262         mFocusHighlight = listener;
263         if (DEBUG) Log.v(TAG, "setFocusHighlight " + mFocusHighlight);
264     }
265 
266     /**
267      * Clears the adapter.
268      */
clear()269     public void clear() {
270         setAdapter(null);
271     }
272 
273     /**
274      * Sets the presenter mapper array.
275      */
setPresenterMapper(ArrayList<Presenter> presenters)276     public void setPresenterMapper(ArrayList<Presenter> presenters) {
277         mPresenters = presenters;
278     }
279 
280     /**
281      * Returns the presenter mapper array.
282      */
getPresenterMapper()283     public ArrayList<Presenter> getPresenterMapper() {
284         return mPresenters;
285     }
286 
287     @Override
getItemCount()288     public int getItemCount() {
289         return mAdapter != null ? mAdapter.size() : 0;
290     }
291 
292     @Override
getItemViewType(int position)293     public int getItemViewType(int position) {
294         PresenterSelector presenterSelector = mPresenterSelector != null
295                 ? mPresenterSelector : mAdapter.getPresenterSelector();
296         Object item = mAdapter.get(position);
297         Presenter presenter = presenterSelector.getPresenter(item);
298         int type = mPresenters.indexOf(presenter);
299         if (type < 0) {
300             mPresenters.add(presenter);
301             type = mPresenters.indexOf(presenter);
302             if (DEBUG) Log.v(TAG, "getItemViewType added presenter " + presenter + " type " + type);
303             onAddPresenter(presenter, type);
304             if (mAdapterListener != null) {
305                 mAdapterListener.onAddPresenter(presenter, type);
306             }
307         }
308         return type;
309     }
310 
311     /**
312      * Called when presenter is added to Adapter.
313      */
onAddPresenter(Presenter presenter, int type)314     protected void onAddPresenter(Presenter presenter, int type) {
315     }
316 
317     /**
318      * Called when ViewHolder is created.
319      */
onCreate(ViewHolder viewHolder)320     protected void onCreate(ViewHolder viewHolder) {
321     }
322 
323     /**
324      * Called when ViewHolder has been bound to data.
325      */
onBind(ViewHolder viewHolder)326     protected void onBind(ViewHolder viewHolder) {
327     }
328 
329     /**
330      * Called when ViewHolder has been unbound from data.
331      */
onUnbind(ViewHolder viewHolder)332     protected void onUnbind(ViewHolder viewHolder) {
333     }
334 
335     /**
336      * Called when ViewHolder has been attached to window.
337      */
onAttachedToWindow(ViewHolder viewHolder)338     protected void onAttachedToWindow(ViewHolder viewHolder) {
339     }
340 
341     /**
342      * Called when ViewHolder has been detached from window.
343      */
onDetachedFromWindow(ViewHolder viewHolder)344     protected void onDetachedFromWindow(ViewHolder viewHolder) {
345     }
346 
347     /**
348      * {@link View.OnFocusChangeListener} that assigned in
349      * {@link Presenter#onCreateViewHolder(ViewGroup)} may be chained, user should never change
350      * {@link View.OnFocusChangeListener} after that.
351      */
352     @Override
onCreateViewHolder(ViewGroup parent, int viewType)353     public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
354         if (DEBUG) Log.v(TAG, "onCreateViewHolder viewType " + viewType);
355         Presenter presenter = mPresenters.get(viewType);
356         Presenter.ViewHolder presenterVh;
357         View view;
358         if (mWrapper != null) {
359             view = mWrapper.createWrapper(parent);
360             presenterVh = presenter.onCreateViewHolder(parent);
361             mWrapper.wrap(view, presenterVh.view);
362         } else {
363             presenterVh = presenter.onCreateViewHolder(parent);
364             view = presenterVh.view;
365         }
366         ViewHolder viewHolder = new ViewHolder(presenter, view, presenterVh);
367         onCreate(viewHolder);
368         if (mAdapterListener != null) {
369             mAdapterListener.onCreate(viewHolder);
370         }
371         View presenterView = viewHolder.mHolder.view;
372         View.OnFocusChangeListener currentListener = presenterView.getOnFocusChangeListener();
373         if (mFocusHighlight != null) {
374             // update or create ChainingFocusChangeListener
375             if (currentListener instanceof ChainingFocusChangeListener) {
376                 ((ChainingFocusChangeListener) currentListener).update(
377                         /* hasWrapper= */ mWrapper != null, mFocusHighlight);
378             } else {
379                 presenterView.setOnFocusChangeListener(new ChainingFocusChangeListener(
380                         currentListener, /* hasWrapper= */ mWrapper != null, mFocusHighlight));
381             }
382             mFocusHighlight.onInitializeView(view);
383         } else {
384             // restore chained listener
385             if (currentListener instanceof ChainingFocusChangeListener) {
386                 presenterView.setOnFocusChangeListener(
387                         ((ChainingFocusChangeListener) currentListener).mChainedListener);
388             }
389         }
390         return viewHolder;
391     }
392 
393     /**
394      * Sets the AdapterListener.
395      */
setAdapterListener(AdapterListener listener)396     public void setAdapterListener(AdapterListener listener) {
397         mAdapterListener = listener;
398     }
399 
400     @Override
onBindViewHolder(RecyclerView.ViewHolder holder, int position)401     public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
402         if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
403         ViewHolder viewHolder = (ViewHolder) holder;
404         viewHolder.mItem = mAdapter.get(position);
405 
406         viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem);
407 
408         onBind(viewHolder);
409         if (mAdapterListener != null) {
410             mAdapterListener.onBind(viewHolder);
411         }
412     }
413 
414     @Override
415     @SuppressWarnings("unchecked")
onBindViewHolder(RecyclerView.ViewHolder holder, int position, List payloads)416     public final  void onBindViewHolder(RecyclerView.ViewHolder holder, int position,
417             List payloads) {
418         if (DEBUG) Log.v(TAG, "onBindViewHolder position " + position);
419         ViewHolder viewHolder = (ViewHolder) holder;
420         viewHolder.mItem = mAdapter.get(position);
421 
422         viewHolder.mPresenter.onBindViewHolder(viewHolder.mHolder, viewHolder.mItem, payloads);
423 
424         onBind(viewHolder);
425         if (mAdapterListener != null) {
426             mAdapterListener.onBind(viewHolder, payloads);
427         }
428     }
429 
430     @Override
onViewRecycled(RecyclerView.ViewHolder holder)431     public final void onViewRecycled(RecyclerView.ViewHolder holder) {
432         ViewHolder viewHolder = (ViewHolder) holder;
433         viewHolder.mPresenter.onUnbindViewHolder(viewHolder.mHolder);
434         onUnbind(viewHolder);
435         if (mAdapterListener != null) {
436             mAdapterListener.onUnbind(viewHolder);
437         }
438         viewHolder.mItem = null;
439     }
440 
441     @Override
onFailedToRecycleView(RecyclerView.ViewHolder holder)442     public final boolean onFailedToRecycleView(RecyclerView.ViewHolder holder) {
443         onViewRecycled(holder);
444         return false;
445     }
446 
447     @Override
onViewAttachedToWindow(RecyclerView.ViewHolder holder)448     public final void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
449         ViewHolder viewHolder = (ViewHolder) holder;
450         onAttachedToWindow(viewHolder);
451         if (mAdapterListener != null) {
452             mAdapterListener.onAttachedToWindow(viewHolder);
453         }
454         viewHolder.mPresenter.onViewAttachedToWindow(viewHolder.mHolder);
455     }
456 
457     @Override
onViewDetachedFromWindow(RecyclerView.ViewHolder holder)458     public final void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
459         ViewHolder viewHolder = (ViewHolder) holder;
460         viewHolder.mPresenter.onViewDetachedFromWindow(viewHolder.mHolder);
461         onDetachedFromWindow(viewHolder);
462         if (mAdapterListener != null) {
463             mAdapterListener.onDetachedFromWindow(viewHolder);
464         }
465     }
466 
467     @Override
getItemId(int position)468     public long getItemId(int position) {
469         return mAdapter.getId(position);
470     }
471 
472     @Override
getFacetProvider(int type)473     public FacetProvider getFacetProvider(int type) {
474         return mPresenters.get(type);
475     }
476 }
477