Внутренний PreferenceScreen не открывается с PreferenceFragmentCompat
Мой внутренний PreferenceScreen из PreferenceFragmentCompat не отображается или, кажется, игнорирует события нажатия.
Я создал MyPreferenceFragment
, что extends PreferenceFragmentCompat
public class MyPreferenceFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences);
}
}
то я изменил тему на styles.xml
как
<style name="AppTheme" parent="@style/Theme.AppCompat.Light">
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
</style>
И, наконец, создайте файл preferences.xml
, например
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference android:title="Check Me"/>
<PreferenceScreen android:title="My Screen"> <!-- This is not opening -->
<EditTextPreference android:title="Edit text" />
</PreferenceScreen>
</PreferenceScreen>
В build.gradle
я добавил оба:
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.android.support:preference-v7:23.0.1'
код действия
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
activity_main.xml
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment"
android:name="com.mando.preferenceapp.MyPreferenceFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Тестирование вышеприведенного кода я не могу открыть/войти в экран настроек. Я что-то упускаю? Почему это не работает?
Ответы
Ответ 1
Проведя много часов с попытками, ищите и, к счастью, с некоторой помощью от создателей библиотеки поддержки. Мне удалось заставить его работать.
Шаг 1. Activity
public class MyActivity extends AppCompatActivity implements
PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
// Create the fragment only when the activity is created for the first time.
// ie. not after orientation changes
Fragment fragment = getSupportFragmentManager().findFragmentByTag(MyPreferenceFragment.FRAGMENT_TAG);
if (fragment == null) {
fragment = new MyPreferenceFragment();
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, fragment, MyPreferenceFragment.FRAGMENT_TAG);
ft.commit();
}
}
@Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat,
PreferenceScreen preferenceScreen) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
MyPreferenceFragment fragment = new MyPreferenceFragment();
Bundle args = new Bundle();
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
fragment.setArguments(args);
ft.replace(R.id.fragment_container, fragment, preferenceScreen.getKey());
ft.addToBackStack(preferenceScreen.getKey());
ft.commit();
return true;
}
}
Советы.
- Не добавляйте фрагмент
xml
, у вас будут сбои при изменении ориентации.
- Обработать рекреацию активности/фрагмента добавить в
onCreate
, чтобы избежать потери фрагмента, когда он находится на экране настроек.
- Активность хоста фрагмента должна реализовывать
PreferenceFragmentCompat.OnPreferenceStartScreenCallback
и воссоздавать фрагменты одного и того же экземпляра.
Шаг 2. PreferenceFragment
public class MyPreferenceFragment extends PreferenceFragmentCompat {
public static final String FRAGMENT_TAG = "my_preference_fragment";
public MyPreferenceFragment() {
}
@Override
public void onCreatePreferences(Bundle bundle, String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
Советы.
- Используйте метод
setPreferencesFromResource
и используйте rootKey
для каждого экрана. Таким образом, ваш код будет правильно использован.
- Имейте в виду, что если у вас есть код типа
findPreference
в вашем фрагменте, он должен иметь null
, как если бы вы были на внутренних экранах, это ничего не даст вам.
То, что сейчас отсутствует, это реализация обратной стрелки в панели действий (домашнее действие), но это никогда не работает само по себе; -)
Я также создал демо-приложение, обертывающее весь этот код, вы можете найти его на github.
Ответ 2
Я сделал это несколько иначе, я запускаю новую активность для каждого экрана. Это, кажется, требует меньше хаков: нет необходимости возиться с заменой фрагментов и цветов фона. Вы также получаете анимацию изменения активности в качестве бонуса!
public class PreferencesActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
final static private String KEY = "key";
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.preferences);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);
if (savedInstanceState != null)
return;
Fragment p = new PreferencesFragment();
String key = getIntent().getStringExtra(KEY);
if (key != null) {
Bundle args = new Bundle();
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, key);
p.setArguments(args);
}
getSupportFragmentManager().beginTransaction()
.add(R.id.preferences, p, null)
.commit();
}
@Override public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
Intent intent = new Intent(PreferencesActivity.this, PreferencesActivity.class);
intent.putExtra(KEY, preferenceScreen.getKey());
startActivity(intent);
return true;
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
public static class PreferencesFragment extends PreferenceFragmentCompat implements ... {
private static final String FRAGMENT_DIALOG_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG";
private String key;
@Override public void onCreatePreferences(Bundle bundle, String key) {
setPreferencesFromResource(R.xml.preferences, this.key = key);
}
// this only sets the title of the action bar
@Override public void onActivityCreated(Bundle savedInstanceState) {
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) actionBar.setTitle((key == null) ? "Settings" : findPreference(key).getTitle());
super.onActivityCreated(savedInstanceState);
}
}
}
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="0dp"
android:orientation="vertical"
android:padding="0dp"
android:id="@+id/preferences">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
<!-- preference fragment will be inserted here programmatically -->
</LinearLayout>
Ответ 3
Решение состоит в том, чтобы запустить другой фрагмент того же класса, но с другим корневым ключом. Никаких действий не вовлечено.
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey){
if(getArguments() != null){
String key = getArguments().getString("rootKey");
setPreferencesFromResource(R.xml.preferences, key);
}else{
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
@Override
public void onNavigateToScreen(PreferenceScreen preferenceScreen){
ApplicationPreferencesFragment applicationPreferencesFragment = new ApplicationPreferencesFragment();
Bundle args = new Bundle();
args.putString("rootKey", preferenceScreen.getKey());
applicationPreferencesFragment.setArguments(args);
getFragmentManager()
.beginTransaction()
.replace(getId(), applicationPreferencesFragment)
.addToBackStack(null)
.commit();
}
Ответ 4
Основываясь на решении @squirrel Intent, я сделал так, чтобы он работал. Это требует еще меньше взлома.
Активность:
import android.support.v7.app.AppCompatActivity;
public class SettingsActivity extends AppCompatActivity {
public static final String TARGET_SETTING_PAGE = "target";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SettingsFragment settingsFragment = new SettingsFragment();
Intent intent = getIntent();
if (intent != null) {
String rootKey = intent.getStringExtra(TARGET_SETTING_PAGE);
if (rootKey != null) {
settingsFragment.setArguments(Bundler.single(TARGET_SETTING_PAGE, rootKey));
}
}
getFragmentManager().beginTransaction()
.replace(android.R.id.content, settingsFragment)
.commit();
}
}
Fragment:
import android.support.v14.preference.PreferenceFragment;
public class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle arguments = getArguments();
if (arguments != null && arguments.getString(TARGET_SETTING_PAGE) != null) {
setPreferencesFromResource(R.xml.preferences, arguments.getString(TARGET_SETTING_PAGE));
} else {
addPreferencesFromResource(R.xml.preferences);
}
}
@Override
public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
Intent intent = new Intent(getActivity(), SettingsActivity.class)
.putExtra(TARGET_SETTING_PAGE, preferenceScreen.getKey());
startActivity(intent);
super.onNavigateToScreen(preferenceScreen);
}
}
Печально, что вам нужно так много хаков в библиотеках поддержки appcompat для чего-то, что работает безупречно из коробки в стандартном андроиде.
Ответ 5
Другое решение состоит в том, чтобы самостоятельно отслеживать экраны предпочтений и использовать API PreferenceFragmentCompat
Здесь основное решение. (Он не охватывает все крайние случаи, см. Расширенное решение ниже)
Убедитесь, что у вас есть configChanges = "direction", чтобы предотвратить создание/уничтожение
<activity
android:name=".MyPreferencesActivity"
android:configChanges="orientation" />
В упражнении вы хотите сохранить стек PreferenceScreens и нажимать/выталкивать по мере необходимости
/* track the screens as a Stack */
private Stack<PreferenceScreen> preferenceScreens = new Stack<>();
// ensure your Activity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback
@Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen());
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
return true;
}
@Override
public void onBackPressed() {
if (preferenceScreens.empty()) {
super.onBackPressed();
} else {
prefsFragment.setPreferenceScreen(preferenceScreens.pop());
}
}
Необязательно: В свой фрагмент, который расширяет PreferenceFragmentCompat, добавьте setRetainInstance (true). (Обратите внимание, что без этого он, скорее всего, будет работать, но иногда он может "сломаться". Если вы установите для параметра "Не сохранять активность" значение true, и вы увидите, что он будет собран)
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setRetainInstance(true);
// Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences, rootKey);
...
Это! За исключением того, что если вы хотите охватить крайние случаи...
Расширенное решение (если для параметра "Не сохранять активность" задано значение "Истина", вам необходимо убедиться, что вы можете перестраивать все с сохраненного экземпляра.
Обратите внимание, что принятый ответ на самом деле не сохраняет состояние.
- установите для параметра "Не сохранять активность" значение True
- перейти к вложенному PreferenceScreen
- Нажмите домой и затем вернитесь к приложению
- Он "должен" все еще быть на Nested PreferenceScreen, но на самом деле он находится на корневом
Полное расширенное решение с использованием API PreferenceFragmentCompat и сохранением стека PreferenceScreen
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceScreen;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Stack;
/**
* Class to Show the preference screen with Activity keeping state
* @author Aaron Vargas
*/
public class MyPreferencesActivityStateful extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
private static final String PREFERENCE_SCREENS = "PREFERENCE_SCREENS";
private PrefsFragment prefsFragment;
private Stack<PreferenceScreen> preferenceScreens = new Stack<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Display the fragment as the main content. Re-Use if possible
String tag = PrefsFragment.class.getName();
prefsFragment = (PrefsFragment) getSupportFragmentManager().findFragmentByTag(tag);
if (prefsFragment == null) prefsFragment = new PrefsFragment();
getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
prefsFragment, tag).commit();
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// rebuild preferenceScreen stack
for (String screenKey : Objects.requireNonNull(savedInstanceState.getStringArrayList(PREFERENCE_SCREENS))) {
preferenceScreens.push((PreferenceScreen) prefsFragment.findPreference(screenKey));
}
PreferenceScreen preferenceScreen = preferenceScreens.pop();
if (preferenceScreen != prefsFragment.getPreferenceScreen()) { // optimize if same
prefsFragment.setPreferenceScreen(preferenceScreen);
}
}
@Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat preferenceFragmentCompat, PreferenceScreen preferenceScreen) {
preferenceScreens.push(preferenceFragmentCompat.getPreferenceScreen());
preferenceFragmentCompat.setPreferenceScreen(preferenceScreen);
return true;
}
@Override
public void onBackPressed() {
// account for onRestore not getting called equally to onSave
while (preferenceScreens.contains(prefsFragment.getPreferenceScreen())) {
preferenceScreens.remove(prefsFragment.getPreferenceScreen());
}
if (preferenceScreens.empty()) {
super.onBackPressed();
} else {
prefsFragment.setPreferenceScreen(preferenceScreens.pop());
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
preferenceScreens.push(prefsFragment.getPreferenceScreen());
ArrayList<String> keys = new ArrayList<>(preferenceScreens.size());
for (PreferenceScreen screen : preferenceScreens) {
keys.add(screen.getKey());
}
outState.putStringArrayList(PREFERENCE_SCREENS, keys);
}
public static class PrefsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setRetainInstance(true); // ensure in manifest - android:configChanges="orientation"
// Load the preferences from an XML resource
setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
}
Вы также можете обрабатывать все это в своем фрагменте вместо действия. Вот суть этого https://gist.github.com/aaronvargas/0f210ad8643b512efda4acfd524e1232