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