LeanBack之preference

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 界面效果


界面1

點(diǎn)擊inner 之后得界面


界面2

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" >

下面是圖片


image.png
  • 注意上面 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è)改到左邊


image.png

我重寫了 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è)布局。


    image.png

右邊的箭頭吧高度改了一下竖螃,上面的布局高度已經(jīng)跟著改變了淑廊。


image.png
  • 假如是多個(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è)布局。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末特咆,一起剝皮案震驚了整個(gè)濱河市季惩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腻格,老刑警劉巖画拾,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異菜职,居然都是意外死亡青抛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門酬核,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜜另,“玉大人适室,你說我怎么就攤上這事〔锨眨” “怎么了亭病?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鹅很,是天一觀的道長(zhǎng)嘶居。 經(jīng)常有香客問我,道長(zhǎng)促煮,這世上最難降的妖魔是什么邮屁? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮菠齿,結(jié)果婚禮上佑吝,老公的妹妹穿的比我還像新娘。我一直安慰自己绳匀,他們只是感情好芋忿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著疾棵,像睡著了一般戈钢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上是尔,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天殉了,我揣著相機(jī)與錄音,去河邊找鬼拟枚。 笑死薪铜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恩溅。 我是一名探鬼主播隔箍,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脚乡!你這毒婦竟也來了蜒滩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤每窖,失蹤者是張志新(化名)和其女友劉穎帮掉,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窒典,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蟆炊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瀑志。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涩搓。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡污秆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出昧甘,到底是詐尸還是另有隱情良拼,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布充边,位于F島的核電站庸推,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏浇冰。R本人自食惡果不足惜贬媒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肘习。 院中可真熱鬧际乘,春花似錦、人聲如沸漂佩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽投蝉。三九已至养葵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間墓拜,已是汗流浹背港柜。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咳榜,地道東北人夏醉。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像涌韩,于是被迫代替她去往敵國(guó)和親畔柔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容