PlaybackControlsRowPresenter.java revision 764e09c1353fa987144a3acecbe94a091f6229a6
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 */
14package android.support.v17.leanback.widget;
15
16import android.graphics.drawable.Drawable;
17import android.support.v17.leanback.R;
18import android.support.v17.leanback.widget.ControlBarPresenter.OnControlClickedListener;
19import android.support.v17.leanback.widget.ControlBarPresenter.OnControlSelectedListener;
20import android.content.Context;
21import android.graphics.Color;
22import android.util.TypedValue;
23import android.view.KeyEvent;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.ViewGroup.LayoutParams;
28import android.view.ViewGroup.MarginLayoutParams;
29import android.widget.ImageView;
30import android.widget.LinearLayout;
31
32/**
33 * A PlaybackControlsRowPresenter renders a {@link PlaybackControlsRow} to display a
34 * series of playback control buttons. Typically this row will be the first row in a fragment
35 * such as the {@link android.support.v17.leanback.app.PlaybackOverlayFragment
36 * PlaybackControlsFragment}.
37 */
38public class PlaybackControlsRowPresenter extends RowPresenter {
39
40    static class BoundData extends PlaybackControlsPresenter.BoundData {
41        ViewHolder mRowViewHolder;
42    }
43
44    /**
45     * A ViewHolder for the PlaybackControlsRow.
46     */
47    public class ViewHolder extends RowPresenter.ViewHolder {
48        public final Presenter.ViewHolder mDescriptionViewHolder;
49        final ViewGroup mCard;
50        final ImageView mImageView;
51        final ViewGroup mDescriptionDock;
52        final ViewGroup mControlsDock;
53        final ViewGroup mSecondaryControlsDock;
54        final View mSpacer;
55        final View mBottomSpacer;
56        View mBgView;
57        int mControlsDockMarginStart;
58        int mControlsDockMarginEnd;
59        PlaybackControlsPresenter.ViewHolder mControlsVh;
60        Presenter.ViewHolder mSecondaryControlsVh;
61        BoundData mControlsBoundData = new BoundData();
62        BoundData mSecondaryBoundData = new BoundData();
63        Presenter.ViewHolder mSelectedViewHolder;
64        Object mSelectedItem;
65        final PlaybackControlsRow.OnPlaybackStateChangedListener mListener =
66                new PlaybackControlsRow.OnPlaybackStateChangedListener() {
67            @Override
68            public void onCurrentTimeChanged(int ms) {
69                mPlaybackControlsPresenter.setCurrentTime(mControlsVh, ms);
70            }
71            @Override
72            public void onBufferedProgressChanged(int ms) {
73                mPlaybackControlsPresenter.setSecondaryProgress(mControlsVh, ms);
74            }
75        };
76
77        ViewHolder(View rootView, Presenter descriptionPresenter) {
78            super(rootView);
79            mCard = (ViewGroup) rootView.findViewById(R.id.controls_card);
80            mImageView = (ImageView) rootView.findViewById(R.id.image);
81            mDescriptionDock = (ViewGroup) rootView.findViewById(R.id.description_dock);
82            mControlsDock = (ViewGroup) rootView.findViewById(R.id.controls_dock);
83            mSecondaryControlsDock =
84                    (ViewGroup) rootView.findViewById(R.id.secondary_controls_dock);
85            mSpacer = rootView.findViewById(R.id.spacer);
86            mBottomSpacer = rootView.findViewById(R.id.bottom_spacer);
87            mDescriptionViewHolder = descriptionPresenter == null ? null :
88                    descriptionPresenter.onCreateViewHolder(mDescriptionDock);
89            if (mDescriptionViewHolder != null) {
90                mDescriptionDock.addView(mDescriptionViewHolder.view);
91            }
92        }
93
94        void dispatchItemSelection() {
95            if (!isSelected()) {
96                return;
97            }
98            if (mSelectedViewHolder == null) {
99                if (getOnItemSelectedListener() != null) {
100                    getOnItemSelectedListener().onItemSelected(null, getRow());
101                }
102                if (getOnItemViewSelectedListener() != null) {
103                    getOnItemViewSelectedListener().onItemSelected(null, null,
104                            ViewHolder.this, getRow());
105                }
106            } else {
107                if (getOnItemSelectedListener() != null) {
108                    getOnItemSelectedListener().onItemSelected(mSelectedItem, getRow());
109                }
110                if (getOnItemViewSelectedListener() != null) {
111                    getOnItemViewSelectedListener().onItemSelected(mSelectedViewHolder, mSelectedItem,
112                            ViewHolder.this, getRow());
113                }
114            }
115        };
116
117        Presenter getPresenter(boolean primary) {
118            ObjectAdapter adapter = primary ?
119                    ((PlaybackControlsRow) getRow()).getPrimaryActionsAdapter() :
120                            ((PlaybackControlsRow) getRow()).getSecondaryActionsAdapter();
121            if (adapter == null) {
122                return null;
123            }
124            if (adapter.getPresenterSelector() instanceof ControlButtonPresenterSelector) {
125                ControlButtonPresenterSelector selector =
126                        (ControlButtonPresenterSelector) adapter.getPresenterSelector();
127                return primary ? selector.getPrimaryPresenter() :
128                    selector.getSecondaryPresenter();
129            }
130            return adapter.getPresenter(adapter.size() > 0 ? adapter.get(0) : null);
131        }
132
133        void setBackground(View view) {
134            if (mBgView != null) {
135                RoundedRectHelper.getInstance().clearBackground(mBgView);
136                ShadowHelper.getInstance().setZ(mBgView, 0);
137            }
138            mBgView = view;
139            RoundedRectHelper.getInstance().setRoundedRectBackground(view, mBackgroundColorSet ?
140                    mBackgroundColor : getDefaultBackgroundColor(view.getContext()));
141
142            if (sShadowZ == 0) {
143                sShadowZ = view.getResources().getDimensionPixelSize(
144                        R.dimen.lb_playback_controls_z);
145            }
146            ShadowHelper.getInstance().setZ(view, sShadowZ);
147        }
148    }
149
150    private int mBackgroundColor = Color.TRANSPARENT;
151    private boolean mBackgroundColorSet;
152    private int mProgressColor = Color.TRANSPARENT;
153    private boolean mProgressColorSet;
154    private boolean mSecondaryActionsHidden;
155    private Presenter mDescriptionPresenter;
156    private PlaybackControlsPresenter mPlaybackControlsPresenter;
157    private ControlBarPresenter mSecondaryControlsPresenter;
158    private OnActionClickedListener mOnActionClickedListener;
159    private static float sShadowZ;
160
161    private final OnControlSelectedListener mOnControlSelectedListener =
162            new OnControlSelectedListener() {
163        @Override
164        public void onControlSelected(Presenter.ViewHolder itemViewHolder, Object item,
165                ControlBarPresenter.BoundData data) {
166            ViewHolder vh = ((BoundData) data).mRowViewHolder;
167            if (vh.mSelectedViewHolder != itemViewHolder || vh.mSelectedItem != item) {
168                vh.mSelectedViewHolder = itemViewHolder;
169                vh.mSelectedItem = item;
170                vh.dispatchItemSelection();
171            }
172        }
173    };
174
175    private final OnControlClickedListener mOnControlClickedListener =
176            new OnControlClickedListener() {
177        @Override
178        public void onControlClicked(Presenter.ViewHolder itemViewHolder, Object item,
179                ControlBarPresenter.BoundData data) {
180            ViewHolder vh = ((BoundData) data).mRowViewHolder;
181            if (getOnItemClickedListener() != null) {
182                getOnItemClickedListener().onItemClicked(item, vh.getRow());
183            }
184            if (getOnItemViewClickedListener() != null) {
185                getOnItemViewClickedListener().onItemClicked(itemViewHolder, item,
186                        vh, vh.getRow());
187            }
188            if (mOnActionClickedListener != null && item instanceof Action) {
189                mOnActionClickedListener.onActionClicked((Action) item);
190            }
191        }
192    };
193
194    /**
195     * Constructor for a PlaybackControlsRowPresenter.
196     *
197     * @param descriptionPresenter Presenter for displaying item details.
198     */
199    public PlaybackControlsRowPresenter(Presenter descriptionPresenter) {
200        setHeaderPresenter(null);
201        setSelectEffectEnabled(false);
202
203        mDescriptionPresenter = descriptionPresenter;
204        mPlaybackControlsPresenter = new PlaybackControlsPresenter(R.layout.lb_playback_controls);
205        mSecondaryControlsPresenter = new ControlBarPresenter(R.layout.lb_control_bar);
206
207        mPlaybackControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
208        mSecondaryControlsPresenter.setOnControlSelectedListener(mOnControlSelectedListener);
209        mPlaybackControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
210        mSecondaryControlsPresenter.setOnControlClickedListener(mOnControlClickedListener);
211    }
212
213    /**
214     * Constructor for a PlaybackControlsRowPresenter.
215     */
216    public PlaybackControlsRowPresenter() {
217        this(null);
218    }
219
220    /**
221     * Sets the listener for {@link Action} click events.
222     */
223    public void setOnActionClickedListener(OnActionClickedListener listener) {
224        mOnActionClickedListener = listener;
225    }
226
227    /**
228     * Gets the listener for {@link Action} click events.
229     */
230    public OnActionClickedListener getOnActionClickedListener() {
231        return mOnActionClickedListener;
232    }
233
234    /**
235     * Sets the background color.  If not set, a default from the theme will be used.
236     */
237    public void setBackgroundColor(int color) {
238        mBackgroundColor = color;
239        mBackgroundColorSet = true;
240    }
241
242    /**
243     * Returns the background color.  If no background color was set, transparent
244     * is returned.
245     */
246    public int getBackgroundColor() {
247        return mBackgroundColor;
248    }
249
250    /**
251     * Sets the primary color for the progress bar.  If not set, a default from
252     * the theme will be used.
253     */
254    public void setProgressColor(int color) {
255        mProgressColor = color;
256        mProgressColorSet = true;
257    }
258
259    /**
260     * Returns the primary color for the progress bar.  If no color was set, transparent
261     * is returned.
262     */
263    public int getProgressColor() {
264        return mProgressColor;
265    }
266
267    /**
268     * Sets the secondary actions to be hidden behind a "more actions" button.
269     * When "more actions" is selected, the primary actions are replaced with
270     * the secondary actions.
271     */
272    public void setSecondaryActionsHidden(boolean hidden) {
273        mSecondaryActionsHidden = hidden;
274    }
275
276    /**
277     * Returns true if secondary actions are hidden.
278     */
279    public boolean areSecondaryActionsHidden() {
280        return mSecondaryActionsHidden;
281    }
282
283    /**
284     * Shows or hides space at the bottom of the playback controls row.
285     * This allows the row to hug the bottom of the display when no
286     * other rows are present.
287     */
288    public void showBottomSpace(ViewHolder vh, boolean show) {
289        vh.mBottomSpacer.setVisibility(show ? View.VISIBLE : View.GONE);
290    }
291
292    /**
293     * Display the primary actions.
294     */
295    public void showPrimaryActions(ViewHolder vh) {
296        mPlaybackControlsPresenter.showPrimaryActions(vh.mControlsVh);
297    }
298
299    private int getDefaultBackgroundColor(Context context) {
300        TypedValue outValue = new TypedValue();
301        if (context.getTheme().resolveAttribute(R.attr.defaultBrandColor, outValue, true)) {
302            return context.getResources().getColor(outValue.resourceId);
303        }
304        return context.getResources().getColor(R.color.lb_default_brand_color);
305    }
306
307    private int getDefaultProgressColor(Context context) {
308        TypedValue outValue = new TypedValue();
309        if (context.getTheme()
310                .resolveAttribute(R.attr.playbackProgressPrimaryColor, outValue, true)) {
311            return context.getResources().getColor(outValue.resourceId);
312        }
313        return context.getResources().getColor(R.color.lb_playback_progress_color_no_theme);
314    }
315
316    @Override
317    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
318        View v = LayoutInflater.from(parent.getContext())
319            .inflate(R.layout.lb_playback_controls_row, parent, false);
320        ViewHolder vh = new ViewHolder(v, mDescriptionPresenter);
321        initRow(vh);
322        return vh;
323    }
324
325    private void initRow(final ViewHolder vh) {
326        MarginLayoutParams lp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
327        vh.mControlsDockMarginStart = lp.getMarginStart();
328        vh.mControlsDockMarginEnd = lp.getMarginEnd();
329
330        vh.mControlsVh = (PlaybackControlsPresenter.ViewHolder)
331                mPlaybackControlsPresenter.onCreateViewHolder(vh.mControlsDock);
332        mPlaybackControlsPresenter.setProgressColor(vh.mControlsVh,
333                mProgressColorSet ? mProgressColor :
334                        getDefaultProgressColor(vh.mControlsDock.getContext()));
335        vh.mControlsDock.addView(vh.mControlsVh.view);
336
337        vh.mSecondaryControlsVh =
338                mSecondaryControlsPresenter.onCreateViewHolder(vh.mSecondaryControlsDock);
339        if (!mSecondaryActionsHidden) {
340            vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view);
341        }
342        ((PlaybackControlsRowView) vh.view).setOnUnhandledKeyListener(
343                new PlaybackControlsRowView.OnUnhandledKeyListener() {
344            @Override
345            public boolean onUnhandledKey(KeyEvent event) {
346                if (vh.getOnKeyListener() != null) {
347                    if (vh.getOnKeyListener().onKey(vh.view, event.getKeyCode(), event)) {
348                        return true;
349                    }
350                }
351                return false;
352            }
353        });
354    }
355
356    @Override
357    protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
358        super.onBindRowViewHolder(holder, item);
359
360        ViewHolder vh = (ViewHolder) holder;
361        PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
362
363        mPlaybackControlsPresenter.enableSecondaryActions(mSecondaryActionsHidden);
364
365        if (row.getItem() == null) {
366            vh.mDescriptionDock.setVisibility(View.GONE);
367            vh.mSpacer.setVisibility(View.GONE);
368        } else {
369            vh.mDescriptionDock.setVisibility(View.VISIBLE);
370            if (vh.mDescriptionViewHolder != null) {
371                mDescriptionPresenter.onBindViewHolder(vh.mDescriptionViewHolder, row.getItem());
372            }
373            vh.mSpacer.setVisibility(View.VISIBLE);
374        }
375
376        if (row.getImageDrawable() == null || row.getItem() == null) {
377            vh.mImageView.setImageDrawable(null);
378            updateCardLayout(vh, LayoutParams.WRAP_CONTENT);
379        } else {
380            vh.mImageView.setImageDrawable(row.getImageDrawable());
381            updateCardLayout(vh, vh.mImageView.getLayoutParams().height);
382        }
383
384        vh.mControlsBoundData.adapter = row.getPrimaryActionsAdapter();
385        vh.mControlsBoundData.secondaryActionsAdapter = row.getSecondaryActionsAdapter();
386        vh.mControlsBoundData.presenter = vh.getPresenter(true);
387        vh.mControlsBoundData.mRowViewHolder = vh;
388        mPlaybackControlsPresenter.onBindViewHolder(vh.mControlsVh, vh.mControlsBoundData);
389
390        vh.mSecondaryBoundData.adapter = row.getSecondaryActionsAdapter();
391        vh.mSecondaryBoundData.presenter = vh.getPresenter(false);
392        vh.mSecondaryBoundData.mRowViewHolder = vh;
393        mSecondaryControlsPresenter.onBindViewHolder(vh.mSecondaryControlsVh,
394                vh.mSecondaryBoundData);
395
396        mPlaybackControlsPresenter.setTotalTime(vh.mControlsVh, row.getTotalTime());
397        mPlaybackControlsPresenter.setCurrentTime(vh.mControlsVh, row.getCurrentTime());
398        mPlaybackControlsPresenter.setSecondaryProgress(vh.mControlsVh, row.getBufferedProgress());
399        row.setOnPlaybackStateChangedListener(vh.mListener);
400    }
401
402    private void updateCardLayout(ViewHolder vh, int height) {
403        LayoutParams lp = vh.mCard.getLayoutParams();
404        lp.height = height;
405        vh.mCard.setLayoutParams(lp);
406
407        MarginLayoutParams mlp = (MarginLayoutParams) vh.mControlsDock.getLayoutParams();
408        LinearLayout.LayoutParams llp =
409                (LinearLayout.LayoutParams) vh.mDescriptionDock.getLayoutParams();
410
411        if (height == LayoutParams.WRAP_CONTENT) {
412            llp.height = LayoutParams.WRAP_CONTENT;
413            mlp.setMarginStart(0);
414            mlp.setMarginEnd(0);
415            vh.setBackground(vh.mControlsDock);
416            mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, true);
417        } else {
418            llp.height = 0;
419            llp.weight = 1;
420            mlp.setMarginStart(vh.mControlsDockMarginStart);
421            mlp.setMarginEnd(vh.mControlsDockMarginEnd);
422            vh.setBackground(vh.mCard);
423            mPlaybackControlsPresenter.enableTimeMargins(vh.mControlsVh, false);
424        }
425        vh.mDescriptionDock.setLayoutParams(llp);
426        vh.mControlsDock.setLayoutParams(mlp);
427    }
428
429    @Override
430    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder holder) {
431        ViewHolder vh = (ViewHolder) holder;
432        PlaybackControlsRow row = (PlaybackControlsRow) vh.getRow();
433
434        if (vh.mDescriptionViewHolder != null) {
435            mDescriptionPresenter.onUnbindViewHolder(vh.mDescriptionViewHolder);
436        }
437        mPlaybackControlsPresenter.onUnbindViewHolder(vh.mControlsVh);
438        mSecondaryControlsPresenter.onUnbindViewHolder(vh.mSecondaryControlsVh);
439        row.setOnPlaybackStateChangedListener(null);
440
441        super.onUnbindRowViewHolder(holder);
442    }
443
444    protected void onRowViewSelected(RowPresenter.ViewHolder vh, boolean selected) {
445        super.onRowViewSelected(vh, selected);
446        if (selected) {
447            ((ViewHolder) vh).dispatchItemSelection();
448        }
449    }
450
451    @Override
452    protected void onRowViewAttachedToWindow(RowPresenter.ViewHolder vh) {
453        super.onRowViewAttachedToWindow(vh);
454        if (mDescriptionPresenter != null) {
455            mDescriptionPresenter.onViewAttachedToWindow(
456                    ((ViewHolder) vh).mDescriptionViewHolder);
457        }
458    }
459
460    @Override
461    protected void onRowViewDetachedFromWindow(RowPresenter.ViewHolder vh) {
462        super.onRowViewDetachedFromWindow(vh);
463        if (mDescriptionPresenter != null) {
464            mDescriptionPresenter.onViewDetachedFromWindow(
465                    ((ViewHolder) vh).mDescriptionViewHolder);
466        }
467    }
468}
469