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.content.Context;
17 import android.util.SparseArray;
18 import android.view.LayoutInflater;
19 import android.view.View;
20 import android.view.ViewGroup;
21 
22 import androidx.leanback.R;
23 
24 /**
25  * A presenter that assumes a LinearLayout container for a series
26  * of control buttons backed by objects of type {@link Action}.
27  *
28  * Different layouts may be passed to the presenter constructor.
29  * The layout must contain a view with id control_bar.
30  */
31 class ControlBarPresenter extends Presenter {
32 
33     static final int MAX_CONTROLS = 7;
34 
35     /**
36      * The data type expected by this presenter.
37      */
38     static class BoundData {
39         /**
40          * Adapter containing objects of type {@link Action}.
41          */
42         ObjectAdapter adapter;
43 
44         /**
45          * The presenter to be used for the adapter objects.
46          */
47         Presenter presenter;
48     }
49 
50     /**
51      * Listener for control selected events.
52      */
53     interface OnControlSelectedListener {
onControlSelected(Presenter.ViewHolder controlViewHolder, Object item, BoundData data)54         void onControlSelected(Presenter.ViewHolder controlViewHolder, Object item,
55                 BoundData data);
56     }
57 
58     /**
59      * Listener for control clicked events.
60      */
61     interface OnControlClickedListener {
onControlClicked(Presenter.ViewHolder controlViewHolder, Object item, BoundData data)62         void onControlClicked(Presenter.ViewHolder controlViewHolder, Object item,
63                 BoundData data);
64     }
65 
66     class ViewHolder extends Presenter.ViewHolder {
67         ObjectAdapter mAdapter;
68         BoundData mData;
69         Presenter mPresenter;
70         ControlBar mControlBar;
71         View mControlsContainer;
72         SparseArray<Presenter.ViewHolder> mViewHolders =
73                 new SparseArray<Presenter.ViewHolder>();
74         ObjectAdapter.DataObserver mDataObserver;
75 
76         /**
77          * Constructor for the ViewHolder.
78          */
ViewHolder(View rootView)79         ViewHolder(View rootView) {
80             super(rootView);
81             mControlsContainer = rootView.findViewById(R.id.controls_container);
82             mControlBar = (ControlBar) rootView.findViewById(R.id.control_bar);
83             if (mControlBar == null) {
84                 throw new IllegalStateException("Couldn't find control_bar");
85             }
86             mControlBar.setDefaultFocusToMiddle(mDefaultFocusToMiddle);
87             mControlBar.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() {
88                 @Override
89                 public void onChildFocusedListener(View child, View focused) {
90                     if (mOnControlSelectedListener == null) {
91                         return;
92                     }
93                     for (int position = 0; position < mViewHolders.size(); position++) {
94                         if (mViewHolders.get(position).view == child) {
95                             mOnControlSelectedListener.onControlSelected(
96                                     mViewHolders.get(position),
97                                     getDisplayedAdapter().get(position), mData);
98                             break;
99                         }
100                     }
101                 }
102             });
103             mDataObserver = new ObjectAdapter.DataObserver() {
104                 @Override
105                 public void onChanged() {
106                     if (mAdapter == getDisplayedAdapter()) {
107                         showControls(mPresenter);
108                     }
109                 }
110                 @Override
111                 public void onItemRangeChanged(int positionStart, int itemCount) {
112                     if (mAdapter == getDisplayedAdapter()) {
113                         for (int i = 0; i < itemCount; i++) {
114                             bindControlToAction(positionStart + i, mPresenter);
115                         }
116                     }
117                 }
118             };
119         }
120 
getChildMarginFromCenter(Context context, int numControls)121         int getChildMarginFromCenter(Context context, int numControls) {
122             // Includes margin between icons plus two times half the icon width.
123             return getChildMarginDefault(context) + getControlIconWidth(context);
124         }
125 
showControls(Presenter presenter)126         void showControls(Presenter presenter) {
127             ObjectAdapter adapter = getDisplayedAdapter();
128             int adapterSize = adapter == null ? 0 : adapter.size();
129             // Shrink the number of attached views
130             View focusedView = mControlBar.getFocusedChild();
131             if (focusedView != null && adapterSize > 0
132                     && mControlBar.indexOfChild(focusedView) >= adapterSize) {
133                 mControlBar.getChildAt(adapter.size() - 1).requestFocus();
134             }
135             for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
136                 mControlBar.removeViewAt(i);
137             }
138             for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
139                     position++) {
140                 bindControlToAction(position, adapter, presenter);
141             }
142             mControlBar.setChildMarginFromCenter(
143                     getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
144         }
145 
bindControlToAction(int position, Presenter presenter)146         void bindControlToAction(int position, Presenter presenter) {
147             bindControlToAction(position, getDisplayedAdapter(), presenter);
148         }
149 
bindControlToAction(final int position, ObjectAdapter adapter, Presenter presenter)150         private void bindControlToAction(final int position,
151                 ObjectAdapter adapter, Presenter presenter) {
152             Presenter.ViewHolder vh = mViewHolders.get(position);
153             Object item = adapter.get(position);
154             if (vh == null) {
155                 vh = presenter.onCreateViewHolder(mControlBar);
156                 mViewHolders.put(position, vh);
157 
158                 final Presenter.ViewHolder itemViewHolder = vh;
159                 presenter.setOnClickListener(vh, new View.OnClickListener() {
160                     @Override
161                     public void onClick(View v) {
162                         Object item = getDisplayedAdapter().get(position);
163                         if (mOnControlClickedListener != null) {
164                             mOnControlClickedListener.onControlClicked(itemViewHolder, item,
165                                     mData);
166                         }
167                     }
168                 });
169             }
170             if (vh.view.getParent() == null) {
171                 mControlBar.addView(vh.view);
172             }
173             presenter.onBindViewHolder(vh, item);
174         }
175 
176         /**
177          * Returns the adapter currently bound to the displayed controls.
178          * May be overridden in a subclass.
179          */
getDisplayedAdapter()180         ObjectAdapter getDisplayedAdapter() {
181             return mAdapter;
182         }
183     }
184 
185     OnControlClickedListener mOnControlClickedListener;
186     OnControlSelectedListener mOnControlSelectedListener;
187     private int mLayoutResourceId;
188     private static int sChildMarginDefault;
189     private static int sControlIconWidth;
190     boolean mDefaultFocusToMiddle = true;
191 
192     /**
193      * Constructor for a ControlBarPresenter.
194      *
195      * @param layoutResourceId The resource id of the layout for this presenter.
196      */
ControlBarPresenter(int layoutResourceId)197     public ControlBarPresenter(int layoutResourceId) {
198         mLayoutResourceId = layoutResourceId;
199     }
200 
201     /**
202      * Returns the layout resource id.
203      */
getLayoutResourceId()204     public int getLayoutResourceId() {
205         return mLayoutResourceId;
206     }
207 
208     /**
209      * Sets the listener for control clicked events.
210      */
setOnControlClickedListener(OnControlClickedListener listener)211     public void setOnControlClickedListener(OnControlClickedListener listener) {
212         mOnControlClickedListener = listener;
213     }
214 
215     /**
216      * Returns the listener for control clicked events.
217      */
getOnItemViewClickedListener()218     public OnControlClickedListener getOnItemViewClickedListener() {
219         return mOnControlClickedListener;
220     }
221 
222     /**
223      * Sets the listener for control selection.
224      */
setOnControlSelectedListener(OnControlSelectedListener listener)225     public void setOnControlSelectedListener(OnControlSelectedListener listener) {
226         mOnControlSelectedListener = listener;
227     }
228 
229     /**
230      * Returns the listener for control selection.
231      */
getOnItemControlListener()232     public OnControlSelectedListener getOnItemControlListener() {
233         return mOnControlSelectedListener;
234     }
235 
setBackgroundColor(ViewHolder vh, int color)236     public void setBackgroundColor(ViewHolder vh, int color) {
237         vh.mControlsContainer.setBackgroundColor(color);
238     }
239 
240     @Override
onCreateViewHolder(ViewGroup parent)241     public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
242         View v = LayoutInflater.from(parent.getContext())
243             .inflate(getLayoutResourceId(), parent, false);
244         return new ViewHolder(v);
245     }
246 
247     @Override
onBindViewHolder(Presenter.ViewHolder holder, Object item)248     public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
249         ViewHolder vh = (ViewHolder) holder;
250         BoundData data = (BoundData) item;
251         if (vh.mAdapter != data.adapter) {
252             vh.mAdapter = data.adapter;
253             if (vh.mAdapter != null) {
254                 vh.mAdapter.registerObserver(vh.mDataObserver);
255             }
256         }
257         vh.mPresenter = data.presenter;
258         vh.mData = data;
259         vh.showControls(vh.mPresenter);
260     }
261 
262     @Override
onUnbindViewHolder(Presenter.ViewHolder holder)263     public void onUnbindViewHolder(Presenter.ViewHolder holder) {
264         ViewHolder vh = (ViewHolder) holder;
265         if (vh.mAdapter != null) {
266             vh.mAdapter.unregisterObserver(vh.mDataObserver);
267             vh.mAdapter = null;
268         }
269         vh.mData = null;
270     }
271 
getChildMarginDefault(Context context)272     int getChildMarginDefault(Context context) {
273         if (sChildMarginDefault == 0) {
274             sChildMarginDefault = context.getResources().getDimensionPixelSize(
275                     R.dimen.lb_playback_controls_child_margin_default);
276         }
277         return sChildMarginDefault;
278     }
279 
getControlIconWidth(Context context)280     int getControlIconWidth(Context context) {
281         if (sControlIconWidth == 0) {
282             sControlIconWidth = context.getResources().getDimensionPixelSize(
283                     R.dimen.lb_control_icon_width);
284         }
285         return sControlIconWidth;
286     }
287 
288     /**
289      * @param defaultFocusToMiddle True for middle item, false for 0.
290      */
setDefaultFocusToMiddle(boolean defaultFocusToMiddle)291     void setDefaultFocusToMiddle(boolean defaultFocusToMiddle) {
292         mDefaultFocusToMiddle = defaultFocusToMiddle;
293     }
294 
295 }
296