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 android.support.v17.leanback.widget;
15 
16 import android.content.Context;
17 import android.support.v17.leanback.R;
18 import android.util.SparseArray;
19 import android.view.LayoutInflater;
20 import android.view.View;
21 import android.view.ViewGroup;
22 import android.widget.LinearLayout;
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     private 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.setOnChildFocusedListener(new ControlBar.OnChildFocusedListener() {
87                 @Override
88                 public void onChildFocusedListener(View child, View focused) {
89                     if (mOnControlSelectedListener == null) {
90                         return;
91                     }
92                     for (int position = 0; position < mViewHolders.size(); position++) {
93                         if (mViewHolders.get(position).view == child) {
94                             mOnControlSelectedListener.onControlSelected(
95                                     mViewHolders.get(position),
96                                     getDisplayedAdapter().get(position), mData);
97                             break;
98                         }
99                     }
100                 }
101             });
102             mDataObserver = new ObjectAdapter.DataObserver() {
103                 @Override
104                 public void onChanged() {
105                     if (mAdapter == getDisplayedAdapter()) {
106                         showControls(mPresenter);
107                     }
108                 }
109                 @Override
110                 public void onItemRangeChanged(int positionStart, int itemCount) {
111                     if (mAdapter == getDisplayedAdapter()) {
112                         for (int i = 0; i < itemCount; i++) {
113                             bindControlToAction(positionStart + i, mPresenter);
114                         }
115                     }
116                 }
117             };
118         }
119 
getChildMarginFromCenter(Context context, int numControls)120         int getChildMarginFromCenter(Context context, int numControls) {
121             // Includes margin between icons plus two times half the icon width.
122             return getChildMarginDefault(context) + getControlIconWidth(context);
123         }
124 
showControls(Presenter presenter)125         void showControls(Presenter presenter) {
126             ObjectAdapter adapter = getDisplayedAdapter();
127             int adapterSize = adapter == null ? 0 : adapter.size();
128             // Shrink the number of attached views
129             View focusedView = mControlBar.getFocusedChild();
130             if (focusedView != null && adapterSize > 0 &&
131                     mControlBar.indexOfChild(focusedView) >= adapterSize) {
132                 mControlBar.getChildAt(adapter.size() - 1).requestFocus();
133             }
134             for (int i = mControlBar.getChildCount() - 1; i >= adapterSize; i--) {
135                 mControlBar.removeViewAt(i);
136             }
137             for (int position = 0; position < adapterSize && position < MAX_CONTROLS;
138                     position++) {
139                 bindControlToAction(position, adapter, presenter);
140             }
141             mControlBar.setChildMarginFromCenter(
142                     getChildMarginFromCenter(mControlBar.getContext(), adapterSize));
143         }
144 
bindControlToAction(int position, Presenter presenter)145         void bindControlToAction(int position, Presenter presenter) {
146             bindControlToAction(position, getDisplayedAdapter(), presenter);
147         }
148 
bindControlToAction(final int position, ObjectAdapter adapter, Presenter presenter)149         private void bindControlToAction(final int position,
150                 ObjectAdapter adapter, Presenter presenter) {
151             Presenter.ViewHolder vh = mViewHolders.get(position);
152             Object item = adapter.get(position);
153             if (vh == null) {
154                 vh = presenter.onCreateViewHolder(mControlBar);
155                 mViewHolders.put(position, vh);
156 
157                 final Presenter.ViewHolder itemViewHolder = vh;
158                 presenter.setOnClickListener(vh, new View.OnClickListener() {
159                     @Override
160                     public void onClick(View v) {
161                         Object item = getDisplayedAdapter().get(position);
162                         if (mOnControlClickedListener != null) {
163                             mOnControlClickedListener.onControlClicked(itemViewHolder, item,
164                                     mData);
165                         }
166                     }
167                 });
168             }
169             if (vh.view.getParent() == null) {
170                 mControlBar.addView(vh.view);
171             }
172             presenter.onBindViewHolder(vh, item);
173         }
174 
175         /**
176          * Returns the adapter currently bound to the displayed controls.
177          * May be overridden in a subclass.
178          */
getDisplayedAdapter()179         ObjectAdapter getDisplayedAdapter() {
180             return mAdapter;
181         }
182     }
183 
184     private OnControlClickedListener mOnControlClickedListener;
185     private OnControlSelectedListener mOnControlSelectedListener;
186     private int mLayoutResourceId;
187     private static int sChildMarginDefault;
188     private static int sControlIconWidth;
189 
190     /**
191      * Constructor for a ControlBarPresenter.
192      *
193      * @param layoutResourceId The resource id of the layout for this presenter.
194      */
ControlBarPresenter(int layoutResourceId)195     public ControlBarPresenter(int layoutResourceId) {
196         mLayoutResourceId = layoutResourceId;
197     }
198 
199     /**
200      * Returns the layout resource id.
201      */
getLayoutResourceId()202     public int getLayoutResourceId() {
203         return mLayoutResourceId;
204     }
205 
206     /**
207      * Sets the listener for control clicked events.
208      */
setOnControlClickedListener(OnControlClickedListener listener)209     public void setOnControlClickedListener(OnControlClickedListener listener) {
210         mOnControlClickedListener = listener;
211     }
212 
213     /**
214      * Returns the listener for control clicked events.
215      */
getOnItemViewClickedListener()216     public OnControlClickedListener getOnItemViewClickedListener() {
217         return mOnControlClickedListener;
218     }
219 
220     /**
221      * Sets the listener for control selection.
222      */
setOnControlSelectedListener(OnControlSelectedListener listener)223     public void setOnControlSelectedListener(OnControlSelectedListener listener) {
224         mOnControlSelectedListener = listener;
225     }
226 
227     /**
228      * Returns the listener for control selection.
229      */
getOnItemControlListener()230     public OnControlSelectedListener getOnItemControlListener() {
231         return mOnControlSelectedListener;
232     }
233 
setBackgroundColor(ViewHolder vh, int color)234     public void setBackgroundColor(ViewHolder vh, int color) {
235         vh.mControlsContainer.setBackgroundColor(color);
236     }
237 
238     @Override
onCreateViewHolder(ViewGroup parent)239     public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
240         View v = LayoutInflater.from(parent.getContext())
241             .inflate(getLayoutResourceId(), parent, false);
242         return new ViewHolder(v);
243     }
244 
245     @Override
onBindViewHolder(Presenter.ViewHolder holder, Object item)246     public void onBindViewHolder(Presenter.ViewHolder holder, Object item) {
247         ViewHolder vh = (ViewHolder) holder;
248         BoundData data = (BoundData) item;
249         if (vh.mAdapter != data.adapter) {
250             vh.mAdapter = data.adapter;
251             if (vh.mAdapter != null) {
252                 vh.mAdapter.registerObserver(vh.mDataObserver);
253             }
254         }
255         vh.mPresenter = data.presenter;
256         vh.mData = data;
257         vh.showControls(vh.mPresenter);
258     }
259 
260     @Override
onUnbindViewHolder(Presenter.ViewHolder holder)261     public void onUnbindViewHolder(Presenter.ViewHolder holder) {
262         ViewHolder vh = (ViewHolder) holder;
263         if (vh.mAdapter != null) {
264             vh.mAdapter.unregisterObserver(vh.mDataObserver);
265             vh.mAdapter = null;
266         }
267         vh.mData = null;
268     }
269 
getChildMarginDefault(Context context)270     int getChildMarginDefault(Context context) {
271         if (sChildMarginDefault == 0) {
272             sChildMarginDefault = context.getResources().getDimensionPixelSize(
273                     R.dimen.lb_playback_controls_child_margin_default);
274         }
275         return sChildMarginDefault;
276     }
277 
getControlIconWidth(Context context)278     int getControlIconWidth(Context context) {
279         if (sControlIconWidth == 0) {
280             sControlIconWidth = context.getResources().getDimensionPixelSize(
281                     R.dimen.lb_control_icon_width);
282         }
283         return sControlIconWidth;
284     }
285 }
286