如需向初次使用的用户说明如何充分利用您的应用,可以在应用启动时显示新手入门信息。以下是新手入门信息的一些示例:
- 当用户第一次访问频道应用时,展示有关提供哪些频道的详细信息。
- 提醒用户关注您的应用中值得注意的功能。
- 说明用户在第一次使用应用时应当执行的必要步骤或建议步骤。
Leanback androidx
库提供了用于向初次使用的用户展示新手入门信息的 OnboardingSupportFragment
类。本节课介绍如何使用 OnboardingSupportFragment
类来展示应用首次启动时所显示的入门信息。OnboardingSupportFragment
利用 TV 界面最佳做法来展示信息,这种方式不仅适合 TV 界面风格,也方便在 TV 设备上导航。
您的 OnboardingSupportFragment
不应包含需要用户输入的界面元素,如按钮和字段。同样,也不应将其用作用户需要定期执行的任务的界面元素。
添加 OnboardingSupportFragment
如需在应用中添加 OnboardingSupportFragment
,请实现一个用于扩展 OnboardingSupportFragment
类的类。将此 Fragment 通过 Activity 的布局 XML 或以编程方式添加到 Activity 中,同时确保 Activity 或 Fragment 采用派生自 Theme_Leanback_Onboarding
的主题背景。
在应用的主 Activity 的 onCreate()
方法中,通过指向 OnboardingSupportFragment's
父 Activity 的 Intent
调用 startActivity()
。这可确保您的 OnboardingSupportFragment
在应用启动后立即显示。
若要确保 OnboardingSupportFragment]
仅在用户首次启动应用时显示,请使用 SharedPreferences
对象来跟踪用户是否已查看过 OnboardingSupportFragment
。定义一个布尔值,使其在用户查看过 OnboardingSupportFragment
时变为 true。在主 Activity 的 onCreate()
中检查这个值,并且仅在值为 false 时才启动 OnboardingSupportFragment
父 Activity。以下示例展示了如何替换用于检查 SharedPreferences
值的 onCreate()
,如果没有设为 true,则调用 startActivity()
来显示 OnboardingSupportFragment
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
// Check if we need to display our OnboardingSupportFragment
if (!sharedPreferences.getBoolean(
MyOnboardingSupportFragment.COMPLETED_ONBOARDING_PREF_NAME, false)) {
// The user hasn't seen the OnboardingSupportFragment yet, so show it
startActivity(new Intent(this, OnboardingActivity.class));
}
}
在用户查看 OnboardingSupportFragment
后,使用 SharedPreferences
对象将其标记为已查看。为此,请在您的 OnboardingSupportFragment
中替换 onFinishFragment()
并将 SharedPreferences
值设为 true,如以下示例所示:
@Override
protected void onFinishFragment() {
super.onFinishFragment();
// User has seen OnboardingSupportFragment, so mark our SharedPreferences
// flag as completed so that we don't show our OnboardingSupportFragment
// the next time the user launches the app.
SharedPreferences.Editor sharedPreferencesEditor =
PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
sharedPreferencesEditor.putBoolean(
COMPLETED_ONBOARDING_PREF_NAME, true);
sharedPreferencesEditor.apply();
}
注意:当切换到最后一页的时候,会显示"开始使用"的按钮,点击该按钮会调用onFinishFragment
方法,因此可以在该方法中保存标记并调用Activity的finish方法关闭该引导页。
添加 OnboardingSupportFragment 页面
在添加 OnboardingSupportFragment 之后,您需要定义新手入门信息页。
OnboardingSupportFragment` 可在一系列有序的页面中显示内容。每个页面可以包含标题、说明和若干包含图片或动画的子视图。
图 2 展示了一个示例页面,其中用标注标出了您的 OnboardingSupportFragment
可以提供的可自定义页面元素。页面元素包括:
- 页面标题。
- 页面说明。
- 页面内容视图,本例中是一个简单的绿色对勾显示在灰色方框中。此视图是可选的。此视图可用于展示页面详细信息,例如突出显示页面所介绍的应用功能的屏幕截图。
- 页面背景视图,本例中为一个简单的蓝色渐变背景。此视图始终呈现在页面上其他视图的后面。此视图是可选的。
- 页面前景视图,本例中为一个徽标。此视图始终呈现在页面上所有其他视图的前面。此视图是可选的。
替换以下各种向系统提供页面信息的方法:
-
getPageCount()
会返回OnboardingSupportFragment
中的页数。 -
getPageTitle()
会返回所请求页码的标题。 -
getPageDescription()
会返回所请求页码的说明。
替换以下各种方法,以提供用于显示图片或动画的可选子视图:
-
onCreateBackgroundView()
会返回您创建用来作为背景视图的View
;如果不需要背景视图,则返回 null。 -
onCreateContentView()
会返回您创建用来作为内容视图的View
;如果不需要内容视图,则返回 null。 -
onCreateForegroundView()
会返回您创建用来作为前台视图的View
;如果不需要前台视图,则返回 null。
系统会将您创建的 View
添加到页面布局中。以下示例会替换 onCreateContentView()
并返回 ImageView
:
private ImageView contentView;
...
@Override
protected View onCreateContentView(LayoutInflater inflater, ViewGroup container) {
contentView = new ImageView(getContext());
contentView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
contentView.setImageResource(R.drawable.onboarding_content_view);
contentView.setPadding(0, 32, 0, 32);
return contentView;
}
添加初始徽标屏幕
您的 OnboardingSupportFragment
可在启动时显示可选的徽标屏幕,用来介绍您的应用。如果您希望将 Drawable
显示为徽标屏幕,请在 OnboardingSupportFragment's
onCreate()
方法中使用 Drawable
的 ID 调用 setLogoResourceId()
。系统将淡入并短暂显示此 Drawable
,再淡出 Drawable
,然后显示 OnboardingSupportFragment
的第一个页面。
若要为徽标屏幕提供自定义动画,则不应调用 setLogoResourceId()
,而应替换 onCreateLogoAnimation()
并返回一个渲染自定义动画的 Animator
对象,如以下示例所示:
@Override
public Animator onCreateLogoAnimation() {
return AnimatorInflater.loadAnimator(getContext(),
R.animator.onboarding_logo_screen_animation);
}
自定义页面动画
在显示 OnboardingSupportFragment
的第一个页面时,以及当用户导航到另一个页面时,系统会使用默认动画。您可以通过替换 OnboardingSupportFragment
中的方法来自定义这些动画。
若要自定义在第一个页面上显示的动画,请替换 onCreateEnterAnimation()
并返回 Animator
。以下示例会创建一个 Animator
,它会在水平方向上缩放内容视图:
@Override
protected Animator onCreateEnterAnimation() {
Animator startAnimator = ObjectAnimator.ofFloat(contentView,
View.SCALE_X, 0.2f, 1.0f).setDuration(ANIMATION_DURATION);
return startAnimator;
}
若要自定义用户导航到另一个页面时使用的动画,请替换 onPageChanged()
。在 onPageChanged() 方法中,创建用于移除上一页并显示下一页的
Animators,将它们添加到
AnimatorSet` 中,再播放这个集合。以下示例使用一个淡出动画来移除上一页,更新内容视图图像,然后使用淡入动画来显示下一页:
@Override
protected void onPageChanged(final int newPage, int previousPage) {
// Create a fade-out animation used to fade out previousPage and, once
// done, swaps the contentView image with the next page's image.
Animator fadeOut = ObjectAnimator.ofFloat(mContentView,
View.ALPHA, 1.0f, 0.0f).setDuration(ANIMATION_DURATION);
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mContentView.setImageResource(pageImages[newPage]);
}
});
// Create a fade-in animation used to fade in nextPage
Animator fadeIn = ObjectAnimator.ofFloat(mContentView,
View.ALPHA, 0.0f, 1.0f).setDuration(ANIMATION_DURATION);
// Create AnimatorSet with our fade-out and fade-in animators, and start it
AnimatorSet set = new AnimatorSet();
set.playSequentially(fadeOut, fadeIn);
set.start();
}
自定义主题背景
任何 OnboardingSupportFragment
实现都必须使用 Theme_Leanback_Onboarding
主题背景,或继承自 Theme_Leanback_Onboarding
的主题背景。若要为您的 OnboardingSupportFragment
设置主题背景,请执行以下某项操作:
-
设置
OnboardingSupportFragment's
父 Activity 以使用所需的主题背景。以下示例展示了如何设置 Activity 以使用应用清单中的Theme_Leanback_Onboarding
:<activity android:name=".OnboardingActivity" android:enabled="true" android:exported="true" android:theme="@style/Theme.Leanback.Onboarding"> </activity>
通过在自定义 Activity 主题背景中使用
LeanbackOnboardingTheme_onboardingTheme
属性,设置父 Activity 中的主题背景。将该属性指向只有您的 Activity 中的OnboardingSupportFragment
对象会使用的另一个自定义主题背景。如果您的 Activity 已使用了一个自定义主题背景,并且您不想将OnboardingSupportFragment
样式应用到 Activity 中的其他视图,请采用这种方法。替换
onProvideTheme()
并返回所需的主题背景。如果有多个 Activity 使用您的OnboardingSupportFragment
,或者父 Activity 无法使用所需的主题背景,请采用这种方法。以下示例会替换onProvideTheme()
并返回Theme_Leanback_Onboarding
:
@Override
public int onProvideTheme() {
return R.style.Theme_Leanback_Onboarding;
}
至此,关于如何"向初次使用的用户介绍您的应用"就介绍完了,但是我相信小伙伴们还是一头雾水的吧,因为理论太多,而是代码过于碎片化。
实战
添加Leanback库
dependencies {
...
implementation 'androidx.leanback:leanback:1.0.0'
}
启动页(MainActivity)
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SharedPreferences sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(this);
// Check if we need to display our OnboardingSupportFragment
if (!sharedPreferences.getBoolean(
BoardingSupportFragment.COMPLETED_ONBOARDING_PREF_NAME, false)) {
// The user hasn't seen the OnboardingSupportFragment yet, so show it
startActivity(new Intent(this, OnboardingActivity.class));
}
}
}
添加OnboardingActivity
添加OnboardingActivity继承FragmentActivity
public class OnboardingActivity extends FragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_onboarding);
}
}
activity_onboarding.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".OnboardingActivity">
<fragment
android:id="@+id/BoardingSupportFragment"
android:name="com.winsat.myapplication.BoardingSupportFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
BoardingSupportFragment实现
public class BoardingSupportFragment extends OnboardingSupportFragment {
public static final String COMPLETED_ONBOARDING_PREF_NAME = "COMPLETED_ONBOARDING_PREF_NAME";
private String[] titles = {"Page1", "Page2", "Page3"};
private String[] descs = {"This is page1", "This is page2", "This is page3"};
private int[] contentImags = {R.drawable.page1, R.drawable.page2, R.drawable.page3};
ImageView contentView;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//添加初始徽标屏幕,会在显示第一页之前显示ic_launcher
setLogoResourceId(R.mipmap.ic_launcher);
}
@Override
public int onProvideTheme() {
//这里设置主题,或者直接设置该Fragment对应的Activity的主题为R.style.Theme_Leanback_Onboarding;
return R.style.Theme_Leanback_Onboarding;
}
@Nullable
@Override
protected Animator onCreateEnterAnimation() {
//第一个页面上显示的动画
return ObjectAnimator.ofFloat(contentView,
View.SCALE_X, 0.2f, 1.0f).setDuration(ANIMATION_DURATION);
}
@Override
protected int getPageCount() {
return titles.length;
}
@Override
protected CharSequence getPageTitle(int pageIndex) {
return titles[pageIndex];
}
@Override
protected CharSequence getPageDescription(int pageIndex) {
return descs[pageIndex];
}
@Nullable
@Override
protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container) {
//背景View是一个全屏的View
return null;
}
@Nullable
@Override
protected View onCreateContentView(LayoutInflater inflater, ViewGroup container) {
//这里需要将contentView作为全局变量,onPageChanged方法中更新图片,背景和前景图片类似
contentView = new ImageView(getContext());
contentView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
int pageIndex = getCurrentPageIndex();
contentView.setImageResource(contentImags[pageIndex]);
contentView.setPadding(0, 32, 0, 32);
return contentView;
}
@Override
protected void onPageChanged(final int newPage, int previousPage) {
super.onPageChanged(newPage, previousPage);
// //当界面发生变化时,contentView设置对应位置的图片,背景和前景图片类似
// contentView.setImageResource(contentImags[newPage]);
// Create a fade-out animation used to fade out previousPage and, once
// done, swaps the contentView image with the next page's image.
Animator fadeOut = ObjectAnimator.ofFloat(contentView,
View.ALPHA, 1.0f, 0.0f).setDuration(ANIMATION_DURATION);
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
//当界面发生变化时,contentView设置对应位置的图片,背景和前景图片类似
contentView.setImageResource(contentImags[newPage]);
}
});
// Create a fade-in animation used to fade in nextPage
Animator fadeIn = ObjectAnimator.ofFloat(contentView,
View.ALPHA, 0.0f, 1.0f).setDuration(ANIMATION_DURATION);
// Create AnimatorSet with our fade-out and fade-in animators, and start it
AnimatorSet set = new AnimatorSet();
set.playSequentially(fadeOut, fadeIn);
set.start();
}
@Nullable
@Override
protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container) {
//前景View是一个全屏的View,因此添加View的时候需要考虑到子View对应的位置
return null;
}
@Override
protected void onFinishFragment() {
super.onFinishFragment();
// User has seen OnboardingSupportFragment, so mark our SharedPreferences
// flag as completed so that we don't show our OnboardingSupportFragment
// the next time the user launches the app.
SharedPreferences.Editor sharedPreferencesEditor =
PreferenceManager.getDefaultSharedPreferences(getContext()).edit();
sharedPreferencesEditor.putBoolean(
COMPLETED_ONBOARDING_PREF_NAME, true);
sharedPreferencesEditor.apply();
FragmentActivity fragmentActivity = getActivity();
if (fragmentActivity != null) {
fragmentActivity.finish();
}
}
}