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