web-dev-qa-db-ja.com

内部PreferenceScreenがPreferenceFragmentCompatで開かない

PreferenceFragmentCompatの内部PreferenceScreenが表示されないか、タップイベントを無視しているようです。

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

上記のコードをテストすると、設定画面を開いたり、取得したりできません。何か不足していますか?なぜこれが機能しないのですか?

23
madlymad

何時間も試行錯誤した後、サポートライブラリの作成者からの支援を受けて、ありがたいことに検索しました。私はなんとかそれを機能させることができました。

ステップ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 で見つけることができるこのすべてのコードをラップするデモアプリも作成しました。

37
madlymad

解決策は、同じクラスの別のルートキーで別のフラグメントを開始することです。アクティビティアクションは含まれていません。

@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();
}
8
Quireg

少し違った方法で行いました。画面ごとに新しいアクティビティを起動しています。これにより、ハッキングが少なくなるようです。フラグメントと背景色の入れ替えをいじる必要はありません。おまけとして、活動変化アニメーションも!

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>
5
squirrel

別の解決策は、設定画面を自分で追跡し、PreferenceFragmentCompat APIを使用することです

これが基本的な解決策です。 (すべてのEdgeケースをカバーしているわけではありません。以下の高度なソリューションを参照してください)

作成/破棄を防ぐためにconfigChanges = "orientation"があることを確認してください

    <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に設定した場合、savedInstanceStateからすべてを再構築できることを確認する必要があります)

受け入れられた回答は実際には状態を保存しないことに注意してください。

  1. 「アクティビティを保持しない」をTrueに設定します
  2. ネストされたPreferenceScreenに移動する
  3. ホームを押してからアプリに戻る
  4. ネストされたPreferenceScreen上にあるはずですが、実際にはルート上にあります

PreferenceFragmentCompat APIを使用し、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

1
aaronvargas

@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();
    }
}

断片:

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);
    }
}

標準のAndroidでそのまますぐに機能するものをサポートするappcompatライブラリーに多くのハックが必要なのは残念です。

0
DSchmidt

ナビゲーションコンポーネント+ androidx.appcomatを使用した代替方法: https://stackoverflow.com/a/59732509/5437789

これにより、戻るボタンを押しても、バックスタックが失われ、メインページの設定に戻ることはありません。

0
TwiXter

ナビゲーションコンポーネントAndroid Jetpack )とKotlinを使用すると、非常に簡単です。

class PrefsFragment : PreferenceFragmentCompat() {
    private val args: PrefsFragmentArgs by navArgs()

    override fun onCreatePreferences(state: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.prefs, args.rootKey)
    }

    override fun onNavigateToScreen(preferenceScreen: PreferenceScreen?) {
        findNavController().navigate(
            PrefsFragmentDirections.changeRoot(preferenceScreen!!.key)
        )
    }
}

enter image description here

enter image description here

0
user924