PlaybackOverlayFragment.java revision da2811c870d38a56f9ea4548ffb36f18d5f20a08
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.animation.AnimatorInflater;
20import android.animation.TimeInterpolator;
21import android.animation.ValueAnimator;
22import android.view.InputEvent;
23import android.view.animation.AccelerateInterpolator;
24import android.animation.ValueAnimator.AnimatorUpdateListener;
25import android.content.Context;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.Message;
29import android.support.v7.widget.RecyclerView;
30import android.support.v17.leanback.R;
31import android.support.v17.leanback.animation.LogAccelerateInterpolator;
32import android.support.v17.leanback.animation.LogDecelerateInterpolator;
33import android.support.v17.leanback.widget.Presenter;
34import android.support.v17.leanback.widget.ItemBridgeAdapter;
35import android.support.v17.leanback.widget.ObjectAdapter;
36import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
37import android.support.v17.leanback.widget.VerticalGridView;
38import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
39import android.util.Log;
40import android.view.KeyEvent;
41import android.view.LayoutInflater;
42import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewGroup;
45
46import java.util.ArrayList;
47
48/**
49 * A fragment for displaying playback controls and related content.
50 * The {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
51 * at position 0 in the adapter.
52 */
53public class PlaybackOverlayFragment extends DetailsFragment {
54
55    /**
56     * No background.
57     */
58    public static final int BG_NONE = 0;
59
60    /**
61     * A dark translucent background.
62     */
63    public static final int BG_DARK = 1;
64
65    /**
66     * A light translucent background.
67     */
68    public static final int BG_LIGHT = 2;
69
70    public static class OnFadeCompleteListener {
71        public void onFadeInComplete() {
72        }
73        public void onFadeOutComplete() {
74        }
75    }
76
77    public interface InputEventHandler {
78        /**
79         * Called when an {@link InputEvent} is received.
80         *
81         * @return If the event should be consumed, return true. To allow the event to
82         * continue on to the next handler, return false.
83         */
84        public boolean handleInputEvent(InputEvent event);
85    }
86
87    private static final String TAG = "PlaybackOverlayFragment";
88    private static final boolean DEBUG = false;
89    private static final int ANIMATION_MULTIPLIER = 1;
90
91    private static int START_FADE_OUT = 1;
92
93    // Fading status
94    private static final int IDLE = 0;
95    private static final int IN = 1;
96    private static final int OUT = 2;
97
98    private int mAlignPosition;
99    private int mPaddingBottom;
100    private View mRootView;
101    private int mBackgroundType = BG_DARK;
102    private int mBgDarkColor;
103    private int mBgLightColor;
104    private int mShowTimeMs;
105    private int mMajorFadeTranslateY, mMinorFadeTranslateY;
106    private int mAnimationTranslateY;
107    private OnFadeCompleteListener mFadeCompleteListener;
108    private InputEventHandler mInputEventHandler;
109    private boolean mFadingEnabled = true;
110    private int mFadingStatus = IDLE;
111    private int mBgAlpha;
112    private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
113    private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
114    private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
115    private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
116    private boolean mTranslateAnimationEnabled;
117    private boolean mResetControlsToPrimaryActionsPending;
118    private RecyclerView.ItemAnimator mItemAnimator;
119
120    private final Animator.AnimatorListener mFadeListener =
121            new Animator.AnimatorListener() {
122        @Override
123        public void onAnimationStart(Animator animation) {
124            enableVerticalGridAnimations(false);
125        }
126        @Override
127        public void onAnimationRepeat(Animator animation) {
128        }
129        @Override
130        public void onAnimationCancel(Animator animation) {
131        }
132        @Override
133        public void onAnimationEnd(Animator animation) {
134            if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
135            if (mBgAlpha > 0) {
136                enableVerticalGridAnimations(true);
137                startFadeTimer();
138                if (mFadeCompleteListener != null) {
139                    mFadeCompleteListener.onFadeInComplete();
140                }
141            } else {
142                if (getVerticalGridView() != null) {
143                    // Reset focus to the controls row
144                    getVerticalGridView().setSelectedPosition(0);
145                    resetControlsToPrimaryActions(null);
146                }
147                if (mFadeCompleteListener != null) {
148                    mFadeCompleteListener.onFadeOutComplete();
149                }
150            }
151            mFadingStatus = IDLE;
152        }
153    };
154
155    private final Handler mHandler = new Handler() {
156        @Override
157        public void handleMessage(Message message) {
158            if (message.what == START_FADE_OUT && mFadingEnabled) {
159                fade(false);
160            }
161        }
162    };
163
164    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
165            new VerticalGridView.OnTouchInterceptListener() {
166        public boolean onInterceptTouchEvent(MotionEvent event) {
167            return onInterceptInputEvent(event);
168        }
169    };
170
171    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
172            new VerticalGridView.OnKeyInterceptListener() {
173        public boolean onInterceptKeyEvent(KeyEvent event) {
174            return onInterceptInputEvent(event);
175        }
176    };
177
178    private void setBgAlpha(int alpha) {
179        mBgAlpha = alpha;
180        if (mRootView != null) {
181            mRootView.getBackground().setAlpha(alpha);
182        }
183    }
184
185    private void enableVerticalGridAnimations(boolean enable) {
186        if (getVerticalGridView() != null) {
187            getVerticalGridView().setAnimateChildLayout(enable);
188        }
189    }
190
191    private void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
192        if (vh == null && getVerticalGridView() != null) {
193            vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
194        }
195        if (vh == null) {
196            mResetControlsToPrimaryActionsPending = true;
197        } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
198            mResetControlsToPrimaryActionsPending = false;
199            ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
200                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
201        }
202    }
203
204    /**
205     * Enables or disables view fading.  If enabled,
206     * the view will be faded in when the fragment starts,
207     * and will fade out after a time period.  The timeout
208     * period is reset each time {@link #tickle} is called.
209     *
210     */
211    public void setFadingEnabled(boolean enabled) {
212        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
213        if (enabled != mFadingEnabled) {
214            mFadingEnabled = enabled;
215            if (mFadingEnabled) {
216                if (isResumed() && mFadingStatus == IDLE
217                        && !mHandler.hasMessages(START_FADE_OUT)) {
218                    startFadeTimer();
219                }
220            } else {
221                // Ensure fully opaque
222                mHandler.removeMessages(START_FADE_OUT);
223                fade(true);
224            }
225        }
226    }
227
228    /**
229     * Returns true if view fading is enabled.
230     */
231    public boolean isFadingEnabled() {
232        return mFadingEnabled;
233    }
234
235    /**
236     * Sets the listener to be called when fade in or out has completed.
237     */
238    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
239        mFadeCompleteListener = listener;
240    }
241
242    /**
243     * Returns the listener to be called when fade in or out has completed.
244     */
245    public OnFadeCompleteListener getFadeCompleteListener() {
246        return mFadeCompleteListener;
247    }
248
249    /**
250     * Sets the input event handler.
251     */
252    public final void setInputEventHandler(InputEventHandler handler) {
253        mInputEventHandler = handler;
254    }
255
256    /**
257     * Returns the input event handler.
258     */
259    public final InputEventHandler getInputEventHandler() {
260        return mInputEventHandler;
261    }
262
263    /**
264     * Tickles the playback controls.  Fades in the view if it was faded out,
265     * otherwise resets the fade out timer.  Tickling on input events is handled
266     * by the fragment.
267     */
268    public void tickle() {
269        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
270        if (!mFadingEnabled || !isResumed()) {
271            return;
272        }
273        if (mHandler.hasMessages(START_FADE_OUT)) {
274            // Restart the timer
275            startFadeTimer();
276        } else {
277            fade(true);
278        }
279    }
280
281    private static boolean isConsumableKey(KeyEvent keyEvent) {
282        if (keyEvent.isSystem()) {
283            return false;
284        }
285        return true;
286    }
287
288    private boolean onInterceptInputEvent(InputEvent event) {
289        if (DEBUG) Log.v(TAG, "onInterceptInputEvent status " + mFadingStatus + " event " + event);
290        boolean consumeEvent = (mFadingStatus == IDLE && mBgAlpha == 0);
291        if (event instanceof KeyEvent) {
292            if (consumeEvent) {
293                consumeEvent = isConsumableKey((KeyEvent) event);
294            }
295            int keyCode = ((KeyEvent) event).getKeyCode();
296            // Back key typically means we're leaving the fragment
297            if (keyCode != KeyEvent.KEYCODE_BACK) {
298                tickle();
299            }
300        } else {
301            tickle();
302        }
303        if (!consumeEvent && mInputEventHandler != null) {
304            consumeEvent = mInputEventHandler.handleInputEvent(event);
305        }
306        return consumeEvent;
307    }
308
309    @Override
310    public void onResume() {
311        super.onResume();
312        if (mFadingEnabled) {
313            setBgAlpha(0);
314            fade(true);
315        }
316        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
317        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
318    }
319
320    private void startFadeTimer() {
321        if (mHandler != null) {
322            mHandler.removeMessages(START_FADE_OUT);
323            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
324        }
325    }
326
327    private static ValueAnimator loadAnimator(Context context, int resId) {
328        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
329        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
330        return animator;
331    }
332
333    private void loadBgAnimator() {
334        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
335            @Override
336            public void onAnimationUpdate(ValueAnimator arg0) {
337                setBgAlpha((Integer) arg0.getAnimatedValue());
338            }
339        };
340
341        mBgFadeInAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_in);
342        mBgFadeInAnimator.addUpdateListener(listener);
343        mBgFadeInAnimator.addListener(mFadeListener);
344
345        mBgFadeOutAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_out);
346        mBgFadeOutAnimator.addUpdateListener(listener);
347        mBgFadeOutAnimator.addListener(mFadeListener);
348    }
349
350    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
351    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
352
353    private View getControlRowView() {
354        if (getVerticalGridView() == null) {
355            return null;
356        }
357        RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
358        if (vh == null) {
359            return null;
360        }
361        return vh.itemView;
362    }
363
364    private void loadControlRowAnimator() {
365        final AnimatorListener listener = new AnimatorListener() {
366            @Override
367            void getViews(ArrayList<View> views) {
368                View view = getControlRowView();
369                if (view != null) {
370                    views.add(view);
371                }
372            }
373        };
374        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
375            @Override
376            public void onAnimationUpdate(ValueAnimator arg0) {
377                View view = getControlRowView();
378                if (view != null) {
379                    final float fraction = (Float) arg0.getAnimatedValue();
380                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
381                    view.setAlpha(fraction);
382                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
383                }
384            }
385        };
386
387        mControlRowFadeInAnimator = loadAnimator(
388                getActivity(), R.animator.lb_playback_controls_fade_in);
389        mControlRowFadeInAnimator.addUpdateListener(updateListener);
390        mControlRowFadeInAnimator.addListener(listener);
391        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
392
393        mControlRowFadeOutAnimator = loadAnimator(
394                getActivity(), R.animator.lb_playback_controls_fade_out);
395        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
396        mControlRowFadeOutAnimator.addListener(listener);
397        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
398    }
399
400    private void loadOtherRowAnimator() {
401        final AnimatorListener listener = new AnimatorListener() {
402            @Override
403            void getViews(ArrayList<View> views) {
404                if (getVerticalGridView() == null) {
405                    return;
406                }
407                final int count = getVerticalGridView().getChildCount();
408                for (int i = 0; i < count; i++) {
409                    View view = getVerticalGridView().getChildAt(i);
410                    if (view != null) {
411                        views.add(view);
412                    }
413                }
414            }
415        };
416        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
417            @Override
418            public void onAnimationUpdate(ValueAnimator arg0) {
419                final float fraction = (Float) arg0.getAnimatedValue();
420                for (View view : listener.mViews) {
421                    if (getVerticalGridView().getChildPosition(view) > 0) {
422                        view.setAlpha(fraction);
423                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
424                    }
425                }
426            }
427        };
428
429        mOtherRowFadeInAnimator = loadAnimator(
430                getActivity(), R.animator.lb_playback_controls_fade_in);
431        mOtherRowFadeInAnimator.addListener(listener);
432        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
433        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
434
435        mOtherRowFadeOutAnimator = loadAnimator(
436                getActivity(), R.animator.lb_playback_controls_fade_out);
437        mOtherRowFadeOutAnimator.addListener(listener);
438        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
439        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
440    }
441
442    private void loadDescriptionAnimator() {
443        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
444            @Override
445            public void onAnimationUpdate(ValueAnimator arg0) {
446                if (getVerticalGridView() == null) {
447                    return;
448                }
449                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
450                        getVerticalGridView().findViewHolderForPosition(0);
451                if (adapterVh != null && adapterVh.getViewHolder()
452                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
453                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
454                            adapterVh.getViewHolder()).mDescriptionViewHolder;
455                    if (vh != null) {
456                        vh.view.setAlpha((Float) arg0.getAnimatedValue());
457                    }
458                }
459            }
460        };
461
462        mDescriptionFadeInAnimator = loadAnimator(
463                getActivity(), R.animator.lb_playback_description_fade_in);
464        mDescriptionFadeInAnimator.addUpdateListener(listener);
465        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
466
467        mDescriptionFadeOutAnimator = loadAnimator(
468                getActivity(), R.animator.lb_playback_description_fade_out);
469        mDescriptionFadeOutAnimator.addUpdateListener(listener);
470    }
471
472    private void fade(boolean fadeIn) {
473        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
474        if (getView() == null) {
475            return;
476        }
477        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
478            if (DEBUG) Log.v(TAG, "requested fade in progress");
479            return;
480        }
481        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
482            if (DEBUG) Log.v(TAG, "fade is no-op");
483            return;
484        }
485
486        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0 ?
487                mMajorFadeTranslateY : mMinorFadeTranslateY;
488
489        if (mFadingStatus == IDLE) {
490            if (fadeIn) {
491                mBgFadeInAnimator.start();
492                mControlRowFadeInAnimator.start();
493                mOtherRowFadeInAnimator.start();
494                mDescriptionFadeInAnimator.start();
495            } else {
496                mBgFadeOutAnimator.start();
497                mControlRowFadeOutAnimator.start();
498                mOtherRowFadeOutAnimator.start();
499                mDescriptionFadeOutAnimator.start();
500            }
501        } else {
502            if (fadeIn) {
503                mBgFadeOutAnimator.reverse();
504                mControlRowFadeOutAnimator.reverse();
505                mOtherRowFadeOutAnimator.reverse();
506                mDescriptionFadeOutAnimator.reverse();
507            } else {
508                mBgFadeInAnimator.reverse();
509                mControlRowFadeInAnimator.reverse();
510                mOtherRowFadeInAnimator.reverse();
511                mDescriptionFadeInAnimator.reverse();
512            }
513        }
514
515        // If fading in while control row is focused, set initial translationY so
516        // views slide in from below.
517        if (fadeIn && mFadingStatus == IDLE) {
518            final int count = getVerticalGridView().getChildCount();
519            for (int i = 0; i < count; i++) {
520                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
521            }
522        }
523
524        mFadingStatus = fadeIn ? IN : OUT;
525    }
526
527    /**
528     * Sets the list of rows for the fragment.
529     */
530    @Override
531    public void setAdapter(ObjectAdapter adapter) {
532        if (getAdapter() != null) {
533            getAdapter().unregisterObserver(mObserver);
534        }
535        super.setAdapter(adapter);
536        if (adapter != null) {
537            adapter.registerObserver(mObserver);
538        }
539    }
540
541    @Override
542    void setVerticalGridViewLayout(VerticalGridView listview) {
543        if (listview == null) {
544            return;
545        }
546        // Padding affects alignment when last row is focused
547        // (last is first when there's only one row).
548        setBottomPadding(listview, mPaddingBottom);
549
550        // Item alignment affects focused row that isn't the last.
551        listview.setItemAlignmentOffset(mAlignPosition);
552        listview.setItemAlignmentOffsetPercent(100);
553
554        // Push rows to the bottom.
555        listview.setWindowAlignmentOffset(0);
556        listview.setWindowAlignmentOffsetPercent(100);
557        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
558    }
559
560    private static void setBottomPadding(View view, int padding) {
561        view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
562                view.getPaddingRight(), padding);
563    }
564
565    @Override
566    public void onCreate(Bundle savedInstanceState) {
567        super.onCreate(savedInstanceState);
568
569        mAlignPosition =
570                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
571        mPaddingBottom =
572                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
573        mBgDarkColor =
574                getResources().getColor(R.color.lb_playback_controls_background_dark);
575        mBgLightColor =
576                getResources().getColor(R.color.lb_playback_controls_background_light);
577        mShowTimeMs =
578                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
579        mMajorFadeTranslateY =
580                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
581        mMinorFadeTranslateY =
582                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
583
584        loadBgAnimator();
585        loadControlRowAnimator();
586        loadOtherRowAnimator();
587        loadDescriptionAnimator();
588    }
589
590    /**
591     * Sets the background type.
592     *
593     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
594     */
595    public void setBackgroundType(int type) {
596        switch (type) {
597        case BG_LIGHT:
598        case BG_DARK:
599        case BG_NONE:
600            if (type != mBackgroundType) {
601                mBackgroundType = type;
602                updateBackground();
603            }
604            break;
605        default:
606            throw new IllegalArgumentException("Invalid background type");
607        }
608    }
609
610    /**
611     * Returns the background type.
612     */
613    public int getBackgroundType() {
614        return mBackgroundType;
615    }
616
617    private void updateBackground() {
618        if (mRootView != null) {
619            int color = mBgDarkColor;
620            switch (mBackgroundType) {
621                case BG_DARK: break;
622                case BG_LIGHT: color = mBgLightColor; break;
623                case BG_NONE: color = Color.TRANSPARENT; break;
624            }
625            mRootView.setBackground(new ColorDrawable(color));
626        }
627    }
628
629    private void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
630        // Add extra space between rows 0 and 1
631        if (vh == null && getVerticalGridView() != null) {
632            vh = (ItemBridgeAdapter.ViewHolder)
633                    getVerticalGridView().findViewHolderForPosition(0);
634        }
635        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
636            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
637            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
638                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
639                    adapterSize > 1);
640        }
641    }
642
643    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
644            new ItemBridgeAdapter.AdapterListener() {
645        @Override
646        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
647            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
648            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
649                if (DEBUG) Log.v(TAG, "setting alpha to 0");
650                vh.getViewHolder().view.setAlpha(0);
651            }
652            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
653                resetControlsToPrimaryActions(vh);
654            }
655        }
656        @Override
657        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
658            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
659            // Reset animation state
660            vh.getViewHolder().view.setAlpha(1f);
661            vh.getViewHolder().view.setTranslationY(0);
662            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
663                Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
664                        vh.getViewHolder()).mDescriptionViewHolder;
665                if (descriptionVh != null) {
666                    descriptionVh.view.setAlpha(1f);
667                }
668            }
669        }
670        @Override
671        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
672            if (vh.getPosition() == 0) {
673                updateControlsBottomSpace(vh);
674            }
675        }
676    };
677
678    @Override
679    public View onCreateView(LayoutInflater inflater, ViewGroup container,
680            Bundle savedInstanceState) {
681        mRootView = super.onCreateView(inflater, container, savedInstanceState);
682        mBgAlpha = 255;
683        updateBackground();
684        getRowsFragment().setExternalAdapterListener(mAdapterListener);
685        return mRootView;
686    }
687
688    @Override
689    public void onDestroyView() {
690        mRootView = null;
691        super.onDestroyView();
692    }
693
694    private final DataObserver mObserver = new DataObserver() {
695        public void onChanged() {
696            updateControlsBottomSpace(null);
697        }
698    };
699
700    static abstract class AnimatorListener implements Animator.AnimatorListener {
701        ArrayList<View> mViews = new ArrayList<View>();
702        ArrayList<Integer> mLayerType = new ArrayList<Integer>();
703
704        public void onAnimationCancel(Animator animation) {
705        }
706        public void onAnimationRepeat(Animator animation) {
707        }
708        public void onAnimationStart(Animator animation) {
709            getViews(mViews);
710            for (View view : mViews) {
711                mLayerType.add(view.getLayerType());
712                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
713            }
714        }
715        public void onAnimationEnd(Animator animation) {
716            for (int i = 0; i < mViews.size(); i++) {
717                mViews.get(i).setLayerType(mLayerType.get(i), null);
718            }
719            mLayerType.clear();
720            mViews.clear();
721        }
722        abstract void getViews(ArrayList<View> views);
723    };
724}
725