OnboardingSupportFragment.java revision 294f8ce702e7134ab8652101d2abac47795a56e9
1/* This file is auto-generated from OnboardingFragment.java.  DO NOT MODIFY. */
2
3/*
4 * Copyright (C) 2015 The Android Open Source Project
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *      http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19package android.support.v17.leanback.app;
20
21import android.animation.Animator;
22import android.animation.AnimatorInflater;
23import android.animation.AnimatorListenerAdapter;
24import android.animation.AnimatorSet;
25import android.animation.ObjectAnimator;
26import android.animation.TimeInterpolator;
27import android.support.v4.app.Fragment;
28import android.os.Bundle;
29import android.support.annotation.Nullable;
30import android.support.v17.leanback.R;
31import android.support.v17.leanback.widget.PagingIndicator;
32import android.view.Gravity;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.View.OnClickListener;
37import android.view.View.OnKeyListener;
38import android.view.ViewGroup;
39import android.view.ViewTreeObserver.OnPreDrawListener;
40import android.view.animation.AccelerateInterpolator;
41import android.view.animation.DecelerateInterpolator;
42import android.widget.ImageView;
43import android.widget.TextView;
44
45import java.util.ArrayList;
46import java.util.List;
47
48/**
49 * An OnboardingSupportFragment provides a common and simple way to build onboarding screen for
50 * applications.
51 * <p>
52 * <h3>Building the screen</h3>
53 * The view structure of onboarding screen is composed of the common parts and custom parts. The
54 * common parts are composed of title, description and page navigator and the custom parts are
55 * composed of background, contents and foreground.
56 * <p>
57 * To build the screen views, the inherited class should override:
58 * <ul>
59 * <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
60 * size as the screen and the lowest z-order.</li>
61 * <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
62 * the content area at the center of the screen.</li>
63 * <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
64 * size as the screen and the highest z-order</li>
65 * </ul>
66 * <p>
67 * Each of these methods can return {@code null} if the application doesn't want to provide it.
68 * <p>
69 * <h3>Page information</h3>
70 * The onboarding screen may have several pages which explain the functionality of the application.
71 * The inherited class should provide the page information by overriding the methods:
72 * <p>
73 * <ul>
74 * <li>{@link #getPageCount} to provide the number of pages.</li>
75 * <li>{@link #getPageTitle} to provide the title of the page.</li>
76 * <li>{@link #getPageDescription} to provide the description of the page.</li>
77 * </ul>
78 * <p>
79 * Note that the information is used in {@link #onCreateView}, so should be initialized before
80 * calling {@code super.onCreateView} or in {@link Fragment#onAttach(android.support.v4.app.FragmentActivity)}.
81 * <p>
82 * <h3>Animation</h3>
83 * Onboarding screen has three kinds of animations:
84 * <p>
85 * <h4>Logo Splash Animation</a></h4>
86 * When onboarding screen appears, the logo splash animation is played by default. The animation
87 * fades in the logo image, pauses in a few seconds and fades it out.
88 * <p>
89 * In most cases, the logo animation needs to be customized because the logo images of applications
90 * are different from each other, or some applications may want to show their own animations.
91 * <p>
92 * The logo animation can be customized in two ways:
93 * <ul>
94 * <li>The simplest way is to provide the logo image by calling {@link #setLogoResourceId} to show
95 * the default logo animation. This method should be called in {@link Fragment#onCreateView}.</li>
96 * <li>If the logo animation is complex, then override {@link #onCreateLogoAnimation} and return the
97 * {@link Animator} object to run.</li>
98 * </ul>
99 * <p>
100 * If the inherited class provides neither the logo image nor the animation, the logo animation will
101 * be omitted.
102 * <h4>Page enter animation</h4>
103 * After logo animation finishes, page enter animation starts. The application can provide the
104 * animations of custom views by overriding {@link #onCreateEnterAnimation}.
105 * <h4>Page change animation</h4>
106 * When the page changes, the default animations of the title and description are played. The
107 * inherited class can override {@link #onPageChanged} to start the custom animations.
108 * <p>
109 * <h3>Finishing the screen</h3>
110 * <p>
111 * If the user finishes the onboarding screen after navigating all the pages,
112 * {@link #onFinishFragment} is called. The inherited class can override this method to show another
113 * fragment or activity, or just remove this fragment.
114 *
115 * @hide
116 */
117abstract public class OnboardingSupportFragment extends Fragment {
118    private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
119    private static final long START_DELAY_TITLE_MS = 33;
120    private static final long START_DELAY_DESCRIPTION_MS = 33;
121
122    private static final long HEADER_ANIMATION_DURATION_MS = 417;
123    private static final long DESCRIPTION_START_DELAY_MS = 33;
124    private static final long HEADER_APPEAR_DELAY_MS = 500;
125    private static final int SLIDE_DISTANCE = 60;
126
127    private static int sSlideDistance;
128
129    private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
130    private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR
131            = new AccelerateInterpolator();
132
133    // Keys used to save and restore the states.
134    private static final String KEY_CURRENT_PAGE_INDEX = "leanback.onboarding.current_page_index";
135
136    private PagingIndicator mPageIndicator;
137    private View mStartButton;
138    private ImageView mLogoView;
139    private TextView mTitleView;
140    private TextView mDescriptionView;
141
142    private boolean mIsLtr;
143    // No need to save/restore the logo resource ID, because the logo animation will not appear when
144    // the fragment is restored.
145    private int mLogoResourceId;
146    private boolean mEnterTransitionFinished;
147    private int mCurrentPageIndex;
148
149    private AnimatorSet mAnimator;
150
151    private final OnClickListener mOnClickListener = new OnClickListener() {
152        @Override
153        public void onClick(View view) {
154            if (!mEnterTransitionFinished) {
155                // Do not change page until the enter transition finishes.
156                return;
157            }
158            if (mCurrentPageIndex == getPageCount() - 1) {
159                onFinishFragment();
160            } else {
161                moveToNextPage();
162            }
163        }
164    };
165
166    private final OnKeyListener mOnKeyListener = new OnKeyListener() {
167        @Override
168        public boolean onKey(View v, int keyCode, KeyEvent event) {
169            if (!mEnterTransitionFinished) {
170                // Ignore key event until the enter transition finishes.
171                return keyCode != KeyEvent.KEYCODE_BACK;
172            }
173            if (event.getAction() == KeyEvent.ACTION_DOWN) {
174                return false;
175            }
176            switch (keyCode) {
177                case KeyEvent.KEYCODE_BACK:
178                    if (mCurrentPageIndex == 0) {
179                        return false;
180                    }
181                    moveToPreviousPage();
182                    return true;
183                case KeyEvent.KEYCODE_DPAD_LEFT:
184                    if (mIsLtr) {
185                        moveToPreviousPage();
186                    } else {
187                        moveToNextPage();
188                    }
189                    return true;
190                case KeyEvent.KEYCODE_DPAD_RIGHT:
191                    if (mIsLtr) {
192                        moveToNextPage();
193                    } else {
194                        moveToPreviousPage();
195                    }
196                    return true;
197            }
198            return false;
199        }
200    };
201
202    private void moveToPreviousPage() {
203        if (mCurrentPageIndex > 0) {
204            --mCurrentPageIndex;
205            onPageChangedInternal(mCurrentPageIndex + 1);
206        }
207    }
208    private void moveToNextPage() {
209        if (mCurrentPageIndex < getPageCount() - 1) {
210            ++mCurrentPageIndex;
211            onPageChangedInternal(mCurrentPageIndex - 1);
212        }
213    }
214
215    @Nullable
216    @Override
217    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
218            Bundle savedInstanceState) {
219        ViewGroup view = (ViewGroup) inflater.inflate(R.layout.lb_onboarding_fragment, container,
220                false);
221        mIsLtr = getResources().getConfiguration().getLayoutDirection()
222                == View.LAYOUT_DIRECTION_LTR;
223        mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
224        mPageIndicator.setOnClickListener(mOnClickListener);
225        mPageIndicator.setOnKeyListener(mOnKeyListener);
226        mStartButton = view.findViewById(R.id.button_start);
227        mStartButton.setOnClickListener(mOnClickListener);
228        mStartButton.setOnKeyListener(mOnKeyListener);
229        mLogoView = (ImageView) view.findViewById(R.id.logo);
230        mTitleView = (TextView) view.findViewById(R.id.title);
231        mDescriptionView = (TextView) view.findViewById(R.id.description);
232        if (sSlideDistance == 0) {
233            sSlideDistance = (int) (SLIDE_DISTANCE * getActivity().getResources()
234                    .getDisplayMetrics().scaledDensity);
235        }
236        if (savedInstanceState == null) {
237            mCurrentPageIndex = 0;
238            mEnterTransitionFinished = false;
239            mPageIndicator.onPageSelected(0, false);
240            container.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
241                @Override
242                public boolean onPreDraw() {
243                    container.getViewTreeObserver().removeOnPreDrawListener(this);
244                    if (!startLogoAnimation()) {
245                        startEnterAnimation();
246                    }
247                    return true;
248                }
249            });
250        } else {
251            mEnterTransitionFinished = true;
252            mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
253            initializeViews(view);
254        }
255        view.requestFocus();
256        return view;
257    }
258
259    @Override
260    public void onSaveInstanceState(Bundle outState) {
261        super.onSaveInstanceState(outState);
262        outState.putInt(KEY_CURRENT_PAGE_INDEX, mCurrentPageIndex);
263    }
264
265    /**
266     * Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
267     * splash animation will be played.
268     *
269     * @param id The resource ID of the logo image.
270     */
271    public final void setLogoResourceId(int id) {
272        mLogoResourceId = id;
273    }
274
275    /**
276     * Returns the resource ID of the splash logo image.
277     *
278     * @return The resource ID of the splash logo image.
279     */
280    public final int getLogoResourceId() {
281        return mLogoResourceId;
282    }
283
284    /**
285     * Called to have the inherited class create its own logo animation.
286     * <p>
287     * This is called only if the logo image resource ID is not set by {@link #setLogoResourceId}.
288     * If this returns {@code null}, the logo animation is skipped.
289     *
290     * @return The {@link Animator} object which runs the logo animation.
291     */
292    @Nullable
293    protected Animator onCreateLogoAnimation() {
294        return null;
295    }
296
297    private boolean startLogoAnimation() {
298        Animator animator = null;
299        if (mLogoResourceId != 0) {
300            mLogoView.setVisibility(View.VISIBLE);
301            mLogoView.setImageResource(mLogoResourceId);
302            Animator inAnimator = AnimatorInflater.loadAnimator(getActivity(),
303                    R.animator.lb_onboarding_logo_enter);
304            Animator outAnimator = AnimatorInflater.loadAnimator(getActivity(),
305                    R.animator.lb_onboarding_logo_exit);
306            outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
307            AnimatorSet logoAnimator = new AnimatorSet();
308            logoAnimator.playSequentially(inAnimator, outAnimator);
309            logoAnimator.setTarget(mLogoView);
310            animator = logoAnimator;
311        } else {
312            animator = onCreateLogoAnimation();
313        }
314        if (animator != null) {
315            animator.addListener(new AnimatorListenerAdapter() {
316                @Override
317                public void onAnimationEnd(Animator animation) {
318                    if (getActivity() != null) {
319                        startEnterAnimation();
320                    }
321                }
322            });
323            animator.start();
324            return true;
325        }
326        return false;
327    }
328
329    /**
330     * Called to have the inherited class create its enter animation. The start animation runs after
331     * logo animation ends.
332     *
333     * @return The {@link Animator} object which runs the page enter animation.
334     */
335    @Nullable
336    protected Animator onCreateEnterAnimation() {
337        return null;
338    }
339
340    private void initializeViews(View container) {
341        mLogoView.setVisibility(View.GONE);
342        // Create custom views.
343        LayoutInflater inflater = LayoutInflater.from(getActivity());
344        ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
345                R.id.background_container);
346        View background = onCreateBackgroundView(inflater, backgroundContainer);
347        if (background != null) {
348            backgroundContainer.setVisibility(View.VISIBLE);
349            backgroundContainer.addView(background);
350        }
351        ViewGroup contentContainer = (ViewGroup) container.findViewById(R.id.content_container);
352        View content = onCreateContentView(inflater, contentContainer);
353        if (content != null) {
354            contentContainer.setVisibility(View.VISIBLE);
355            contentContainer.addView(content);
356        }
357        ViewGroup foregroundContainer = (ViewGroup) container.findViewById(
358                R.id.foreground_container);
359        View foreground = onCreateForegroundView(inflater, foregroundContainer);
360        if (foreground != null) {
361            foregroundContainer.setVisibility(View.VISIBLE);
362            foregroundContainer.addView(foreground);
363        }
364        // Make views visible which were invisible while logo animation is running.
365        container.findViewById(R.id.page_container).setVisibility(View.VISIBLE);
366        container.findViewById(R.id.content_container).setVisibility(View.VISIBLE);
367        if (getPageCount() > 1) {
368            mPageIndicator.setPageCount(getPageCount());
369            mPageIndicator.onPageSelected(mCurrentPageIndex, false);
370        }
371        if (mCurrentPageIndex == getPageCount() - 1) {
372            mStartButton.setVisibility(View.VISIBLE);
373        } else {
374            mPageIndicator.setVisibility(View.VISIBLE);
375        }
376        // Header views.
377        mTitleView.setText(getPageTitle(mCurrentPageIndex));
378        mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
379    }
380
381    private void startEnterAnimation() {
382        mEnterTransitionFinished = true;
383        initializeViews(getView());
384        List<Animator> animators = new ArrayList<>();
385        Animator animator = AnimatorInflater.loadAnimator(getActivity(),
386                R.animator.lb_onboarding_page_indicator_enter);
387        animator.setTarget(getPageCount() <= 1 ? mStartButton : mPageIndicator);
388        animators.add(animator);
389        // Header title
390        View view = getActivity().findViewById(R.id.title);
391        view.setAlpha(0);
392        animator = AnimatorInflater.loadAnimator(getActivity(),
393                R.animator.lb_onboarding_title_enter);
394        animator.setStartDelay(START_DELAY_TITLE_MS);
395        animator.setTarget(view);
396        animators.add(animator);
397        // Header description
398        view = getActivity().findViewById(R.id.description);
399        view.setAlpha(0);
400        animator = AnimatorInflater.loadAnimator(getActivity(),
401                R.animator.lb_onboarding_description_enter);
402        animator.setStartDelay(START_DELAY_DESCRIPTION_MS);
403        animator.setTarget(view);
404        animators.add(animator);
405        // Customized animation by the inherited class.
406        Animator customAnimator = onCreateEnterAnimation();
407        if (customAnimator != null) {
408            animators.add(customAnimator);
409        }
410        mAnimator = new AnimatorSet();
411        mAnimator.playTogether(animators);
412        mAnimator.start();
413        // Search focus and give the focus to the appropriate child which has become visible.
414        getView().requestFocus();
415    }
416
417    /**
418     * Returns the page count.
419     *
420     * @return The page count.
421     */
422    abstract protected int getPageCount();
423
424    /**
425     * Returns the title of the given page.
426     *
427     * @param pageIndex The page index.
428     *
429     * @return The title of the page.
430     */
431    abstract protected String getPageTitle(int pageIndex);
432
433    /**
434     * Returns the description of the given page.
435     *
436     * @param pageIndex The page index.
437     *
438     * @return The description of the page.
439     */
440    abstract protected String getPageDescription(int pageIndex);
441
442    /**
443     * Returns the index of the current page.
444     *
445     * @return The index of the current page.
446     */
447    protected final int getCurrentPageIndex() {
448        return mCurrentPageIndex;
449    }
450
451    /**
452     * Called to have the inherited class create background view. This is optional and the fragment
453     * which doesn't have the background view can return {@code null}. This is called inside
454     * {@link #onCreateView}.
455     *
456     * @param inflater The LayoutInflater object that can be used to inflate the views,
457     * @param container The parent view that the additional views are attached to.The fragment
458     *        should not add the view by itself.
459     *
460     * @return The background view for the onboarding screen, or {@code null}.
461     */
462    @Nullable
463    abstract protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container);
464
465    /**
466     * Called to have the inherited class create content view. This is optional and the fragment
467     * which doesn't have the content view can return {@code null}. This is called inside
468     * {@link #onCreateView}.
469     *
470     * <p>The content view would be located at the center of the screen.
471     *
472     * @param inflater The LayoutInflater object that can be used to inflate the views,
473     * @param container The parent view that the additional views are attached to.The fragment
474     *        should not add the view by itself.
475     *
476     * @return The content view for the onboarding screen, or {@code null}.
477     */
478    @Nullable
479    abstract protected View onCreateContentView(LayoutInflater inflater, ViewGroup container);
480
481    /**
482     * Called to have the inherited class create foreground view. This is optional and the fragment
483     * which doesn't need the foreground view can return {@code null}. This is called inside
484     * {@link #onCreateView}.
485     *
486     * <p>This foreground view would have the highest z-order.
487     *
488     * @param inflater The LayoutInflater object that can be used to inflate the views,
489     * @param container The parent view that the additional views are attached to.The fragment
490     *        should not add the view by itself.
491     *
492     * @return The foreground view for the onboarding screen, or {@code null}.
493     */
494    @Nullable
495    abstract protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container);
496
497    /**
498     * Called when the onboarding flow finishes.
499     */
500    protected void onFinishFragment() { }
501
502    /**
503     * Called when the page changes.
504     */
505    private void onPageChangedInternal(int previousPage) {
506        if (mAnimator != null) {
507            mAnimator.end();
508        }
509        mPageIndicator.onPageSelected(mCurrentPageIndex, true);
510
511        List<Animator> animators = new ArrayList<>();
512        // Header animation
513        Animator fadeAnimator = null;
514        if (previousPage < getCurrentPageIndex()) {
515            // sliding to left
516            animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
517            animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
518                    DESCRIPTION_START_DELAY_MS));
519            animators.add(createAnimator(mTitleView, true, Gravity.END,
520                    HEADER_APPEAR_DELAY_MS));
521            animators.add(createAnimator(mDescriptionView, true, Gravity.END,
522                    HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
523        } else {
524            // sliding to right
525            animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
526            animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
527                    DESCRIPTION_START_DELAY_MS));
528            animators.add(createAnimator(mTitleView, true, Gravity.START,
529                    HEADER_APPEAR_DELAY_MS));
530            animators.add(createAnimator(mDescriptionView, true, Gravity.START,
531                    HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
532        }
533        final int currentPageIndex = getCurrentPageIndex();
534        fadeAnimator.addListener(new AnimatorListenerAdapter() {
535            @Override
536            public void onAnimationEnd(Animator animation) {
537                mTitleView.setText(getPageTitle(currentPageIndex));
538                mDescriptionView.setText(getPageDescription(currentPageIndex));
539            }
540        });
541
542        // Animator for switching between page indicator and button.
543        if (getCurrentPageIndex() == getPageCount() - 1) {
544            mStartButton.setVisibility(View.VISIBLE);
545            Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
546                    R.animator.lb_onboarding_page_indicator_fade_out);
547            navigatorFadeOutAnimator.setTarget(mPageIndicator);
548            Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
549                    R.animator.lb_onboarding_start_button_fade_in);
550            buttonFadeInAnimator.setTarget(mStartButton);
551            animators.add(navigatorFadeOutAnimator);
552            navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
553                @Override
554                public void onAnimationEnd(Animator animation) {
555                    mPageIndicator.setVisibility(View.GONE);
556                }
557            });
558            animators.add(buttonFadeInAnimator);
559        } else if (previousPage == getPageCount() - 1) {
560            mPageIndicator.setVisibility(View.VISIBLE);
561            Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
562                    R.animator.lb_onboarding_page_indicator_fade_in);
563            navigatorFadeInAnimator.setTarget(mPageIndicator);
564            Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
565                    R.animator.lb_onboarding_start_button_fade_out);
566            buttonFadeOutAnimator.setTarget(mStartButton);
567            buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
568                @Override
569                public void onAnimationEnd(Animator animation) {
570                    mStartButton.setVisibility(View.GONE);
571                }
572            });
573            mAnimator = new AnimatorSet();
574            mAnimator.playTogether(navigatorFadeInAnimator, buttonFadeOutAnimator);
575            mAnimator.start();
576        }
577        mAnimator = new AnimatorSet();
578        mAnimator.playTogether(animators);
579        mAnimator.start();
580        onPageChanged(mCurrentPageIndex, previousPage);
581    }
582
583    /**
584     * Called when the page has been changed.
585     *
586     * @param newPage The new page.
587     * @param previousPage The previous page.
588     */
589    protected void onPageChanged(int newPage, int previousPage) { }
590
591    private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
592            long startDelay) {
593        boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
594        boolean slideRight = (isLtr && slideDirection == Gravity.END)
595                || (!isLtr && slideDirection == Gravity.START)
596                || slideDirection == Gravity.RIGHT;
597        Animator fadeAnimator;
598        Animator slideAnimator;
599        if (fadeIn) {
600            fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
601            slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
602                    slideRight ? sSlideDistance : -sSlideDistance, 0);
603            fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
604            slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
605        } else {
606            fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
607            slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
608                    slideRight ? sSlideDistance : -sSlideDistance);
609            fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
610            slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
611        }
612        fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
613        fadeAnimator.setTarget(view);
614        slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
615        slideAnimator.setTarget(view);
616        AnimatorSet animator = new AnimatorSet();
617        animator.playTogether(fadeAnimator, slideAnimator);
618        if (startDelay > 0) {
619            animator.setStartDelay(startDelay);
620        }
621        return animator;
622    }
623}
624