PlaybackOverlayFragment.java revision 372d07bb41510d91a6a662a1906aceb0ee759481
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.app;
15
16import android.graphics.Color;
17import android.graphics.drawable.ColorDrawable;
18import android.animation.Animator;
19import android.os.Bundle;
20import android.os.Handler;
21import android.os.Message;
22import android.support.v17.leanback.R;
23import android.support.v17.leanback.widget.ObjectAdapter;
24import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
25import android.support.v17.leanback.widget.VerticalGridView;
26import android.util.Log;
27import android.view.KeyEvent;
28import android.view.LayoutInflater;
29import android.view.MotionEvent;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.animation.Interpolator;
33import android.view.animation.LinearInterpolator;
34
35
36/**
37 * A fragment for displaying playback controls and related content.
38 * The {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
39 * at position 0 in the adapter.
40 */
41public class PlaybackOverlayFragment extends DetailsFragment {
42
43    /**
44     * No background.
45     */
46    public static final int BG_NONE = 0;
47
48    /**
49     * A dark translucent background.
50     */
51    public static final int BG_DARK = 1;
52
53    /**
54     * A light translucent background.
55     */
56    public static final int BG_LIGHT = 2;
57
58    public static class OnFadeCompleteListener {
59        public void onFadeInComplete() {
60        }
61        public void onFadeOutComplete() {
62        }
63    }
64
65    private static final String TAG = "PlaybackOverlayFragment";
66    private static final boolean DEBUG = false;
67
68    private static int START_FADE_OUT = 1;
69
70    // Fading status
71    private static final int IDLE = 0;
72    private static final int IN = 1;
73    private static final int OUT = 2;
74
75    private int mAlignPosition;
76    private View mRootView;
77    private int mBackgroundType = BG_DARK;
78    private int mBgDarkColor;
79    private int mBgLightColor;
80    private int mFadeInDurationMs;
81    private int mFadeOutDurationMs;
82    private int mShowTimeMs;
83    private OnFadeCompleteListener mFadeCompleteListener;
84    private boolean mFadingEnabled = true;
85    private int mFadingStatus = IDLE;
86
87    private final Animator.AnimatorListener mFadeListener =
88            new Animator.AnimatorListener() {
89        @Override
90        public void onAnimationStart(Animator animation) {
91        }
92        @Override
93        public void onAnimationRepeat(Animator animation) {
94        }
95        @Override
96        public void onAnimationCancel(Animator animation) {
97        }
98        @Override
99        public void onAnimationEnd(Animator animation) {
100            float alpha = getView().getAlpha();
101            if (DEBUG) Log.v(TAG, "onAnimationEnd " + alpha);
102            if (alpha == 1) {
103                startFadeTimer();
104                if (mFadeCompleteListener != null) {
105                    mFadeCompleteListener.onFadeInComplete();
106                }
107            } else if (alpha == 0 && mFadeCompleteListener != null) {
108                mFadeCompleteListener.onFadeOutComplete();
109            }
110            mFadingStatus = IDLE;
111        }
112    };
113
114    private final Handler mHandler = new Handler() {
115        @Override
116        public void handleMessage(Message message) {
117            if (message.what == START_FADE_OUT && mFadingEnabled) {
118                fade(false);
119            }
120        }
121    };
122
123    private final Interpolator mFadeInterpolator = new LinearInterpolator();
124
125    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
126            new VerticalGridView.OnTouchInterceptListener() {
127        public boolean onInterceptTouchEvent(MotionEvent event) {
128            return onInterceptInputEvent();
129        }
130    };
131
132    private final VerticalGridView.OnMotionInterceptListener mOnMotionInterceptListener =
133            new VerticalGridView.OnMotionInterceptListener() {
134        public boolean onInterceptMotionEvent(MotionEvent event) {
135            return onInterceptInputEvent();
136        }
137    };
138
139    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
140            new VerticalGridView.OnKeyInterceptListener() {
141        public boolean onInterceptKeyEvent(KeyEvent event) {
142            return onInterceptInputEvent();
143        }
144    };
145
146    /**
147     * Enables or disables view fading.  If enabled,
148     * the view will be faded in when the fragment starts,
149     * and will fade out after a time period.  The timeout
150     * period is reset each time {@link #tickle} is called.
151     *
152     */
153    public void setFadingEnabled(boolean enabled) {
154        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
155        if (enabled != mFadingEnabled) {
156            mFadingEnabled = enabled;
157            if (isResumed()) {
158                if (mFadingEnabled) {
159                    if (mFadingStatus == IDLE && !mHandler.hasMessages(START_FADE_OUT)) {
160                        startFadeTimer();
161                    }
162                } else {
163                    // Ensure fully opaque
164                    mHandler.removeMessages(START_FADE_OUT);
165                    fade(true);
166                }
167            }
168        }
169    }
170
171    /**
172     * Returns true if view fading is enabled.
173     */
174    public boolean isFadingEnabled() {
175        return mFadingEnabled;
176    }
177
178    /**
179     * Sets the listener to be called when fade in or out has completed.
180     */
181    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
182        mFadeCompleteListener = listener;
183    }
184
185    /**
186     * Returns the listener to be called when fade in or out has completed.
187     */
188    public OnFadeCompleteListener getFadeCompleteListener() {
189        return mFadeCompleteListener;
190    }
191
192    /**
193     * Tickles the playback controls.  Fades in the view if it was faded out,
194     * otherwise resets the fade out timer.  Tickling on input events is handled
195     * by the fragment.
196     */
197    public void tickle() {
198        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
199        if (!mFadingEnabled || !isResumed()) {
200            return;
201        }
202        if (mHandler.hasMessages(START_FADE_OUT)) {
203            // Restart the timer
204            startFadeTimer();
205        } else {
206            fade(true);
207        }
208    }
209
210    private boolean onInterceptInputEvent() {
211        if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus +
212                " alpha " + getView().getAlpha());
213        boolean consumeEvent = (mFadingStatus == IDLE && getView().getAlpha() == 0);
214        tickle();
215        return consumeEvent;
216    }
217
218    @Override
219    public void onResume() {
220        super.onResume();
221        if (mFadingEnabled) {
222            getView().setAlpha(0);
223            fade(true);
224        }
225        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
226        getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener);
227        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
228    }
229
230    private void startFadeTimer() {
231        if (DEBUG) Log.v(TAG, "startFadeTime");
232        mHandler.removeMessages(START_FADE_OUT);
233        mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
234    }
235
236    private void fade(boolean fadeIn) {
237        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
238        if (getView() == null) {
239            return;
240        }
241        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
242            if (DEBUG) Log.v(TAG, "fade " + fadeIn + " in progress");
243            return;
244        }
245
246        getView().animate().alpha(fadeIn ? 1 : 0)
247                .setDuration(fadeIn ? mFadeInDurationMs : mFadeOutDurationMs)
248                .setListener(mFadeListener)
249                .setInterpolator(mFadeInterpolator)
250                .start();
251        mFadingStatus = fadeIn ? IN : OUT;
252    }
253
254    /**
255     * Sets the list of rows for the fragment.
256     */
257    @Override
258    public void setAdapter(ObjectAdapter adapter) {
259        if (getAdapter() != null) {
260            getAdapter().unregisterObserver(mObserver);
261        }
262        super.setAdapter(adapter);
263        if (adapter != null) {
264            adapter.registerObserver(mObserver);
265        }
266        setVerticalGridViewLayout(getVerticalGridView());
267    }
268
269    @Override
270    void setVerticalGridViewLayout(VerticalGridView listview) {
271        if (listview == null || getAdapter() == null) {
272            return;
273        }
274        final int alignPosition = getAdapter().size() > 1 ? mAlignPosition : 0;
275        listview.setItemAlignmentOffset(alignPosition);
276        listview.setItemAlignmentOffsetPercent(100);
277        listview.setWindowAlignmentOffset(0);
278        listview.setWindowAlignmentOffsetPercent(100);
279        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
280    }
281
282    @Override
283    public void onCreate(Bundle savedInstanceState) {
284        super.onCreate(savedInstanceState);
285
286        mAlignPosition =
287            getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
288        mBgDarkColor =
289                getResources().getColor(R.color.lb_playback_controls_background_dark);
290        mBgLightColor =
291                getResources().getColor(R.color.lb_playback_controls_background_light);
292        mFadeInDurationMs =
293                getResources().getInteger(R.integer.lb_playback_controls_fade_in_duration_ms);
294        mFadeOutDurationMs =
295                getResources().getInteger(R.integer.lb_playback_controls_fade_out_duration_ms);
296        mShowTimeMs =
297                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
298    }
299
300    /**
301     * Sets the background type.
302     *
303     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
304     */
305    public void setBackgroundType(int type) {
306        switch (type) {
307        case BG_LIGHT:
308        case BG_DARK:
309        case BG_NONE:
310            if (type != mBackgroundType) {
311                mBackgroundType = type;
312                updateBackground();
313            }
314            break;
315        default:
316            throw new IllegalArgumentException("Invalid background type");
317        }
318    }
319
320    /**
321     * Returns the background type.
322     */
323    public int getBackgroundType() {
324        return mBackgroundType;
325    }
326
327    private void updateBackground() {
328        if (mRootView != null) {
329            int color = mBgDarkColor;
330            switch (mBackgroundType) {
331                case BG_DARK: break;
332                case BG_LIGHT: color = mBgLightColor; break;
333                case BG_NONE: color = Color.TRANSPARENT; break;
334            }
335            mRootView.setBackground(new ColorDrawable(color));
336        }
337    }
338
339    @Override
340    public View onCreateView(LayoutInflater inflater, ViewGroup container,
341            Bundle savedInstanceState) {
342        mRootView = super.onCreateView(inflater, container, savedInstanceState);
343        updateBackground();
344        return mRootView;
345    }
346
347    private final DataObserver mObserver = new DataObserver() {
348        public void onChanged() {
349            setVerticalGridViewLayout(getVerticalGridView());
350        }
351    };
352}
353