【问题标题】:App theme not changing after onSharedPreferenceChanged is called调用 onSharedPreferenceChanged 后应用主题未更改
【发布时间】:2018-08-29 18:34:30
【问题描述】:

创建首选项活动后,我注意到尽管调用了onSharedPreferenceChanged,但在选中我的复选框首选项时,我的主要活动并没有改变主题。有谁知道出了什么问题以及如何解决?

styles.xml

<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <!--<item name="android:windowBackground">@color/colorLight</item>-->
</style>

<style name="MyDarkMaterialTheme" parent="android:Theme.Material">
    <item name="android:windowBackground">@android:color/black</item>
</style>

<style name="MyLightMaterialTheme" parent="android:Theme.Material.Light.DarkActionBar">
    <item name="android:windowBackground">@color/colorLight</item>
</style>

MainActivity 类

public class MainActivity extends Activity {

    boolean themeState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setTheme(R.style.MyDarkMaterialTheme);

        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.action_settings:
                Intent settingsIntent = new Intent(this, SettingsActivity.class);
                startActivity(settingsIntent);
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }


    @Override
    public void onResume(){
        super.onResume();
        loadPreferences();
        displaySettings();
    }

    private void loadPreferences(){
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        themeState = sharedPreferences.getBoolean("pref_pref1", true);
    }

    public void displaySettings() {
        if (themeState) {
            setTheme(R.style.MyDarkMaterialTheme);
            recreate();
        } else {
            setTheme(R.style.MyLightMaterialTheme);
            recreate();
        }
    }
}

SettingsActivity 类

public class SettingsActivity extends Activity {

    boolean themeState;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        if (savedInstanceState == null) {
            Fragment preferenceFragment = new SettingsFragment();
            FragmentTransaction ft = getFragmentManager().beginTransaction();
            ft.add(R.id.pref_container, preferenceFragment);
            ft.commit();
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            final Intent intent = getParentActivityIntent();
            if(intent != null){
                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
            }
            onBackPressed();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    public void onResume(){
        super.onResume();
        loadPreferences();
        displaySettings();
    }

    private void loadPreferences(){
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        themeState = sharedPreferences.getBoolean("pref_pref1", true);
    }

    public void displaySettings() {
        if (themeState) {
            getApplication().setTheme(R.style.MyDarkMaterialTheme);
            recreate();
        } else {
            getApplication().setTheme(R.style.MyLightMaterialTheme);
            recreate();
        }
    }
}

SettingsFragment 类

public class SettingsFragment extends PreferenceFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Load the Preferences from the XML file
        addPreferencesFromResource(R.xml.app_preferences);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        new SharedPreferences.OnSharedPreferenceChangeListener() {
            @Override
            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
                // Settings activity or fragment should restart with changes applied

            }
        };
    }
}

xml/app_preferences

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">

    <CheckBoxPreference
        android:key="pref_pref1"
        android:title="@string/dark_theme"
        android:defaultValue="false"
        android:layout="@layout/preference_multiline"
        />

</PreferenceScreen>

Csongi77的建议

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Load the Preferences from the XML file
        addPreferencesFromResource(R.xml.app_preferences);

        // Find appropriate preference
        CheckBoxPreference mThemePreference =(CheckBoxPreference)getPreferenceManager().findPreference("pref_pref1");
        // we have to set up listener in order for persisting change to new value
        mThemePreference.setOnPreferenceChangeListener(this);
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mThemePreference.getContext());
        Boolean value=sharedPreferences.getBoolean("pref_pref1",true);
        onPreferenceChange(mThemePreference, value);
    }

    // overriding onPreferenceChange - if we return true, the preference will be persisted
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        String preferenceKey = preference.getKey();
        // we have to check the preference type and key, maybe later we have more preferences....
        if(preference instanceof CheckBoxPreference){
            if(preferenceKey.equals("pref_pref1")){
                ((CheckBoxPreference)preference).setChecked((Boolean)newValue);
                // ... do other preference related stuff here - if necessary, for example setSummary, etc...
                getActivity().setTheme(R.style.MyDarkMaterialTheme);
            } else {
                getActivity().setTheme(R.style.MyLightMaterialTheme);
            }
        }
        return true;
    }
}

Logcat

          Process: com.companyname.appname, PID: 4505
          java.lang.NullPointerException: Attempt to invoke interface method 'void com.companyname.appname.SettingsFragment$PreferenceXchangeListener.onXchange(java.lang.Boolean)' on a null object reference
              at com.companyname.appname.SettingsFragment.onPreferenceChange(SettingsFragment.java:57)
              at android.preference.Preference.callChangeListener(Preference.java:928)
              at android.preference.TwoStatePreference.onClick(TwoStatePreference.java:64)
              at android.preference.Preference.performClick(Preference.java:983)
              at android.preference.PreferenceScreen.onItemClick(PreferenceScreen.java:214)
              at android.widget.AdapterView.performItemClick(AdapterView.java:300)
              at android.widget.AbsListView.performItemClick(AbsListView.java:1143)
              at android.widget.AbsListView$PerformClick.run(AbsListView.java:3044)
              at android.widget.AbsListView$3.run(AbsListView.java:3833)
              at android.os.Handler.handleCallback(Handler.java:739)
              at android.os.Handler.dispatchMessage(Handler.java:95)
              at android.os.Looper.loop(Looper.java:135)
              at android.app.ActivityThread.main(ActivityThread.java:5221)
              at java.lang.reflect.Method.invoke(Native Method)
              at java.lang.reflect.Method.invoke(Method.java:372)
              at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
              at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

SettingsActivity 类

public class SettingsActivity extends Activity implements SettingsFragment.PreferenceXchangeListener {
    private static final String TAG = SettingsActivity.class.getSimpleName();

    // declaring initial value for applying appropriate Theme
    private Boolean mCurrentValue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        // Checking which Theme should be used. IMPORTANT: applying Themes MUST called BEFORE super.onCreate() and setContentView!!!
        Log.d(TAG, "onCreate:::: retrieving preferences");
        SharedPreferences mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        mCurrentValue = mSharedPreferences.getBoolean("my_preference",false);
        Log.d(TAG, "onCreate:::: my_preference and mCurrentValue=" + mCurrentValue);
        if(mCurrentValue){
            // we have to use simple setTheme() instead getApplication.setTheme()!!!
            setTheme(R.style.DarkTheme);
            Log.d(TAG, "onCreate:::: setTheme:DarkTheme");
        } else {
            setTheme(R.style.LightTheme);
            Log.d(TAG, "onCreate:::: setTheme:LightTheme");
        }

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        Fragment preferenceFragment = new SettingsFragment();
        getFragmentManager().beginTransaction().add(R.id.preference_container, preferenceFragment).commit();
    }

    // callback method for changing preference. It's called only if "my_preference" has changed
    @Override
    public void onXchange(Boolean value) {
        // if value differs from previous Theme, we recreate Activity
        Log.d(TAG, "onXchange:::: \n has called");
        if (value!=mCurrentValue) {
            Log.d(TAG, "onXchange:::: \n new value!=oldValue");
            mCurrentValue=value;
            recreate();
        }
    }
}

SettingsFragment 类

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {
    private static final String TAG = SettingsFragment.class.getSimpleName();

    // declaring PreferenceXchangeListener
    private PreferenceXchangeListener mPreferenceXchangeListener;

    public SettingsFragment() {
    }

    // declaring PreferenceXchangeListener in order to communicate with Activities
    public interface PreferenceXchangeListener {
        void onXchange(Boolean value);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Load the Preferences from the XML file
        addPreferencesFromResource(R.xml.app_preferences);

        CheckBoxPreference mCheckBoxPreference = (CheckBoxPreference)findPreference("my_preference");
        mCheckBoxPreference.setOnPreferenceChangeListener(this);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // on Attch we assign parent Activity as PreferenceXchangeListener
        try {
            mPreferenceXchangeListener = (PreferenceXchangeListener) context;
        } catch (ClassCastException e) {
            Log.e(TAG, "onAttach::::: PreferenceXchangeListener must be set in parent Activity");
        }
    }

    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        String preferenceKey=preference.getKey();
        // only my_preference is checked in this case. Later you may add another behaviour to another preference change
        if(preferenceKey.equals("my_preference")){
            ((CheckBoxPreference)preference).setChecked((Boolean)newValue);
            // executing parent Activity's callback with the new value
            mPreferenceXchangeListener.onXchange((Boolean)newValue);
            return true;
        }
        // ... check other preferences here
        return false;
    }
}

【问题讨论】:

  • 你在SettingsActivity中实现了PreferenceXchangeListener吗?您是否在 PreferenceFragment 的 onAttach(Context c) 方法中分配了 PreferenceXchangeListener?
  • @Csongi77 我相信是的。请更新我的 Java 类 SettingsActivitySettingsFragment
  • 我不知道是什么导致了这个:/。在我的模拟器上,它完美无瑕(在 Android API26 上)...根据 LogCat 消息,似乎 mPreferenceXchangeListener 未初始化 newValue 为空。请检查这个。也许卸载应用程序可能会有所帮助(如果 SharedPreferences 尚未修改并且新代码尝试读取它但使用新类型......)
  • 我认为问题出在我的模拟器上,因为它运行的是 API 21,而且它也存在一些性能问题。感谢您的帮助。
  • 更新:此解决方案仅适用于 API 23+(Marshmallow 及更高版本)

标签: java android xml android-theme android-preferences


【解决方案1】:

试试这个:

public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //Load the Preferences from the XML file
    addPreferencesFromResource(R.xml.app_preferences);

    // Find appropriate preference
    CheckBoxPreference mThemePreference =(CheckBoxPreference)getPreferenceManager().findPreference("pref_pref1");
    // we have to set up listener in order for persisting change to new value
    mThemePreference.setOnPreferenceChangeListener(this);
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mThemePreference.getContext());
    Boolean value=sharedPreferences.getBoolean("pref_pref1",true);
    onPreferenceChange(mThemePreference, value);
    }

// overriding onPreferenceChange - if we return 'true', the preference will be persisted
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
    String preferenceKey = preference.getKey();
    // we have to check the preference type and key, maybe later we have more preferences....
    if(preference instanceof CheckBoxPreference){
        if(preferenceKey.equals("pref_pref1")){
      // Edited this line *******               
        ((CheckBoxPreference)preference).setChecked((Boolean)newValue);   
            // ... do other preference related stuff here - if necessary, for example setSummary, etc...       
        }
    }
return true;
}

}

简而言之:在您的 PreferenceFragment 中实现 OnPreferenceChange。当您覆盖 onPreferenceChane 时,返回 true。在这种情况下,旧的首选项将被覆盖。 希望它有帮助(如果是,请不要忘记接受我的回答)! 最好的祝福, 铯

P.S:不要忘记在模拟器上卸载应用程序

【讨论】:

  • 尝试您的代码后返回警告和错误:Method invocation 'setOnPreferenceChangeListener' may produce 'java.lang.NullPointerException'Cannot resolve method 'setChecked(java.lang.Boolean)'
  • 我忘记将偏好设置为 CheckBoxPreference。我试过这个版本,它对我有用。但是我使用了 Activity(从 AppCompatActivity 扩展)并且 PreferenceFragment 是 Activity 的静态内部类。
  • 好吧 NullPointerException 部分呢?应该改成什么?
  • Logcat 说什么?扔到哪里去了?
  • 我在 logcat 中没有收到任何错误,但是当我选中或取消选中 CheckBox 时主题不会改变。见代码部分**Csongi77的建议**
【解决方案2】:

好的,这是工作版本:

1) 在 MainActivity.java 的 onCreate 方法中检查当前主题 before 调用 super.onCreate() setContentView() 并将其添加到私有全局布尔变量中(让我们称之为 mTheme)。更多信息:Change Activity's theme programmatically

2) 在 MainActivity.java 的 onStart() 方法中,您应该检查设置是否已更改,因为当您从另一个 Activity 返回时不会调用 onCreate。如果 mTheme!=newSettingValue,调用 recreate()。重要提示:它类似于 onDestroy 方法,因此之前设置的值可能会丢失! https://developer.android.com/reference/android/app/Activity#recreate()

3) 在您的 SettingsFragment 中,您必须使用 update(Boolean value) 方法定义一个接口 (ThemeXchangeListener)。您还必须声明 ThemeXchangeListener mListener 字段。

4) 在 SettingsFragment 的 onAttach(Context context) 方法中将 context 分配给 mListener -> mListener=(ThemeXchangeListener)context;

5) 在SettingsFragment的onPreferenceChange(Preference pref, Object value)中调用mListener.update((Boolean)value);

6) 在 SettingsActivity 中声明用于存储当前主题值的布尔值 (boolean mTheme)。在 onCreate() 加载首选项并为其赋值。 之前 super.onCreate() 和 setContentView() 分配适当的主题。

7) 在 SettingsActivity 中实现 ThemeXchangeListener 并覆盖 update(Boolean value) 方法。如果 value!=mTheme,调用 recreate() 方法。 这将立即更新您的主题。

您可以查看完整的工作代码(使用 cmets):https://github.com/csongi77/UpdateThemeOnPreferenceChange

它在我的 API15(IceCreamSandwich) 模拟器上运行良好。请让我知道它是否有效! 最好的祝福, 铯

【讨论】:

  • 此代码不再有效,因为 PreferenceFragment 现在已弃用
猜你喜欢
  • 2013-06-29
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
  • 2011-03-08
  • 2011-12-06
  • 1970-01-01
  • 2011-09-12
  • 1970-01-01
相关资源
最近更新 更多