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