java.lang.Object
↳Fragment
↳androidx.leanback.app.OnboardingFragment
Gradle dependencies
compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'
- groupId: androidx.leanback
- artifactId: leanback
- version: 1.2.0-alpha04
Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.leanback:leanback com.android.support:leanback-v17
Androidx class mapping:
androidx.leanback.app.OnboardingFragment android.support.v17.leanback.app.OnboardingFragment
Overview
An OnboardingFragment provides a common and simple way to build onboarding screen for
applications.
Building the screen
The view structure of onboarding screen is composed of the common parts and custom parts. The
common parts are composed of icon, title, description and page navigator and the custom parts
are composed of background, contents and foreground.
To build the screen views, the inherited class should override:
Each of these methods can return null if the application doesn't want to provide it.
Page information
The onboarding screen may have several pages which explain the functionality of the application.
The inherited class should provide the page information by overriding the methods:
Note that the information is used in OnboardingFragment.onCreateView(LayoutInflater, ViewGroup, Bundle), so should be initialized before
calling super.onCreateView.
Animation
Onboarding screen has three kinds of animations:
Logo Splash Animation
When onboarding screen appears, the logo splash animation is played by default. The animation
fades in the logo image, pauses in a few seconds and fades it out.
In most cases, the logo animation needs to be customized because the logo images of applications
are different from each other, or some applications may want to show their own animations.
The logo animation can be customized in two ways:
If the inherited class provides neither the logo image nor the animation, the logo animation will
be omitted.
Page enter animation
After logo animation finishes, page enter animation starts, which causes the header section -
title and description views to fade and slide in. Users can override the default
fade + slide animation by overriding
OnboardingFragment.onCreateTitleAnimator() &
OnboardingFragment.onCreateDescriptionAnimator(). By default we don't animate the custom views but users
can provide animation by overriding
OnboardingFragment.onCreateEnterAnimation().
Page change animation
When the page changes, the default animations of the title and description are played. The
inherited class can override
OnboardingFragment.onPageChanged(int, int) to start the custom animations.
Finishing the screen
If the user finishes the onboarding screen after navigating all the pages,
OnboardingFragment.onFinishFragment() is called. The inherited class can override this method to show another
fragment or activity, or just remove this fragment.
Theming
OnboardingFragment must have access to an appropriate theme. Specifically, the fragment must
receive , or a theme whose parent is set to that theme.
Themes can be provided in one of three ways:
- The simplest way is to set the theme for the host Activity to the Onboarding theme or a theme
that derives from it.
- If the Activity already has a theme and setting its parent theme is inconvenient, the
existing Activity theme can have an entry added for the attribute
. If present, this theme will be used
by OnboardingFragment as an overlay to the Activity's theme.
- Finally, custom subclasses of OnboardingFragment may provide a theme through the
OnboardingFragment.onProvideTheme() method. This can be useful if a subclass is used across multiple
Activities.
If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
need to set the onboardingTheme attribute; if set, it will be ignored.)
Summary
Methods |
---|
public final int | getArrowBackgroundColor()
Returns the background color of the arrow if it's set through
OnboardingFragment.setArrowBackgroundColor(int). |
public final int | getArrowColor()
Returns the color of the arrow if it's set through
OnboardingFragment.setArrowColor(int). |
protected final int | getCurrentPageIndex()
Returns the index of the current page. |
public final int | getDescriptionViewTextColor()
Returns the text color of DescriptionView if it's set through
OnboardingFragment.setDescriptionViewTextColor(int). |
public final int | getDotBackgroundColor()
Returns the background color of the dot if it's set through
OnboardingFragment.setDotBackgroundColor(int). |
public final int | getIconResourceId()
Returns the resource id of the main icon. |
public final int | getLogoResourceId()
Returns the resource ID of the splash logo image. |
protected abstract int | getPageCount()
Returns the page count. |
protected abstract java.lang.CharSequence | getPageDescription(int pageIndex)
Returns the description of the given page. |
protected abstract java.lang.CharSequence | getPageTitle(int pageIndex)
Returns the title of the given page. |
public final java.lang.CharSequence | getStartButtonText()
Returns the start button text if it's set through
OnboardingFragment.setStartButtonText(CharSequence)}. |
public final int | getTitleViewTextColor()
Returns the text color of TitleView if it's set through
OnboardingFragment.setTitleViewTextColor(int). |
protected final boolean | isLogoAnimationFinished()
Returns whether the logo enter animation is finished. |
protected void | moveToNextPage()
Navigates to the next page. |
protected void | moveToPreviousPage()
Navigates to the previous page. |
protected abstract View | onCreateBackgroundView(LayoutInflater inflater, ViewGroup container)
Called to have the inherited class create background view. |
protected abstract View | onCreateContentView(LayoutInflater inflater, ViewGroup container)
Called to have the inherited class create content view. |
protected Animator | onCreateDescriptionAnimator()
Provides the entry animation for description view. |
protected Animator | onCreateEnterAnimation()
Called to have the inherited class create its enter animation. |
protected abstract View | onCreateForegroundView(LayoutInflater inflater, ViewGroup container)
Called to have the inherited class create foreground view. |
protected Animator | onCreateLogoAnimation()
Called to have the inherited class create its own logo animation. |
protected Animator | onCreateTitleAnimator()
Provides the entry animation for title view. |
public View | onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
|
protected void | onFinishFragment()
Called when the onboarding flow finishes. |
protected void | onLogoAnimationFinished()
Called immediately after the logo animation is complete or no logo animation is specified. |
protected void | onPageChanged(int newPage, int previousPage)
Called when the page has been changed. |
public int | onProvideTheme()
Returns the theme used for styling the fragment. |
public void | onSaveInstanceState(Bundle outState)
|
public void | onViewCreated(View view, Bundle savedInstanceState)
|
public void | setArrowBackgroundColor(int color)
Sets the background color of the arrow. |
public void | setArrowColor(int color)
Sets the color of the arrow. |
public void | setDescriptionViewTextColor(int color)
Sets the text color for DescriptionView. |
public void | setDotBackgroundColor(int color)
Sets the background color of the dots. |
public final void | setIconResouceId(int resourceId)
Sets the resource id for the main icon. |
public final void | setLogoResourceId(int id)
Sets the resource ID of the splash logo image. |
public void | setStartButtonText(java.lang.CharSequence text)
Sets the text on the start button text. |
public void | setTitleViewTextColor(int color)
Sets the text color for TitleView. |
protected final void | startEnterAnimation(boolean force)
Called to start entrance transition. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructors
public
OnboardingFragment()
Methods
protected void
moveToPreviousPage()
Navigates to the previous page.
protected void
moveToNextPage()
Navigates to the next page.
public View
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
public void
onViewCreated(View view, Bundle savedInstanceState)
public void
onSaveInstanceState(Bundle outState)
public void
setTitleViewTextColor(int color)
Sets the text color for TitleView. If not set, the default textColor set in style
referenced by attr will be used.
Parameters:
color: the color to use as the text color for TitleView
public final int
getTitleViewTextColor()
Returns the text color of TitleView if it's set through
OnboardingFragment.setTitleViewTextColor(int). If no color was set, transparent is returned.
public void
setDescriptionViewTextColor(int color)
Sets the text color for DescriptionView. If not set, the default textColor set in style
referenced by attr will be used.
Parameters:
color: the color to use as the text color for DescriptionView
public final int
getDescriptionViewTextColor()
Returns the text color of DescriptionView if it's set through
OnboardingFragment.setDescriptionViewTextColor(int). If no color was set, transparent is returned.
public void
setDotBackgroundColor(int color)
Sets the background color of the dots. If not set, the default color from attr
in the theme will be used.
Parameters:
color: the color to use for dot backgrounds
public final int
getDotBackgroundColor()
Returns the background color of the dot if it's set through
OnboardingFragment.setDotBackgroundColor(int). If no color was set, transparent is returned.
public void
setArrowColor(int color)
Sets the color of the arrow. This color will supersede the color set in the theme attribute
if provided. If none of these two are set, the
arrow will have its original bitmap color.
Parameters:
color: the color to use for arrow background
public final int
getArrowColor()
Returns the color of the arrow if it's set through
OnboardingFragment.setArrowColor(int). If no color was set, transparent is returned.
public void
setArrowBackgroundColor(int color)
Sets the background color of the arrow. If not set, the default color from attr
in the theme will be used.
Parameters:
color: the color to use for arrow background
public final int
getArrowBackgroundColor()
Returns the background color of the arrow if it's set through
OnboardingFragment.setArrowBackgroundColor(int). If no color was set, transparent is returned.
public final java.lang.CharSequence
getStartButtonText()
Returns the start button text if it's set through
OnboardingFragment.setStartButtonText(CharSequence)}. If no string was set, null is returned.
public void
setStartButtonText(java.lang.CharSequence text)
Sets the text on the start button text. If not set, the default text set in
will be used.
Parameters:
text: the start button text
public int
onProvideTheme()
Returns the theme used for styling the fragment. The default returns -1, indicating that the
host Activity's theme should be used.
Returns:
The theme resource ID of the theme to use in this fragment, or -1 to use the host
Activity's theme.
public final void
setLogoResourceId(int id)
Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
splash animation will be played.
Parameters:
id: The resource ID of the logo image.
public final int
getLogoResourceId()
Returns the resource ID of the splash logo image.
Returns:
The resource ID of the splash logo image.
protected Animator
onCreateLogoAnimation()
Called to have the inherited class create its own logo animation.
This is called only if the logo image resource ID is not set by OnboardingFragment.setLogoResourceId(int).
If this returns null, the logo animation is skipped.
Returns:
The Animator
object which runs the logo animation.
protected Animator
onCreateEnterAnimation()
Called to have the inherited class create its enter animation. The start animation runs after
logo animation ends.
Returns:
The Animator
object which runs the page enter animation.
protected void
onLogoAnimationFinished()
Called immediately after the logo animation is complete or no logo animation is specified.
This method can also be called when the activity is recreated, i.e. when no logo animation
are performed.
By default, this method will hide the logo view and start the entrance animation for this
fragment.
Overriding subclasses can provide their own data loading logic as to when the entrance
animation should be executed.
protected final void
startEnterAnimation(boolean force)
Called to start entrance transition. This can be called by subclasses when the logo animation
and data loading is complete. If force flag is set to false, it will only start the animation
if it's not already done yet. Otherwise, it will always start the enter animation. In both
cases, the logo view will hide and the rest of fragment views become visible after this call.
Parameters:
force: true if enter animation has to be performed regardless of whether it's
been done in the past, false otherwise
protected Animator
onCreateDescriptionAnimator()
Provides the entry animation for description view. This allows users to override the
default fade and slide animation. Returning null will disable the animation.
protected Animator
onCreateTitleAnimator()
Provides the entry animation for title view. This allows users to override the
default fade and slide animation. Returning null will disable the animation.
protected final boolean
isLogoAnimationFinished()
Returns whether the logo enter animation is finished.
Returns:
true if the logo enter transition is finished, false otherwise
protected abstract int
getPageCount()
Returns the page count.
Returns:
The page count.
protected abstract java.lang.CharSequence
getPageTitle(int pageIndex)
Returns the title of the given page.
Parameters:
pageIndex: The page index.
Returns:
The title of the page.
protected abstract java.lang.CharSequence
getPageDescription(int pageIndex)
Returns the description of the given page.
Parameters:
pageIndex: The page index.
Returns:
The description of the page.
protected final int
getCurrentPageIndex()
Returns the index of the current page.
Returns:
The index of the current page.
protected abstract View
onCreateBackgroundView(LayoutInflater inflater, ViewGroup container)
Called to have the inherited class create background view. This is optional and the fragment
which doesn't have the background view can return null. This is called inside
OnboardingFragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
Parameters:
inflater: The LayoutInflater object that can be used to inflate the views,
container: The parent view that the additional views are attached to.The fragment
should not add the view by itself.
Returns:
The background view for the onboarding screen, or null.
protected abstract View
onCreateContentView(LayoutInflater inflater, ViewGroup container)
Called to have the inherited class create content view. This is optional and the fragment
which doesn't have the content view can return null. This is called inside
OnboardingFragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
The content view would be located at the center of the screen.
Parameters:
inflater: The LayoutInflater object that can be used to inflate the views,
container: The parent view that the additional views are attached to.The fragment
should not add the view by itself.
Returns:
The content view for the onboarding screen, or null.
protected abstract View
onCreateForegroundView(LayoutInflater inflater, ViewGroup container)
Called to have the inherited class create foreground view. This is optional and the fragment
which doesn't need the foreground view can return null. This is called inside
OnboardingFragment.onCreateView(LayoutInflater, ViewGroup, Bundle).
This foreground view would have the highest z-order.
Parameters:
inflater: The LayoutInflater object that can be used to inflate the views,
container: The parent view that the additional views are attached to.The fragment
should not add the view by itself.
Returns:
The foreground view for the onboarding screen, or null.
protected void
onFinishFragment()
Called when the onboarding flow finishes.
protected void
onPageChanged(int newPage, int previousPage)
Called when the page has been changed.
Parameters:
newPage: The new page.
previousPage: The previous page.
public final void
setIconResouceId(int resourceId)
Sets the resource id for the main icon.
public final int
getIconResourceId()
Returns the resource id of the main icon.
Source
// CHECKSTYLE:OFF Generated code
/* This file is auto-generated from OnboardingSupportFragment.java. DO NOT MODIFY. */
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.leanback.app;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.app.Fragment;
import androidx.leanback.R;
import androidx.leanback.widget.PagingIndicator;
import java.util.ArrayList;
import java.util.List;
/**
* An OnboardingFragment provides a common and simple way to build onboarding screen for
* applications.
* <p>
* <h3>Building the screen</h3>
* The view structure of onboarding screen is composed of the common parts and custom parts. The
* common parts are composed of icon, title, description and page navigator and the custom parts
* are composed of background, contents and foreground.
* <p>
* To build the screen views, the inherited class should override:
* <ul>
* <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
* size as the screen and the lowest z-order.</li>
* <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
* the content area at the center of the screen.</li>
* <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
* size as the screen and the highest z-order</li>
* </ul>
* <p>
* Each of these methods can return {@code null} if the application doesn't want to provide it.
* <p>
* <h3>Page information</h3>
* The onboarding screen may have several pages which explain the functionality of the application.
* The inherited class should provide the page information by overriding the methods:
* <p>
* <ul>
* <li>{@link #getPageCount} to provide the number of pages.</li>
* <li>{@link #getPageTitle} to provide the title of the page.</li>
* <li>{@link #getPageDescription} to provide the description of the page.</li>
* </ul>
* <p>
* Note that the information is used in {@link #onCreateView}, so should be initialized before
* calling {@code super.onCreateView}.
* <p>
* <h3>Animation</h3>
* Onboarding screen has three kinds of animations:
* <p>
* <h4>Logo Splash Animation</a></h4>
* When onboarding screen appears, the logo splash animation is played by default. The animation
* fades in the logo image, pauses in a few seconds and fades it out.
* <p>
* In most cases, the logo animation needs to be customized because the logo images of applications
* are different from each other, or some applications may want to show their own animations.
* <p>
* The logo animation can be customized in two ways:
* <ul>
* <li>The simplest way is to provide the logo image by calling {@link #setLogoResourceId} to show
* the default logo animation. This method should be called in {@link Fragment#onCreateView}.</li>
* <li>If the logo animation is complex, then override {@link #onCreateLogoAnimation} and return the
* {@link Animator} object to run.</li>
* </ul>
* <p>
* If the inherited class provides neither the logo image nor the animation, the logo animation will
* be omitted.
* <h4>Page enter animation</h4>
* After logo animation finishes, page enter animation starts, which causes the header section -
* title and description views to fade and slide in. Users can override the default
* fade + slide animation by overriding {@link #onCreateTitleAnimator()} &
* {@link #onCreateDescriptionAnimator()}. By default we don't animate the custom views but users
* can provide animation by overriding {@link #onCreateEnterAnimation}.
*
* <h4>Page change animation</h4>
* When the page changes, the default animations of the title and description are played. The
* inherited class can override {@link #onPageChanged} to start the custom animations.
* <p>
* <h3>Finishing the screen</h3>
* <p>
* If the user finishes the onboarding screen after navigating all the pages,
* {@link #onFinishFragment} is called. The inherited class can override this method to show another
* fragment or activity, or just remove this fragment.
* <p>
* <h3>Theming</h3>
* <p>
* OnboardingFragment must have access to an appropriate theme. Specifically, the fragment must
* receive {@link R.style#Theme_Leanback_Onboarding}, or a theme whose parent is set to that theme.
* Themes can be provided in one of three ways:
* <ul>
* <li>The simplest way is to set the theme for the host Activity to the Onboarding theme or a theme
* that derives from it.</li>
* <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
* existing Activity theme can have an entry added for the attribute
* {@link R.styleable#LeanbackOnboardingTheme_onboardingTheme}. If present, this theme will be used
* by OnboardingFragment as an overlay to the Activity's theme.</li>
* <li>Finally, custom subclasses of OnboardingFragment may provide a theme through the
* {@link #onProvideTheme} method. This can be useful if a subclass is used across multiple
* Activities.</li>
* </ul>
* <p>
* If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
* the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
* need to set the onboardingTheme attribute; if set, it will be ignored.)
*
* {@link R.attr#onboardingTheme}
* {@link R.attr#onboardingHeaderStyle}
* {@link R.attr#onboardingTitleStyle}
* {@link R.attr#onboardingDescriptionStyle}
* {@link R.attr#onboardingNavigatorContainerStyle}
* {@link R.attr#onboardingPageIndicatorStyle}
* {@link R.attr#onboardingStartButtonStyle}
* {@link R.attr#onboardingLogoStyle}
* @deprecated use {@link OnboardingSupportFragment}
*/
@Deprecated
abstract public class OnboardingFragment extends Fragment {
private static final String TAG = "OnboardingF";
private static final boolean DEBUG = false;
private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
private static final long HEADER_ANIMATION_DURATION_MS = 417;
private static final long DESCRIPTION_START_DELAY_MS = 33;
private static final long HEADER_APPEAR_DELAY_MS = 500;
private static final int SLIDE_DISTANCE = 60;
private static int sSlideDistance;
private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR =
new AccelerateInterpolator();
// Keys used to save and restore the states.
private static final String KEY_CURRENT_PAGE_INDEX = "leanback.onboarding.current_page_index";
private static final String KEY_LOGO_ANIMATION_FINISHED =
"leanback.onboarding.logo_animation_finished";
private static final String KEY_ENTER_ANIMATION_FINISHED =
"leanback.onboarding.enter_animation_finished";
private ContextThemeWrapper mThemeWrapper;
PagingIndicator mPageIndicator;
View mStartButton;
private ImageView mLogoView;
// Optional icon that can be displayed on top of the header section.
private ImageView mMainIconView;
private int mIconResourceId;
TextView mTitleView;
TextView mDescriptionView;
boolean mIsLtr;
// No need to save/restore the logo resource ID, because the logo animation will not appear when
// the fragment is restored.
private int mLogoResourceId;
boolean mLogoAnimationFinished;
boolean mEnterAnimationFinished;
int mCurrentPageIndex;
@ColorInt
private int mTitleViewTextColor = Color.TRANSPARENT;
private boolean mTitleViewTextColorSet;
@ColorInt
private int mDescriptionViewTextColor = Color.TRANSPARENT;
private boolean mDescriptionViewTextColorSet;
@ColorInt
private int mDotBackgroundColor = Color.TRANSPARENT;
private boolean mDotBackgroundColorSet;
@ColorInt
private int mArrowColor = Color.TRANSPARENT;
private boolean mArrowColorSet;
@ColorInt
private int mArrowBackgroundColor = Color.TRANSPARENT;
private boolean mArrowBackgroundColorSet;
private CharSequence mStartButtonText;
private boolean mStartButtonTextSet;
private AnimatorSet mAnimator;
private final OnClickListener mOnClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
if (!mLogoAnimationFinished) {
// Do not change page until the enter transition finishes.
return;
}
if (mCurrentPageIndex == getPageCount() - 1) {
onFinishFragment();
} else {
moveToNextPage();
}
}
};
private final OnKeyListener mOnKeyListener = new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (!mLogoAnimationFinished) {
// Ignore key event until the enter transition finishes.
return keyCode != KeyEvent.KEYCODE_BACK;
}
if (event.getAction() == KeyEvent.ACTION_DOWN) {
return false;
}
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
if (mCurrentPageIndex == 0) {
return false;
}
moveToPreviousPage();
return true;
case KeyEvent.KEYCODE_DPAD_LEFT:
if (mIsLtr) {
moveToPreviousPage();
} else {
moveToNextPage();
}
return true;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (mIsLtr) {
moveToNextPage();
} else {
moveToPreviousPage();
}
return true;
}
return false;
}
};
/**
* Navigates to the previous page.
*/
protected void moveToPreviousPage() {
if (!mLogoAnimationFinished) {
// Ignore if the logo enter transition is in progress.
return;
}
if (mCurrentPageIndex > 0) {
--mCurrentPageIndex;
onPageChangedInternal(mCurrentPageIndex + 1);
}
}
/**
* Navigates to the next page.
*/
protected void moveToNextPage() {
if (!mLogoAnimationFinished) {
// Ignore if the logo enter transition is in progress.
return;
}
if (mCurrentPageIndex < getPageCount() - 1) {
++mCurrentPageIndex;
onPageChangedInternal(mCurrentPageIndex - 1);
}
}
@Override
@Nullable
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
resolveTheme();
LayoutInflater localInflater = getThemeInflater(inflater);
final ViewGroup view = (ViewGroup) localInflater.inflate(R.layout.lb_onboarding_fragment,
container, false);
mIsLtr = getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_LTR;
mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
mPageIndicator.setOnClickListener(mOnClickListener);
mPageIndicator.setOnKeyListener(mOnKeyListener);
mStartButton = view.findViewById(R.id.button_start);
mStartButton.setOnClickListener(mOnClickListener);
mStartButton.setOnKeyListener(mOnKeyListener);
mMainIconView = (ImageView) view.findViewById(R.id.main_icon);
mLogoView = (ImageView) view.findViewById(R.id.logo);
mTitleView = (TextView) view.findViewById(R.id.title);
mDescriptionView = (TextView) view.findViewById(R.id.description);
if (mTitleViewTextColorSet) {
mTitleView.setTextColor(mTitleViewTextColor);
}
if (mDescriptionViewTextColorSet) {
mDescriptionView.setTextColor(mDescriptionViewTextColor);
}
if (mDotBackgroundColorSet) {
mPageIndicator.setDotBackgroundColor(mDotBackgroundColor);
}
if (mArrowColorSet) {
mPageIndicator.setArrowColor(mArrowColor);
}
if (mArrowBackgroundColorSet) {
mPageIndicator.setDotBackgroundColor(mArrowBackgroundColor);
}
if (mStartButtonTextSet) {
((Button) mStartButton).setText(mStartButtonText);
}
final Context context = FragmentUtil.getContext(OnboardingFragment.this);
if (sSlideDistance == 0) {
sSlideDistance = (int) (SLIDE_DISTANCE * context.getResources()
.getDisplayMetrics().scaledDensity);
}
view.requestFocus();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null) {
mCurrentPageIndex = 0;
mLogoAnimationFinished = false;
mEnterAnimationFinished = false;
mPageIndicator.onPageSelected(0, false);
view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getView().getViewTreeObserver().removeOnPreDrawListener(this);
if (!startLogoAnimation()) {
mLogoAnimationFinished = true;
onLogoAnimationFinished();
}
return true;
}
});
} else {
mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
mLogoAnimationFinished = savedInstanceState.getBoolean(KEY_LOGO_ANIMATION_FINISHED);
mEnterAnimationFinished = savedInstanceState.getBoolean(KEY_ENTER_ANIMATION_FINISHED);
if (!mLogoAnimationFinished) {
// logo animation wasn't started or was interrupted when the activity was destroyed;
// restart it againl
if (!startLogoAnimation()) {
mLogoAnimationFinished = true;
onLogoAnimationFinished();
}
} else {
onLogoAnimationFinished();
}
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_CURRENT_PAGE_INDEX, mCurrentPageIndex);
outState.putBoolean(KEY_LOGO_ANIMATION_FINISHED, mLogoAnimationFinished);
outState.putBoolean(KEY_ENTER_ANIMATION_FINISHED, mEnterAnimationFinished);
}
/**
* Sets the text color for TitleView. If not set, the default textColor set in style
* referenced by attr {@link R.attr#onboardingTitleStyle} will be used.
* @param color the color to use as the text color for TitleView
*/
public void setTitleViewTextColor(@ColorInt int color) {
mTitleViewTextColor = color;
mTitleViewTextColorSet = true;
if (mTitleView != null) {
mTitleView.setTextColor(color);
}
}
/**
* Returns the text color of TitleView if it's set through
* {@link #setTitleViewTextColor(int)}. If no color was set, transparent is returned.
*/
@ColorInt
public final int getTitleViewTextColor() {
return mTitleViewTextColor;
}
/**
* Sets the text color for DescriptionView. If not set, the default textColor set in style
* referenced by attr {@link R.attr#onboardingDescriptionStyle} will be used.
* @param color the color to use as the text color for DescriptionView
*/
public void setDescriptionViewTextColor(@ColorInt int color) {
mDescriptionViewTextColor = color;
mDescriptionViewTextColorSet = true;
if (mDescriptionView != null) {
mDescriptionView.setTextColor(color);
}
}
/**
* Returns the text color of DescriptionView if it's set through
* {@link #setDescriptionViewTextColor(int)}. If no color was set, transparent is returned.
*/
@ColorInt
public final int getDescriptionViewTextColor() {
return mDescriptionViewTextColor;
}
/**
* Sets the background color of the dots. If not set, the default color from attr
* {@link R.styleable#PagingIndicator_dotBgColor} in the theme will be used.
* @param color the color to use for dot backgrounds
*/
public void setDotBackgroundColor(@ColorInt int color) {
mDotBackgroundColor = color;
mDotBackgroundColorSet = true;
if (mPageIndicator != null) {
mPageIndicator.setDotBackgroundColor(color);
}
}
/**
* Returns the background color of the dot if it's set through
* {@link #setDotBackgroundColor(int)}. If no color was set, transparent is returned.
*/
@ColorInt
public final int getDotBackgroundColor() {
return mDotBackgroundColor;
}
/**
* Sets the color of the arrow. This color will supersede the color set in the theme attribute
* {@link R.styleable#PagingIndicator_arrowColor} if provided. If none of these two are set, the
* arrow will have its original bitmap color.
*
* @param color the color to use for arrow background
*/
public void setArrowColor(@ColorInt int color) {
mArrowColor = color;
mArrowColorSet = true;
if (mPageIndicator != null) {
mPageIndicator.setArrowColor(color);
}
}
/**
* Returns the color of the arrow if it's set through
* {@link #setArrowColor(int)}. If no color was set, transparent is returned.
*/
@ColorInt
public final int getArrowColor() {
return mArrowColor;
}
/**
* Sets the background color of the arrow. If not set, the default color from attr
* {@link R.styleable#PagingIndicator_arrowBgColor} in the theme will be used.
* @param color the color to use for arrow background
*/
public void setArrowBackgroundColor(@ColorInt int color) {
mArrowBackgroundColor = color;
mArrowBackgroundColorSet = true;
if (mPageIndicator != null) {
mPageIndicator.setArrowBackgroundColor(color);
}
}
/**
* Returns the background color of the arrow if it's set through
* {@link #setArrowBackgroundColor(int)}. If no color was set, transparent is returned.
*/
@ColorInt
public final int getArrowBackgroundColor() {
return mArrowBackgroundColor;
}
/**
* Returns the start button text if it's set through
* {@link #setStartButtonText(CharSequence)}}. If no string was set, null is returned.
*/
@Nullable
public final CharSequence getStartButtonText() {
return mStartButtonText;
}
/**
* Sets the text on the start button text. If not set, the default text set in
* {@link R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle} will be used.
*
* @param text the start button text
*/
public void setStartButtonText(@Nullable CharSequence text) {
mStartButtonText = text;
mStartButtonTextSet = true;
if (mStartButton != null) {
((Button) mStartButton).setText(mStartButtonText);
}
}
/**
* Returns the theme used for styling the fragment. The default returns -1, indicating that the
* host Activity's theme should be used.
*
* @return The theme resource ID of the theme to use in this fragment, or -1 to use the host
* Activity's theme.
*/
public int onProvideTheme() {
return -1;
}
private void resolveTheme() {
final Context context = FragmentUtil.getContext(OnboardingFragment.this);
int theme = onProvideTheme();
if (theme == -1) {
// Look up the onboardingTheme in the activity's currently specified theme. If it
// exists, wrap the theme with its value.
int resId = R.attr.onboardingTheme;
TypedValue typedValue = new TypedValue();
boolean found = context.getTheme().resolveAttribute(resId, typedValue, true);
if (DEBUG) Log.v(TAG, "Found onboarding theme reference? " + found);
if (found) {
mThemeWrapper = new ContextThemeWrapper(context, typedValue.resourceId);
}
} else {
mThemeWrapper = new ContextThemeWrapper(context, theme);
}
}
private LayoutInflater getThemeInflater(LayoutInflater inflater) {
return mThemeWrapper == null ? inflater : inflater.cloneInContext(mThemeWrapper);
}
/**
* Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
* splash animation will be played.
*
* @param id The resource ID of the logo image.
*/
public final void setLogoResourceId(int id) {
mLogoResourceId = id;
}
/**
* Returns the resource ID of the splash logo image.
*
* @return The resource ID of the splash logo image.
*/
public final int getLogoResourceId() {
return mLogoResourceId;
}
/**
* Called to have the inherited class create its own logo animation.
* <p>
* This is called only if the logo image resource ID is not set by {@link #setLogoResourceId}.
* If this returns {@code null}, the logo animation is skipped.
*
* @return The {@link Animator} object which runs the logo animation.
*/
@Nullable
protected Animator onCreateLogoAnimation() {
return null;
}
boolean startLogoAnimation() {
final Context context = FragmentUtil.getContext(OnboardingFragment.this);
if (context == null) {
return false;
}
Animator animator = null;
if (mLogoResourceId != 0) {
mLogoView.setVisibility(View.VISIBLE);
mLogoView.setImageResource(mLogoResourceId);
Animator inAnimator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_logo_enter);
Animator outAnimator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_logo_exit);
outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
AnimatorSet logoAnimator = new AnimatorSet();
logoAnimator.playSequentially(inAnimator, outAnimator);
logoAnimator.setTarget(mLogoView);
animator = logoAnimator;
} else {
animator = onCreateLogoAnimation();
}
if (animator != null) {
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (context != null) {
mLogoAnimationFinished = true;
onLogoAnimationFinished();
}
}
});
animator.start();
return true;
}
return false;
}
/**
* Called to have the inherited class create its enter animation. The start animation runs after
* logo animation ends.
*
* @return The {@link Animator} object which runs the page enter animation.
*/
@Nullable
protected Animator onCreateEnterAnimation() {
return null;
}
/**
* Hides the logo view and makes other fragment views visible. Also initializes the texts for
* Title and Description views.
*/
void hideLogoView() {
mLogoView.setVisibility(View.GONE);
if (mIconResourceId != 0) {
mMainIconView.setImageResource(mIconResourceId);
mMainIconView.setVisibility(View.VISIBLE);
}
View container = getView();
// Create custom views.
LayoutInflater inflater = getThemeInflater(LayoutInflater.from(
FragmentUtil.getContext(OnboardingFragment.this)));
ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
R.id.background_container);
View background = onCreateBackgroundView(inflater, backgroundContainer);
if (background != null) {
backgroundContainer.setVisibility(View.VISIBLE);
backgroundContainer.addView(background);
}
ViewGroup contentContainer = (ViewGroup) container.findViewById(R.id.content_container);
View content = onCreateContentView(inflater, contentContainer);
if (content != null) {
contentContainer.setVisibility(View.VISIBLE);
contentContainer.addView(content);
}
ViewGroup foregroundContainer = (ViewGroup) container.findViewById(
R.id.foreground_container);
View foreground = onCreateForegroundView(inflater, foregroundContainer);
if (foreground != null) {
foregroundContainer.setVisibility(View.VISIBLE);
foregroundContainer.addView(foreground);
}
// Make views visible which were invisible while logo animation is running.
container.findViewById(R.id.page_container).setVisibility(View.VISIBLE);
container.findViewById(R.id.content_container).setVisibility(View.VISIBLE);
if (getPageCount() > 1) {
mPageIndicator.setPageCount(getPageCount());
mPageIndicator.onPageSelected(mCurrentPageIndex, false);
}
if (mCurrentPageIndex == getPageCount() - 1) {
mStartButton.setVisibility(View.VISIBLE);
} else {
mPageIndicator.setVisibility(View.VISIBLE);
}
// Header views.
mTitleView.setText(getPageTitle(mCurrentPageIndex));
mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
}
/**
* Called immediately after the logo animation is complete or no logo animation is specified.
* This method can also be called when the activity is recreated, i.e. when no logo animation
* are performed.
* By default, this method will hide the logo view and start the entrance animation for this
* fragment.
* Overriding subclasses can provide their own data loading logic as to when the entrance
* animation should be executed.
*/
protected void onLogoAnimationFinished() {
startEnterAnimation(false);
}
/**
* Called to start entrance transition. This can be called by subclasses when the logo animation
* and data loading is complete. If force flag is set to false, it will only start the animation
* if it's not already done yet. Otherwise, it will always start the enter animation. In both
* cases, the logo view will hide and the rest of fragment views become visible after this call.
*
* @param force {@code true} if enter animation has to be performed regardless of whether it's
* been done in the past, {@code false} otherwise
*/
protected final void startEnterAnimation(boolean force) {
final Context context = FragmentUtil.getContext(OnboardingFragment.this);
if (context == null) {
return;
}
hideLogoView();
if (mEnterAnimationFinished && !force) {
return;
}
List<Animator> animators = new ArrayList<>();
Animator animator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_page_indicator_enter);
animator.setTarget(getPageCount() <= 1 ? mStartButton : mPageIndicator);
animators.add(animator);
animator = onCreateTitleAnimator();
if (animator != null) {
// Header title.
animator.setTarget(mTitleView);
animators.add(animator);
}
animator = onCreateDescriptionAnimator();
if (animator != null) {
// Header description.
animator.setTarget(mDescriptionView);
animators.add(animator);
}
// Customized animation by the inherited class.
Animator customAnimator = onCreateEnterAnimation();
if (customAnimator != null) {
animators.add(customAnimator);
}
// Return if we don't have any animations.
if (animators.isEmpty()) {
return;
}
mAnimator = new AnimatorSet();
mAnimator.playTogether(animators);
mAnimator.start();
mAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mEnterAnimationFinished = true;
}
});
// Search focus and give the focus to the appropriate child which has become visible.
getView().requestFocus();
}
/**
* Provides the entry animation for description view. This allows users to override the
* default fade and slide animation. Returning null will disable the animation.
*/
@NonNull
protected Animator onCreateDescriptionAnimator() {
return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
R.animator.lb_onboarding_description_enter);
}
/**
* Provides the entry animation for title view. This allows users to override the
* default fade and slide animation. Returning null will disable the animation.
*/
@NonNull
protected Animator onCreateTitleAnimator() {
return AnimatorInflater.loadAnimator(FragmentUtil.getContext(OnboardingFragment.this),
R.animator.lb_onboarding_title_enter);
}
/**
* Returns whether the logo enter animation is finished.
*
* @return {@code true} if the logo enter transition is finished, {@code false} otherwise
*/
protected final boolean isLogoAnimationFinished() {
return mLogoAnimationFinished;
}
/**
* Returns the page count.
*
* @return The page count.
*/
protected abstract int getPageCount();
/**
* Returns the title of the given page.
*
* @param pageIndex The page index.
*
* @return The title of the page.
*/
@Nullable
protected abstract CharSequence getPageTitle(int pageIndex);
/**
* Returns the description of the given page.
*
* @param pageIndex The page index.
*
* @return The description of the page.
*/
@Nullable
protected abstract CharSequence getPageDescription(int pageIndex);
/**
* Returns the index of the current page.
*
* @return The index of the current page.
*/
protected final int getCurrentPageIndex() {
return mCurrentPageIndex;
}
/**
* Called to have the inherited class create background view. This is optional and the fragment
* which doesn't have the background view can return {@code null}. This is called inside
* {@link #onCreateView}.
*
* @param inflater The LayoutInflater object that can be used to inflate the views,
* @param container The parent view that the additional views are attached to.The fragment
* should not add the view by itself.
*
* @return The background view for the onboarding screen, or {@code null}.
*/
@Nullable
protected abstract View onCreateBackgroundView(
@NonNull LayoutInflater inflater,
@NonNull ViewGroup container
);
/**
* Called to have the inherited class create content view. This is optional and the fragment
* which doesn't have the content view can return {@code null}. This is called inside
* {@link #onCreateView}.
*
* <p>The content view would be located at the center of the screen.
*
* @param inflater The LayoutInflater object that can be used to inflate the views,
* @param container The parent view that the additional views are attached to.The fragment
* should not add the view by itself.
*
* @return The content view for the onboarding screen, or {@code null}.
*/
@Nullable
protected abstract View onCreateContentView(
@NonNull LayoutInflater inflater,
@NonNull ViewGroup container
);
/**
* Called to have the inherited class create foreground view. This is optional and the fragment
* which doesn't need the foreground view can return {@code null}. This is called inside
* {@link #onCreateView}.
*
* <p>This foreground view would have the highest z-order.
*
* @param inflater The LayoutInflater object that can be used to inflate the views,
* @param container The parent view that the additional views are attached to.The fragment
* should not add the view by itself.
*
* @return The foreground view for the onboarding screen, or {@code null}.
*/
@Nullable
protected abstract View onCreateForegroundView(
@NonNull LayoutInflater inflater,
@NonNull ViewGroup container
);
/**
* Called when the onboarding flow finishes.
*/
protected void onFinishFragment() { }
/**
* Called when the page changes.
*/
private void onPageChangedInternal(int previousPage) {
if (mAnimator != null) {
mAnimator.end();
}
mPageIndicator.onPageSelected(mCurrentPageIndex, true);
List<Animator> animators = new ArrayList<>();
// Header animation
Animator fadeAnimator = null;
if (previousPage < getCurrentPageIndex()) {
// sliding to left
animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
DESCRIPTION_START_DELAY_MS));
animators.add(createAnimator(mTitleView, true, Gravity.END,
HEADER_APPEAR_DELAY_MS));
animators.add(createAnimator(mDescriptionView, true, Gravity.END,
HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
} else {
// sliding to right
animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
DESCRIPTION_START_DELAY_MS));
animators.add(createAnimator(mTitleView, true, Gravity.START,
HEADER_APPEAR_DELAY_MS));
animators.add(createAnimator(mDescriptionView, true, Gravity.START,
HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
}
final int currentPageIndex = getCurrentPageIndex();
fadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mTitleView.setText(getPageTitle(currentPageIndex));
mDescriptionView.setText(getPageDescription(currentPageIndex));
}
});
final Context context = FragmentUtil.getContext(OnboardingFragment.this);
// Animator for switching between page indicator and button.
if (getCurrentPageIndex() == getPageCount() - 1) {
mStartButton.setVisibility(View.VISIBLE);
Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_page_indicator_fade_out);
navigatorFadeOutAnimator.setTarget(mPageIndicator);
navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mPageIndicator.setVisibility(View.GONE);
}
});
animators.add(navigatorFadeOutAnimator);
Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_start_button_fade_in);
buttonFadeInAnimator.setTarget(mStartButton);
animators.add(buttonFadeInAnimator);
} else if (previousPage == getPageCount() - 1) {
mPageIndicator.setVisibility(View.VISIBLE);
Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_page_indicator_fade_in);
navigatorFadeInAnimator.setTarget(mPageIndicator);
animators.add(navigatorFadeInAnimator);
Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(context,
R.animator.lb_onboarding_start_button_fade_out);
buttonFadeOutAnimator.setTarget(mStartButton);
buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mStartButton.setVisibility(View.GONE);
}
});
animators.add(buttonFadeOutAnimator);
}
mAnimator = new AnimatorSet();
mAnimator.playTogether(animators);
mAnimator.start();
onPageChanged(mCurrentPageIndex, previousPage);
}
/**
* Called when the page has been changed.
*
* @param newPage The new page.
* @param previousPage The previous page.
*/
protected void onPageChanged(int newPage, int previousPage) { }
private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
long startDelay) {
boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
boolean slideRight = (isLtr && slideDirection == Gravity.END)
|| (!isLtr && slideDirection == Gravity.START)
|| slideDirection == Gravity.RIGHT;
Animator fadeAnimator;
Animator slideAnimator;
if (fadeIn) {
fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
slideRight ? sSlideDistance : -sSlideDistance, 0);
fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
} else {
fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
slideRight ? sSlideDistance : -sSlideDistance);
fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
}
fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
fadeAnimator.setTarget(view);
slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
slideAnimator.setTarget(view);
AnimatorSet animator = new AnimatorSet();
animator.playTogether(fadeAnimator, slideAnimator);
if (startDelay > 0) {
animator.setStartDelay(startDelay);
}
return animator;
}
/**
* Sets the resource id for the main icon.
*/
public final void setIconResouceId(int resourceId) {
this.mIconResourceId = resourceId;
if (mMainIconView != null) {
mMainIconView.setImageResource(resourceId);
mMainIconView.setVisibility(View.VISIBLE);
}
}
/**
* Returns the resource id of the main icon.
*/
public final int getIconResourceId() {
return mIconResourceId;
}
}