如需向初次使用的用戶(hù)說(shuō)明如何充分利用您的應(yīng)用,可以在應(yīng)用啟動(dòng)時(shí)顯示新手入門(mén)信息。以下是新手入門(mén)信息的一些示例:
- 當(dāng)用戶(hù)第一次訪問(wèn)頻道應(yīng)用時(shí),展示有關(guān)提供哪些頻道的詳細(xì)信息构哺。
- 提醒用戶(hù)關(guān)注您的應(yīng)用中值得注意的功能。
- 說(shuō)明用戶(hù)在第一次使用應(yīng)用時(shí)應(yīng)當(dāng)執(zhí)行的必要步驟或建議步驟战坤。
Leanback androidx
庫(kù)提供了用于向初次使用的用戶(hù)展示新手入門(mén)信息的 OnboardingSupportFragment
類(lèi)曙强。本節(jié)課介紹如何使用 OnboardingSupportFragment
類(lèi)來(lái)展示應(yīng)用首次啟動(dòng)時(shí)所顯示的入門(mén)信息。OnboardingSupportFragment
利用 TV 界面最佳做法來(lái)展示信息途茫,這種方式不僅適合 TV 界面風(fēng)格碟嘴,也方便在 TV 設(shè)備上導(dǎo)航。
您的 OnboardingSupportFragment
不應(yīng)包含需要用戶(hù)輸入的界面元素娜扇,如按鈕和字段。同樣栅组,也不應(yīng)將其用作用戶(hù)需要定期執(zhí)行的任務(wù)的界面元素雀瓢。
添加 OnboardingSupportFragment
如需在應(yīng)用中添加 OnboardingSupportFragment
,請(qǐng)實(shí)現(xiàn)一個(gè)用于擴(kuò)展 OnboardingSupportFragment
類(lèi)的類(lèi)玉掸。將此 Fragment 通過(guò) Activity 的布局 XML 或以編程方式添加到 Activity 中刃麸,同時(shí)確保 Activity 或 Fragment 采用派生自 Theme_Leanback_Onboarding
的主題背景。
在應(yīng)用的主 Activity 的 onCreate()
方法中司浪,通過(guò)指向 OnboardingSupportFragment's
父 Activity 的 Intent
調(diào)用 startActivity()
泊业。這可確保您的 OnboardingSupportFragment
在應(yīng)用啟動(dòng)后立即顯示把沼。
若要確保 OnboardingSupportFragment]
僅在用戶(hù)首次啟動(dòng)應(yīng)用時(shí)顯示,請(qǐng)使用 SharedPreferences
對(duì)象來(lái)跟蹤用戶(hù)是否已查看過(guò) OnboardingSupportFragment
吁伺。定義一個(gè)布爾值智政,使其在用戶(hù)查看過(guò) OnboardingSupportFragment
時(shí)變?yōu)?true。在主 Activity 的 onCreate()
中檢查這個(gè)值箱蝠,并且僅在值為 false 時(shí)才啟動(dòng) OnboardingSupportFragment
父 Activity。以下示例展示了如何替換用于檢查 SharedPreferences
值的 onCreate()
垦垂,如果沒(méi)有設(shè)為 true宦搬,則調(diào)用 startActivity()
來(lái)顯示 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));
}
}
在用戶(hù)查看 OnboardingSupportFragment
后,使用 SharedPreferences
對(duì)象將其標(biāo)記為已查看劫拗。為此间校,請(qǐng)?jiān)谀?OnboardingSupportFragment
中替換 onFinishFragment()
并將 SharedPreferences
值設(shè)為 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();
}
注意:當(dāng)切換到最后一頁(yè)的時(shí)候页慷,會(huì)顯示"開(kāi)始使用"的按鈕憔足,點(diǎn)擊該按鈕會(huì)調(diào)用onFinishFragment
方法,因此可以在該方法中保存標(biāo)記并調(diào)用Activity的finish方法關(guān)閉該引導(dǎo)頁(yè)酒繁。
添加 OnboardingSupportFragment 頁(yè)面
在添加 OnboardingSupportFragment 之后滓彰,您需要定義新手入門(mén)信息頁(yè)。
OnboardingSupportFragment` 可在一系列有序的頁(yè)面中顯示內(nèi)容州袒。每個(gè)頁(yè)面可以包含標(biāo)題揭绑、說(shuō)明和若干包含圖片或動(dòng)畫(huà)的子視圖。
圖 2 展示了一個(gè)示例頁(yè)面他匪,其中用標(biāo)注標(biāo)出了您的 OnboardingSupportFragment
可以提供的可自定義頁(yè)面元素。頁(yè)面元素包括:
- 頁(yè)面標(biāo)題夸研。
- 頁(yè)面說(shuō)明邦蜜。
- 頁(yè)面內(nèi)容視圖,本例中是一個(gè)簡(jiǎn)單的綠色對(duì)勾顯示在灰色方框中亥至。此視圖是可選的悼沈。此視圖可用于展示頁(yè)面詳細(xì)信息,例如突出顯示頁(yè)面所介紹的應(yīng)用功能的屏幕截圖抬闯。
- 頁(yè)面背景視圖井辆,本例中為一個(gè)簡(jiǎn)單的藍(lán)色漸變背景。此視圖始終呈現(xiàn)在頁(yè)面上其他視圖的后面溶握。此視圖是可選的杯缺。
- 頁(yè)面前景視圖,本例中為一個(gè)徽標(biāo)睡榆。此視圖始終呈現(xiàn)在頁(yè)面上所有其他視圖的前面萍肆。此視圖是可選的袍榆。
替換以下各種向系統(tǒng)提供頁(yè)面信息的方法:
-
getPageCount()
會(huì)返回OnboardingSupportFragment
中的頁(yè)數(shù)。 -
getPageTitle()
會(huì)返回所請(qǐng)求頁(yè)碼的標(biāo)題塘揣。 -
getPageDescription()
會(huì)返回所請(qǐng)求頁(yè)碼的說(shuō)明包雀。
替換以下各種方法,以提供用于顯示圖片或動(dòng)畫(huà)的可選子視圖:
-
onCreateBackgroundView()
會(huì)返回您創(chuàng)建用來(lái)作為背景視圖的View
亲铡;如果不需要背景視圖才写,則返回 null。 -
onCreateContentView()
會(huì)返回您創(chuàng)建用來(lái)作為內(nèi)容視圖的View
奖蔓;如果不需要內(nèi)容視圖赞草,則返回 null。 -
onCreateForegroundView()
會(huì)返回您創(chuàng)建用來(lái)作為前臺(tái)視圖的View
吆鹤;如果不需要前臺(tái)視圖厨疙,則返回 null。
系統(tǒng)會(huì)將您創(chuàng)建的 View
添加到頁(yè)面布局中疑务。以下示例會(huì)替換 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;
}
添加初始徽標(biāo)屏幕
您的 OnboardingSupportFragment
可在啟動(dòng)時(shí)顯示可選的徽標(biāo)屏幕沾凄,用來(lái)介紹您的應(yīng)用。如果您希望將 Drawable
顯示為徽標(biāo)屏幕知允,請(qǐng)?jiān)?OnboardingSupportFragment's
onCreate()
方法中使用 Drawable
的 ID 調(diào)用 setLogoResourceId()
撒蟀。系統(tǒng)將淡入并短暫顯示此 Drawable
,再淡出 Drawable
温鸽,然后顯示 OnboardingSupportFragment
的第一個(gè)頁(yè)面牙肝。
若要為徽標(biāo)屏幕提供自定義動(dòng)畫(huà),則不應(yīng)調(diào)用 setLogoResourceId()
嗤朴,而應(yīng)替換 onCreateLogoAnimation()
并返回一個(gè)渲染自定義動(dòng)畫(huà)的 Animator
對(duì)象配椭,如以下示例所示:
@Override
public Animator onCreateLogoAnimation() {
return AnimatorInflater.loadAnimator(getContext(),
R.animator.onboarding_logo_screen_animation);
}
自定義頁(yè)面動(dòng)畫(huà)
在顯示 OnboardingSupportFragment
的第一個(gè)頁(yè)面時(shí),以及當(dāng)用戶(hù)導(dǎo)航到另一個(gè)頁(yè)面時(shí)雹姊,系統(tǒng)會(huì)使用默認(rèn)動(dòng)畫(huà)股缸。您可以通過(guò)替換 OnboardingSupportFragment
中的方法來(lái)自定義這些動(dòng)畫(huà)。
若要自定義在第一個(gè)頁(yè)面上顯示的動(dòng)畫(huà)吱雏,請(qǐng)?zhí)鎿Q onCreateEnterAnimation()
并返回 Animator
敦姻。以下示例會(huì)創(chuàng)建一個(gè) Animator
,它會(huì)在水平方向上縮放內(nèi)容視圖:
@Override
protected Animator onCreateEnterAnimation() {
Animator startAnimator = ObjectAnimator.ofFloat(contentView,
View.SCALE_X, 0.2f, 1.0f).setDuration(ANIMATION_DURATION);
return startAnimator;
}
若要自定義用戶(hù)導(dǎo)航到另一個(gè)頁(yè)面時(shí)使用的動(dòng)畫(huà)歧杏,請(qǐng)?zhí)鎿Q onPageChanged()
镰惦。在 onPageChanged() 方法中,創(chuàng)建用于移除上一頁(yè)并顯示下一頁(yè)的
Animators犬绒,將它們添加到
AnimatorSet` 中旺入,再播放這個(gè)集合。以下示例使用一個(gè)淡出動(dòng)畫(huà)來(lái)移除上一頁(yè),更新內(nèi)容視圖圖像茵瘾,然后使用淡入動(dòng)畫(huà)來(lái)顯示下一頁(yè):
@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
實(shí)現(xiàn)都必須使用 Theme_Leanback_Onboarding
主題背景礼华,或繼承自 Theme_Leanback_Onboarding
的主題背景。若要為您的 OnboardingSupportFragment
設(shè)置主題背景拗秘,請(qǐng)執(zhí)行以下某項(xiàng)操作:
-
設(shè)置
OnboardingSupportFragment's
父 Activity 以使用所需的主題背景圣絮。以下示例展示了如何設(shè)置 Activity 以使用應(yīng)用清單中的Theme_Leanback_Onboarding
:<activity android:name=".OnboardingActivity" android:enabled="true" android:exported="true" android:theme="@style/Theme.Leanback.Onboarding"> </activity>
通過(guò)在自定義 Activity 主題背景中使用
LeanbackOnboardingTheme_onboardingTheme
屬性,設(shè)置父 Activity 中的主題背景雕旨。將該屬性指向只有您的 Activity 中的OnboardingSupportFragment
對(duì)象會(huì)使用的另一個(gè)自定義主題背景扮匠。如果您的 Activity 已使用了一個(gè)自定義主題背景,并且您不想將OnboardingSupportFragment
樣式應(yīng)用到 Activity 中的其他視圖凡涩,請(qǐng)采用這種方法餐禁。替換
onProvideTheme()
并返回所需的主題背景。如果有多個(gè) Activity 使用您的OnboardingSupportFragment
突照,或者父 Activity 無(wú)法使用所需的主題背景,請(qǐng)采用這種方法氧吐。以下示例會(huì)替換onProvideTheme()
并返回Theme_Leanback_Onboarding
:
@Override
public int onProvideTheme() {
return R.style.Theme_Leanback_Onboarding;
}
至此讹蘑,關(guān)于如何"向初次使用的用戶(hù)介紹您的應(yīng)用"就介紹完了,但是我相信小伙伴們還是一頭霧水的吧筑舅,因?yàn)槔碚撎嘧浚谴a過(guò)于碎片化。
實(shí)戰(zhàn)
添加Leanback庫(kù)
dependencies {
...
implementation 'androidx.leanback:leanback:1.0.0'
}
啟動(dòng)頁(yè)(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實(shí)現(xiàn)
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);
//添加初始徽標(biāo)屏幕翠拣,會(huì)在顯示第一頁(yè)之前顯示ic_launcher
setLogoResourceId(R.mipmap.ic_launcher);
}
@Override
public int onProvideTheme() {
//這里設(shè)置主題版仔,或者直接設(shè)置該Fragment對(duì)應(yīng)的Activity的主題為R.style.Theme_Leanback_Onboarding;
return R.style.Theme_Leanback_Onboarding;
}
@Nullable
@Override
protected Animator onCreateEnterAnimation() {
//第一個(gè)頁(yè)面上顯示的動(dòng)畫(huà)
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是一個(gè)全屏的View
return null;
}
@Nullable
@Override
protected View onCreateContentView(LayoutInflater inflater, ViewGroup container) {
//這里需要將contentView作為全局變量,onPageChanged方法中更新圖片误墓,背景和前景圖片類(lèi)似
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);
// //當(dāng)界面發(fā)生變化時(shí)蛮粮,contentView設(shè)置對(duì)應(yīng)位置的圖片,背景和前景圖片類(lèi)似
// 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) {
//當(dāng)界面發(fā)生變化時(shí)谜慌,contentView設(shè)置對(duì)應(yīng)位置的圖片然想,背景和前景圖片類(lèi)似
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是一個(gè)全屏的View,因此添加View的時(shí)候需要考慮到子View對(duì)應(yīng)的位置
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();
}
}
}