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