Day29 - Preference

两种创建 PreferenceActivity 的方案

简述:

  • 第一种方案 来自 Android Studio new 出来的 demo.
    activity 和 fragment 布局都来自R.xml.
    监听通过 OnPreferenceChangeListener, 对表单选项单独注册监听
  • 第二种方案 来自 github 上的 Twidere
    PreferenceEXDemo.gif

    首先谷歌官方对于 PreferenceActivity 的建议
    如果您在开发针对 Android 3.0(API 级别 11)及更高版本的应用,则应使用 PreferenceFragment 显示 Preference 对象的列表。您可以将 PreferenceFragment 添加到任何 Activity,而不必使用 PreferenceActivity。
    
    所以用的 activity 是普通的 xml, 用 listview 实现了列表,
    fragment 布局使用第一种方案的 R.xml

先来看第一种

第一种方案

其实完全是 Android Studio 的 Demo,只需知道第二种方案用了第一种方案,也就是官方 Demo 里自带的 R.xml.xxx 的 preference 布局文件, 其余可跳过

  1. 资源下新建 xml 文件夹, 新建布局
    activity的布局:
<preference-headers>
  <header/>
  <header/>
</preference-headers>

fragment 的布局框架:

<PreferenceScreen>
  <PreferenceCategory/>分类标题
  <Preference 条目
    title 条目名
    summary 条目内容/>
  <Preference/>

一个具体的 fragment 布局,添加了 key, 用于监听

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <SwitchPreference android:title="节省流量"
        android:key="save_data"
        android:summary="在使用收费网络时,禁用媒体预览" />

</PreferenceScreen>
  1. 新建Activity(基本)
public class SettingActivity extends PreferenceActivity {
  //内部嵌套Fragment1
  public static class NetWorkPreferenceFragment extends PreferenceFragment{

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.pref_network);
        }
    }
  //内部嵌套Fragment2
  public static class AboutMePreferenceFragment extends PreferenceFragment{
      @Override
      public void onCreate(@Nullable Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          addPreferencesFromResource(R.xml.pref_me);
      }
  }
  //加入布局
  @Override
   public void onBuildHeaders(List<Header> target) {
       super.onBuildHeaders(target);
       loadHeadersFromResource(R.xml.pref_headers, target);
   }

   //加入Fragment
   @Override
    protected boolean isValidFragment(String fragmentName) {
        return PreferenceFragment.class.getName().equals(fragmentName)||
                NetWorkPreferenceFragment.class.getName().equals(fragmentName)||
                AboutMePreferenceFragment.class.getName().equals(fragmentName);
    }
}
  1. 加入对偏好修改的监听
//原registerOnSharedPreferenceChangeListener是弱引用注册,会被回收,所以全局变量
    static Preference.OnPreferenceChangeListener bindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener(){

        //ListPreference 和 RingtonePreference 的 summary 过多,分开处理
        //stolen from AndroidStudio Sample Project
        @Override
        public boolean onPreferenceChange(Preference preference, Object value) {
            String stringValue = value.toString();
            if (preference instanceof ListPreference){
                // For list preferences, look up the correct display value in
                // the preference's 'entries' list.
                ListPreference listPreference = (ListPreference) preference;
                int index = listPreference.findIndexOfValue(stringValue);

                // Set the summary to reflect the new value.
                preference.setSummary(
                        index >= 0
                                ? listPreference.getEntries()[index]
                                : null);
            }else if (preference instanceof RingtonePreference){
                // For ringtone preferences, look up the correct display value
                // using RingtoneManager.
                if (TextUtils.isEmpty(stringValue)) {
                    // Empty values correspond to 'silent' (no ringtone).
                    preference.setSummary(R.string.pref_ringtone_silent);

                } else {
                    Ringtone ringtone = RingtoneManager.getRingtone(
                            preference.getContext(), Uri.parse(stringValue));

                    if (ringtone == null) {
                        // Clear the summary if there was a lookup error.
                        preference.setSummary(null);
                    } else {
                        // Set the summary to reflect the new ringtone display
                        // name.
                        String name = ringtone.getTitle(preference.getContext());
                        preference.setSummary(name);
                    }
                }
            }else {
                // For all other preferences, set the summary to the value's
                // simple string representation.
                preference.setSummary(stringValue);
            }

            return true;
        }
    };
  1. 抽出添加监听操作
private static void bindPreferenceSummaryToValue(Preference preference, Boolean isBoolean){
        //绑定listener
        preference.setOnPreferenceChangeListener(bindPreferenceSummaryToValueListener);
        if (isBoolean){
            bindPreferenceSummaryToValueListener.onPreferenceChange(preference,
                    PreferenceManager
                            .getDefaultSharedPreferences(preference.getContext())
                            .getBoolean(preference.getKey(),false));
        }else {
            bindPreferenceSummaryToValueListener.onPreferenceChange(preference,
                    PreferenceManager
                            .getDefaultSharedPreferences(preference.getContext())
                            .getString(preference.getKey(),""));
        }

    }
  1. 给Fragment里的具体key添加监听
public static class NetWorkPreferenceFragment extends PreferenceFragment{

        @Override
        public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            addPreferencesFromResource(R.xml.pref_network);
            bindPreferenceSummaryToValue(findPreference("test_key"), false);
            bindPreferenceSummaryToValue(findPreference("save_data"), true);
        }
    }

接着来看第二种

第二种方案

首先致敬 Twidere 项目的完全开源.才得以看到源码, 虽然是kotlin的。。= =
以及被我删了部分后转成的java项目Github

  1. 构建左侧 listview 的条目 item 的类
  2. 创建左侧 listview 的条目 item 的 xml, 以及点击效果的 selector
  3. 构建 adapter, 把 xml 和创建的 item 类的实体关联起来
  4. 开始 onCreate, 初始化 listview 和条目 item
  5. 构建右侧详细条目Fragment
  6. 补充 onCreate, intent 中如果没指定具体条目, 打开的是第一个详细条目, 并添加左侧 listview的点击监听
  7. 切记!, style 里补充
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>

否则报错 Must specify preferenceTheme in theme

  1. 监听back, log打印sp里存储的设置参数
  2. 进阶 右侧详细条目还需要二次跳转, 重写 onPreferenceStartFragment(注意v7包还是v14包, 和Fragment里的preference一致就行)
  3. 进阶2 back键时判断是 activity 还是 fragment 跳出

Must specify preferenceTheme in theme

在style里新建

<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>

针对4.4以下的可能还需另外配置

关于SharePreference

getSharedPreferences(name , mode)

mode的选择:

  1. Context.MODE_PRIVATE:
  2. Context.MODE_WORLD_READABLE: API17后不建议使用, 全局可读文件很危险
  3. Context.MODE_MULTI_PROCESS: 不要用来跨进程, 还是用ContentProvider

SharedPreference 的使用 tips参考

  1. 别存大 key 和 value(一口气加载时会卡住)
  2. commit在当前线程别在主线程
  3. 别用来跨进程
  4. 不要存放JSON(特殊符号解析浪费时间)
  5. edit和apply尽量一次搞定
  6. apply还是commit. 异步commit.
  7. 如何命名一个独立的sp:
    getPreferenceManager().setSharedPreferencesName("preference");
    

OnSharedPreferenceChangeListener

  1. OnSharedPreferenceChangeListener 和 OnPreferenceChangeListener比较, 前者是 preference 有变化就收到,后者只针对用 onPreferenceChange 绑定过的 key.参考
  2. OnSharedPreferenceChangeListener 是弱引用, 需要在生命周期里注册监听参考

提交用apply还是commit参考

apply 调用 QueuedWork.add(awaitCommit), 如果任务过多, 等待时间过久, 且开始了onPause, 会导致onPause 因为 QueuedWork.waitToFinish()被apply过久而ANR. 最优方案是开启一个线程去 commit.
且 commit 有返回值, 可以补救

Preference 中 inflate xml 使用 addPreferencesFromResource(redId) 还是 setPreferencesFromResource(redId, rootkey)

其实没有太大的差别, 虽然官方的PreferenceFragment 是在 onCreate 中调用了 addPreferencesFromResource链接, 而PreferenceFragmentCompat的demo中, onCreatePreferences 里用的是 setPreferencesFromResource 链接,
来看 setPreferencesFromResource 的源码

@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   final TypedValue tv = new TypedValue();
   getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
   final int theme = tv.resourceId;
   if (theme == 0) {
       throw new IllegalStateException("Must specify preferenceTheme in theme");
   }
   mStyledContext = new ContextThemeWrapper(getActivity(), theme);
   mPreferenceManager = new PreferenceManager(mStyledContext);
   mPreferenceManager.setOnNavigateToScreenListener(this);
   final Bundle args = getArguments();
   final String rootKey;
   if (args != null) {
       rootKey = getArguments().getString(ARG_PREFERENCE_ROOT);
   } else {
       rootKey = null;
   }
   onCreatePreferences(savedInstanceState, rootKey);
}

可以看到 rootKey 是根据 oncreate 时传进来的 Bundle 设置的

那么问题来了, 怎么传进来了, 再来看谷歌的一个例子LeanbackPreferenceFragment
其中

@Override
public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) {
    final Fragment f = new PrefsFragment();
    final Bundle args = new Bundle(1);
    args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey());
    f.setArguments(args);
    startPreferenceFragment(f);
    return true;
}

通过重写 onPreferenceStartScreen 方法, 自己配置了bundle.

结论: 从 intent 里传进来 PreferenceFragment.ARG_PREFERENCE_ROOT. 不然就重写 onPreferenceStartScreen

LeanbackSettingsFragment

参考
github-Twidere项目
OnSharedPreferenceChangeListeners设计-弱引用
SharedPreference使用tips

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335