LeanBack 之Preference
LeanBack 是 JectPack的一個(gè)用于開發(fā)Tv App的一個(gè)庫擎浴,對(duì)UI的展示比較友好丐膝,也比較常用捞魁。它里面有一個(gè)Prefrerence的庫是專門用來開發(fā)Setting 界面Ui的一個(gè)庫澎怒。
我們項(xiàng)目里面使用了preference现诀,經(jīng)常會(huì)遇到preference的一些問題。 我們?cè)傧M@篇文章達(dá)到得目的:可以通過這篇文章正常使用LeanBack 得preference鞍时。
為何使用leanback-preference
開發(fā)setting界面,方便簡(jiǎn)介,易于理解逆巍,很多個(gè)嵌套setting頁面(menu)布局只需要一個(gè)xml文件就可以完成及塘。
添加引用
buildgradle 里面添加依賴
implementation 'androidx.leanback:leanback-preference:1.0.0'
使用 demo1
2 界面效果
點(diǎn)擊inner 之后得界面
1 xml文件
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/pref_title_settings">
<SwitchPreference
android:title="@string/pref_title_recommendations"
android:defaultValue="true"
android:key="@string/pref_key_recommendations" />
<Preference
android:key="setting1"
android:title="pref_setting_1" />
</PreferenceCategory>
<Preference
android:key="@string/pref_key_login"
android:title="@string/pref_title_login" />
<PreferenceScreen android:key="key_inner" android:title="inner">
<Preference
android:key="inner_1"
android:title="pref_title_login" />
<Preference
android:key="inner_2"
android:title="pref_title_login2" />
</PreferenceScreen>
</PreferenceScreen>
xml 文件標(biāo)簽和圖片界面對(duì)照
preferenceScreen 對(duì)應(yīng)的是一個(gè)頁面
PreferenceCategory 對(duì)應(yīng)了一個(gè)標(biāo)簽 (一組item)
第一個(gè)界面的inner 點(diǎn)開調(diào)用到第二個(gè)界面,它對(duì)應(yīng)的也是一個(gè)prefernceScreen.
各個(gè)控件使用介紹(常用的用法和屬性)
Preference 控件(自動(dòng)存热窦)
Preference 為啥叫Preference 跟SharedPreference 有啥關(guān)系嗎笙僚?
答案是有關(guān)系的。
Preference 的一些值都存在本地的灵再。跟他的一個(gè)屬性 mPersistent (后面屬性的時(shí)候會(huì)講到)有關(guān)系 可以在xml 里面設(shè)置 android:persistent="true" 在java代碼里面也可以設(shè)置肋层。
看下怎么存儲(chǔ)的。
Preference的一些代碼
// 判斷是否可以本地化存儲(chǔ)一些內(nèi)容
// hasKey Preference是否設(shè)置 key 是以這個(gè)key 作為本地化的鍵來存儲(chǔ)的
// isPersistent 就是屬性的設(shè)置
protected boolean shouldPersist() {
return mPreferenceManager != null && isPersistent() && hasKey();
}
// 存儲(chǔ)字符串的 里面用的是 PreferenceDataStore 或者 SharedPreferences 用來存儲(chǔ)的
protected boolean persistString(String value) {
if (!shouldPersist()) { // 判斷應(yīng)不應(yīng)該本地化
return false;
}
// Shouldn't store null
if (TextUtils.equals(value, getPersistedString(null))) {
// It's already there, so the same as persisting
return true;
}
PreferenceDataStore dataStore = getPreferenceDataStore();
if (dataStore != null) {
dataStore.putString(mKey, value);
} else {
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putString(mKey, value);
tryCommit(editor);
}
return true;
}
EdittextPreference 的存值取值(初始化)的方法
// 調(diào)用父類 persistString 保存本地化
public void setText(String text) {
....
persistString(text);
....
}
}
初始化
//restoreValue 是否取本地存的值 跟父類的 shouldPersist 有關(guān)
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setText(restoreValue ? getPersistedString(mText) : (String) defaultValue);
}
上面說的是 保存字符串的方法翎迁,在EdittextPreference 會(huì)調(diào)用自動(dòng)保存edittext的字符串栋猖,當(dāng)在重新進(jìn)入頁面的時(shí)候會(huì)默認(rèn)去取原來存的值。
默認(rèn)是用SharedPreferences 存儲(chǔ)的汪榔,其實(shí)上面先 判斷用的有沒有PreferenceDataStore 再去使用 SharedPreferences 保存的了蒲拉,如果設(shè)置了PreferenceDataStore 的話先用PreferenceDataStore 存儲(chǔ)。PreferenceDataStore 提供了了一些自動(dòng)存值取值的 abstract class痴腌,自己去通過別的方式實(shí)現(xiàn)存值取值雌团。
下面在看其他的一些保存值的方式
// Preference class
- persistBoolean TwoStatePreference 比如 switchPreference checkboxPreference 等。
- persistInt SeekBarPreference 使用
- persistFloat 暫時(shí)還沒發(fā)現(xiàn)自帶的preference 使用 士聪,但是得封裝好給自定義的需要存這個(gè)類型的使用
- persistLong 同上
- persistStringSet MultiSelectListPreference 多選列表類型
- persistString EditTextPreference ListPreference 單選列表
Preference 的屬性
一般都可以在xml 和 java代碼進(jìn)行設(shè)置锦援,一般是知道的就在xml進(jìn)行靜態(tài)設(shè)置,不確定的一般在java代碼里面進(jìn)行動(dòng)態(tài)設(shè)置剥悟。
- key preference 的key就相當(dāng)于 view的id 一樣在java代碼里面可以通過key 來找到preference灵寺。例如Preference placement = findPreference(KEY_SOUND_PLACEMENT)。
- title preference的 title普通顯示的大的文字懦胞,比較常用替久。
- icon preference的上顯示的icon 一般在左側(cè)。
- layout 是用來替換單獨(dú)的布局躏尉。下面單獨(dú)有布局的講解蚯根。
上面四個(gè)屬性是配置的跟頁面ui展示的內(nèi)容有關(guān)系。 - enable preference的enable 類似于view的enable胀糜,這個(gè)時(shí)候不可獲取焦點(diǎn)颅拦,顏色比較暗。例如原生setting 里面屏保 startnow 這個(gè)當(dāng)屏保關(guān)閉的時(shí)候就是enable false 不可獲取焦點(diǎn)教藻,顏色也變暗了距帅。
- visible preference 的visible的屬性是顯示不顯示的屬性,比如一個(gè)頁面的設(shè)置項(xiàng)在不同的信號(hào)源里面有的項(xiàng)顯示有的不顯示就使用的設(shè)個(gè)括堤。
還有在intent 跳轉(zhuǎn)activity 和data 的內(nèi)容設(shè)置
<Preference
android:key="key_epg_program_search_travel"
android:title="TRAVEL">
<intent android:targetClass="com.android.tv.epg.activity.EpgSearchProgramActivity"
android:targetPackage="com.android.tv"
android:data="key_epg_program_search_travel"/>
</Preference>
常用的監(jiān)聽方法
setOnPreferenceClickListener 跟View的onclick listenner類似 用preference 的key來進(jìn)行判斷
setOnPreferenceChangeListener twoState 的常用這個(gè)這個(gè)方法稍微有一個(gè)小特點(diǎn)碌秸,比較有用
public boolean onPreferenceChange(Preference preference, Object newValue) {
return false;
}
這個(gè)方法的這個(gè)返回值 false 是ui 和值都不變化 newValue 是將要變化的值绍移。 比如原來是switch 關(guān)的狀態(tài),點(diǎn)擊一下 newValue 是true返回false的話讥电,switch的ui 不變蹂窖,返回true的話switch的UI變化。
常用的preference 子類
官方已經(jīng)封裝好了 常用的Preference 的子類 常用
- CheckBoxPreference 單選框
- SwitchPreference switch的preference
CheckBoxPreference 和 SwitchPreference 都是TwoStatePreference check的方法 SwitchPreference 多了 on 和off的時(shí)候text的設(shè)置 - ListPreference 列表使用的也可以是單選框使用
下面是使用例子
<ListPreference
android:defaultValue="0"
android:entries="@array/pref_prefecture_area"
android:entryValues="@array/pref_prefecture_area_code"
android:key="key_prefs_area"
android:title="@string/menu_prefecture">
defaultValue 是默認(rèn)選中的item恩敌。
enteries 是默認(rèn)的顯示的列表
entryValues 是和顯示的列表一一對(duì)應(yīng)的類似于key 和 defaultValue是對(duì)應(yīng)的瞬测。
上面幾行設(shè)置之后功能 就把列表展示出來了,還會(huì)記錄位置纠炮,不需要我們單獨(dú)記錄月趟。
- MultiSelectListPreference 多選框的列表
類似于ListPreference ,ListPreference是單選框 MultiSelectListPreference是多選框
MultiSelectListPreference 會(huì)把選中的 value 列表存下來下次就可以使用了恢口。
<MultiSelectListPreference
android:defaultValue="@array/pref_tuners"
android:entries="@array/pref_tuners"
android:entryValues="@array/pref_tuners"
android:key="key_prefs_tuner_mode"
android:title="MultiSelectListPreference" >
下面是圖片
- 注意上面 ListPreference 和 MultiSelectListPreference 里面 entryValues 不能按照語言等 翻譯的 entries 需要按照語言翻譯的孝宗。
Preference 的fragment 界面處理
有時(shí)候我們都在xm了里面嵌套了很多的頁面,F(xiàn)ragment 也就一個(gè)類也就展示所有的界面這樣的話如果在java里面寫的邏輯比較多的話代碼就非常復(fù)雜全都寫在一個(gè)類里面弧蝇。
就像
public class SettingsFragment extends LeanbackSettingsFragment
implements DialogPreference.TargetFragment {
private final static String PREFERENCE_RESOURCE_ID = "preferenceResource";
private final static String PREFERENCE_ROOT = "root";
private PreferenceFragment mPreferenceFragment;
@Override
public void onPreferenceStartInitialScreen() {
mPreferenceFragment = buildPreferenceFragment(R.xml.settings, null);
startPreferenceFragment(mPreferenceFragment);
}
@Override
public boolean onPreferenceStartScreen(PreferenceFragment preferenceFragment,
PreferenceScreen preferenceScreen) {
PreferenceFragment frag = buildPreferenceFragment(R.xml.settings,
preferenceScreen.getKey());
startPreferenceFragment(frag);
return true;
}
···
private PreferenceFragment buildPreferenceFragment(int preferenceResId, String root) {
PreferenceFragment fragment = new PrefFragment();
Bundle args = new Bundle();
args.putInt(PREFERENCE_RESOURCE_ID, preferenceResId);
args.putString(PREFERENCE_ROOT, root);
fragment.setArguments(args);
return fragment;
}
public static class PrefFragment extends LeanbackPreferenceFragment {
@Override
public void onCreatePreferences(Bundle bundle, String s) {
String root = getArguments().getString(PREFERENCE_ROOT, null);
int prefResId = getArguments().getInt(PREFERENCE_RESOURCE_ID);
if (root == null) {
addPreferencesFromResource(prefResId);
} else {
setPreferencesFromResource(prefResId, root);
}
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference.getKey().equals(getString(R.string.pref_key_login))) {
// Open an AuthenticationActivity
// startActivity(new Intent(getActivity(), AuthenticationActivity.class));
}
return super.onPreferenceTreeClick(preference);
}
}
}
上面是官方的demo的界面 所有的一般的業(yè)務(wù)邏輯都是在PrefFragment 里面處理的
buildPreferenceFragment 這個(gè)方法 第一和xml 文件名字 第二個(gè)是PreferencePage 對(duì)應(yīng)的key 根頁面的話就是 null碳褒。
每一個(gè) PrefFragment 對(duì)應(yīng)的是一個(gè)界面也就是對(duì)應(yīng)一個(gè)PreferencePage。
如果都在PrefFragment 處理的話就很復(fù)雜看疗。
我們這個(gè)地方可以多創(chuàng)建 LeanbackPreferenceFragment 來單獨(dú)處理一些邏輯沙峻。
private PreferenceFragment buildPreferenceFragment(int preferenceResId, String screenKey) {
PreferenceFragment fragment = null;
if (TextUtils.isEmpty(screenKey)) {
fragment = new PrefFragment();
} else {
switch (screenKey) {
case KEY_RECEPTION_SETTINGS:
fragment = new ReceptionFragment();
break;
case KEY_PREFECTURE_SETTINGS:
fragment = new PrefectureFragment();
break;
case KEY_CHANNEL_SKIP_SETTINGS:
fragment = new ChannelSkipFragment();
break;
case Constants.KEY_PREFS_REC_SETTINGS:
fragment = new RecordingSettingsFragment();
break;
case KEY_SYSTEM_SETTINGS:
fragment = new MenuSettingsSystemFragment();
break;
case KEY_SYSTEM_PARENTAL_CONTROL:
fragment = new MenuParentalControlFragment();
break;
case KEY_SYSTEM_PARENTAL_CONTROL_JAPANESE_BROADCAST:
fragment = new JapaneseBroadcastFragment();
break;
case Constants.KEY_ADVANCED_SETTINGS_OADL_SETTINGS:
fragment = new OADLSettingsFragment();
break;
case Constants.KEY_PREFS_B_CAS_CARD_INFO:
fragment = new BCasFragment();
break;
default:
fragment = new PrefFragment();
break;
}
}
Bundle args = new Bundle();
args.putInt(Constants.KEY_PREFS_RES_DI, preferenceResId);
args.putString(Constants.KEY_PREFS_SCREEN, screenKey);
fragment.setArguments(args);
return fragment;
}
Preference 的fragment ui自定義
Preference 頁面替換 比如文字大小位置等
1 Preferene 里面有一個(gè)屬性 layout 我們可以創(chuàng)建一個(gè) layout 布局。但是限制條件是里面的view的布局必須和原來leanback_preference.xml 里面設(shè)置的id 一樣两芳。
icon 的id android:id="@android:id/icon"
title的id android:id="@android:id/title"
summary的id android:id="@android:id/summary"
2 假如我們的布局不單單這些控件 我們比如 左右鍵箭頭
需要我們自定義Preferenc 了摔寨。
例如
public class PictureModePreference extends Preference {
···
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
mItemView = holder.itemView;
mLeftImg = mItemView.findViewById(R.id.img_arrow_left);
mRightImg = mItemView.findViewById(R.id.img_arrow_right);
mModeText = mItemView.findViewById(android.R.id.title);
}
···
}
3 為什么我們的setting 在右邊呢
LeanbackSettingFragment對(duì)應(yīng)的布局
leanback_settings_fragment.xml
<androidx.leanback.preference.LeanbackSettingsRootView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_dialog_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.leanback.preference.internal.OutlineOnlyWithChildrenFrameLayout
android:id="@+id/settings_preference_fragment_container"
android:layout_width="@dimen/lb_settings_pane_width"
android:layout_height="match_parent"
android:layout_gravity="end"
android:elevation="@dimen/lb_preference_decor_elevation"
android:outlineProvider="bounds" />
</androidx.leanback.preference.LeanbackSettingsRootView>
這個(gè)里面有這個(gè)布局配置的
修改顯示的布局的話可以設(shè)置 LeanbackSettingFragment 的對(duì)應(yīng)的布局
例如 我把這個(gè)改到左邊
我重寫了 onCreateView 方法 指向我自己的布局 但是里面的id 不要變。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.leanback_settings_fragment_start, container, false);
return v;
}
····
// 布局
<androidx.leanback.preference.LeanbackSettingsRootView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_dialog_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings_preference_fragment_container"
android:layout_width="@dimen/lb_settings_pane_width"
android:layout_height="match_parent"
android:layout_gravity="start"
android:elevation="@dimen/lb_preference_decor_elevation"
android:outlineProvider="bounds" />
</androidx.leanback.preference.LeanbackSettingsRootView>
4 帶有頭部的布局怎么改變
-
全局修改
寫一個(gè)和leanback_preference_fragment.xml 一樣的文件修改里面的布局怖辆,布局就跟著變化了是复,他會(huì)優(yōu)先使用我們定義的這個(gè)布局。
右邊的箭頭吧高度改了一下竖螃,上面的布局高度已經(jīng)跟著改變了淑廊。
- 假如是多個(gè)頁面不一致的話
public abstract class LeanbackPreferenceFragment extends BaseLeanbackPreferenceFragment {
···
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.leanback_preference_fragment, container, false);
final ViewGroup innerContainer = (ViewGroup) view.findViewById(R.id.main_frame);
final View innerView = super.onCreateView(inflater, innerContainer, savedInstanceState);
if (innerView != null) {
innerContainer.addView(innerView);
}
return view;
}
···
}
我們可以重寫onCreateView 的方法去修改這個(gè)布局。