Toolbar is hidden in nested PreferenceScreen

2019-03-11 02:00发布

I use PreferenceFragment in ActionBarActivity from support-v7 library.
In the Activity I have Toolbar. Everything goes okay, until I open a nested PreferenceScreen.
In the opened screen the Toolbar is hidden.

Maybe somebody know a workaround for this issue?

Preferences xml-file:

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

    <PreferenceCategory android:title="Main category" >

        <EditTextPreference
            android:defaultValue="defaultValue"
            android:key="key_global_setting"
            android:title="Global title" />        

    </PreferenceCategory>

    <PreferenceCategory android:title="Nested screens" >        

        <PreferenceScreen
            android:persistent="false"
            android:title="@string/settings_facility_title" >

        <CheckBoxPreference
            android:defaultValue="false"
            android:key="nested_screen_1_1"
            android:title="Nested screen 1.1 check box" />

        <CheckBoxPreference
            android:defaultValue="true"
            android:key="nested_screen_1_2"
            android:title="Nested screen 1.2 check box" />
        </PreferenceScreen>

        <PreferenceScreen
            android:persistent="false"
            android:title="@string/settings_menu_screen_title" >

         <CheckBoxPreference
            android:defaultValue="true"
            android:key="nested_screen2"
            android:title="Nested screen 2 check box" />
        </PreferenceScreen>        

    </PreferenceCategory>    

</PreferenceScreen> 

Activity layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"    
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SettingsScreen" >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        style="@style/Toolbar" />

    <FrameLayout
        android:id="@+id/contentSettings"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

4条回答
forever°为你锁心
2楼-- · 2019-03-11 02:15

Here comes my solution, which is inspired by the original answer but not that complicated. Maybe it'll help someone...

layout/settings.xml:

    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <include
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentTop="true"
            layout="@layout/toolbar" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/content"
            android:layout_below="@+id/toolbar"/>

    </RelativeLayout>

Classes:

public class SettingsActivity extends ActionBarActivity {
  @Override
  protected void onCreate( Bundle savedInstanceState ) {
    setContentView( R.layout.settings );
    super.onCreate( savedInstanceState ); 
    initializeSupportActionBar();
    getFragmentManager().beginTransaction().replace( R.id.content, new MainFragment() ).commit();
  }

  @Override
  public void onBackPressed() {
    if( !getFragmentManager().popBackStackImmediate() ) super.onBackPressed();
  }
}

public class MainFragment extends PreferenceFragment {

  public MainFragment() {}

  @Override
  public void onCreate( Bundle savedInstanceState ) {
    super.onCreate( savedInstanceState );
    addPreferencesFromResource( R.xml.pref_main );
    // "nested" is the <Preference android:key="nested" android:persistent="false"/>`
    findPreference( "nested" ).setOnPreferenceClickListener( new OnPreferenceClickListener() {
      @Override public boolean onPreferenceClick( Preference preference ) {
        getFragmentManager().beginTransaction().replace( R.id.content, new NestedFragment() ).addToBackStack( NestedFragment.class.getSimpleName() ).commit();
        return true;
      }
    } );
  }

public class NestedFragment extends PreferenceFragment {
  ...
}

I tested it on 4.3 and 5.0.2 and no limitation on nesting levels applies

查看更多
啃猪蹄的小仙女
3楼-- · 2019-03-11 02:17

I found the solution on my own. I used a small work-around of all this nested PreferenceScreen's. I simply made a separation to different xml-preference files, created an additional Fragment which extends PreferenceFragment and there I show an appropriate nested preference screen.
Maybe somebody would found this useful.

Github sources link.

Some code examples below:

main_preferences.xml

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

    <PreferenceCategory android:title="Main category" >

        <EditTextPreference
            android:defaultValue="defaultValue"
            android:key="key_global_setting"
            android:title="Global title" />

    </PreferenceCategory>

    <PreferenceCategory android:title="Nested screens" >

        <Preference
            android:key="NESTED_KEY1"
            android:persistent="false"
            android:title="Nested screen #1" />

        <Preference
            android:key="NESTED_KEY2"
            android:persistent="false"
            android:title="Nested screen #2" />

    </PreferenceCategory>

</PreferenceScreen> 

nested_screen1_preferences.xml

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

    <CheckBoxPreference
        android:defaultValue="false"
        android:key="nested_screen_1_1"
        android:title="Nested screen 1.1 check box" />

    <CheckBoxPreference
        android:defaultValue="true"
        android:key="nested_screen_1_2"
        android:title="Nested screen 1.2 check box" />
</PreferenceScreen>

nested_screen2_preferences.xml

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

    <CheckBoxPreference
        android:defaultValue="true"
        android:key="nested_screen2"
        android:title="Nested screen 2 check box" />
</PreferenceScreen>

SettingsActivity.java

public class SettingsActivity extends ActionBarActivity implements MyPreferenceFragment.Callback {

    private static final String TAG_NESTED = "TAG_NESTED";

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

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        if (toolbar != null) {
            setSupportActionBar(toolbar);
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }

        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.contentSettings, new MyPreferenceFragment())
                    .commit();
        }
    }

    @Override
    public void onBackPressed() {
        // this if statement is necessary to navigate through nested and main fragments
        if (getFragmentManager().getBackStackEntryCount() == 0) {
            super.onBackPressed();
        } else {
            getFragmentManager().popBackStack();
        }
    }

    @Override
    public void onNestedPreferenceSelected(int key) {
        getFragmentManager().beginTransaction().replace(R.id.contentSettings, NestedPreferenceFragment.newInstance(key), TAG_NESTED).addToBackStack(TAG_NESTED).commit();
    }    
}

MyPreferenceFragment.java

// The main preference fragment class
public class MyPreferenceFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener {

    private Callback mCallback;

    private static final String KEY_1 = "NESTED_KEY1";
    private static final String KEY_2 = "NESTED_KEY2";

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);

        if (activity instanceof Callback) {
            mCallback = (Callback) activity;
        } else {
            throw new IllegalStateException("Owner must implement Callback interface");
        }
    }
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Load the preferences from an XML resource
        addPreferencesFromResource(R.xml.main_preferences);

        // add listeners for non-default actions
        Preference preference = findPreference(KEY_1);
        preference.setOnPreferenceClickListener(this);

        preference = findPreference(KEY_2);
        preference.setOnPreferenceClickListener(this);
    }

    @Override
    public boolean onPreferenceClick(Preference preference) {
        // here you should use the same keys as you used in the xml-file
        if (preference.getKey().equals(KEY_1)) {
            mCallback.onNestedPreferenceSelected(NestedPreferenceFragment.NESTED_SCREEN_1_KEY);
        }

        if (preference.getKey().equals(KEY_2)) {
            mCallback.onNestedPreferenceSelected(NestedPreferenceFragment.NESTED_SCREEN_2_KEY);
        }

        return false;
    }

    public interface Callback {
        public void onNestedPreferenceSelected(int key);
    }
}

NestedPreferencesFragment.java

public class NestedPreferenceFragment extends PreferenceFragment {

    public static final int NESTED_SCREEN_1_KEY = 1;
    public static final int NESTED_SCREEN_2_KEY = 2;

    private static final String TAG_KEY = "NESTED_KEY";

    public static NestedPreferenceFragment newInstance(int key) {
        NestedPreferenceFragment fragment = new NestedPreferenceFragment();
        // supply arguments to bundle.
        Bundle args = new Bundle();
        args.putInt(TAG_KEY, key);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        checkPreferenceResource();
    }

    private void checkPreferenceResource() {
        int key = getArguments().getInt(TAG_KEY);
        // Load the preferences from an XML resource
        switch (key) {
            case NESTED_SCREEN_1_KEY:
                addPreferencesFromResource(R.xml.nested_screen1_preferences);
                break;

            case NESTED_SCREEN_2_KEY:
                addPreferencesFromResource(R.xml.nested_screen2_preferences);
                break;

            default:
                break;
        }
    }

}
查看更多
霸刀☆藐视天下
4楼-- · 2019-03-11 02:18

As the issue comes from the part that you are still in the same activity/fragment and the nested pref screen is just a dialog you can do the following:

  • You can set preference click listener
  • Get the root view from the dialog: (PreferenceScreen)preference).getDialog().getWindow() .getDecorView().getRootView());

  • Recursively search until find a stub view (there is one, unfortunately I do not know the android.R.id.xxxxx) and set what layout you need as title which will look like the toolbar(You can inflate toolbar):

    private Toolbar toolbar;
    
        public void findViewStub(ViewGroup viewGroup) {
        int childCount = viewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = viewGroup.getChildAt(i);
            if( childView instanceof ViewStub){
                ((ViewStub)childView).setLayoutResource(R.layout.your_title_layout);
               toolbar =  ((ViewStub)childView).inflate();
            }
            if (childView instanceof ViewGroup) {
                findViewStub((ViewGroup) childView);
            }
        }
    
      }
    
    toolbar.setNavigationIcon();
    toolbar.setNavigationOnClickListener();
    toolbar.setTitle();
    
  • In the layout you can put only a toolbar. And set the back icon. Register for click on it and having reference to the fragment, on click you can dismiss the dialog. You have set title and etc.

查看更多
够拽才男人
5楼-- · 2019-03-11 02:25

In my solution you only need one AppCompatActivity and one PreferenceFragement, but several XML files, each having only one level of PreferenceScreens.

XML file list

  • top level PreferenceScreen
  • second level PreferenceScreen 0
  • second level PreferenceScreen 1
  • second level PreferenceScreen 2
  • ...

This code is for one sub-level (for simplicity and to get the idea), but you can easily extend it to have arbitrary sub-levels of PreferenceScreens.

SettingsFragment.java

public class SettingsFragment extends PreferenceFragment
{
    private int xmlId;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        xmlId = R.xml.preferences;
        addPreferencesFromResource(xmlId);
    }

    public void changePrefScreen(int xmlId, int titleId)
    {
        this.xmlId = xmlId;
        ((AppCompatActivity)getActivity()).getSupportActionBar().setTitle(getActivity().getResources().getString(titleId));
        getPreferenceScreen().removeAll();
        addPreferencesFromResource(xmlId);
    }

    // will be called by SettingsActivity (Host Activity)
    public void onUpButton()
    {
        if(xmlId == R.xml.preferences) // in top-level
        {
            // Switch to MainActivity
            Intent intent = new Intent(getActivity(), MainActivity.class);
            startActivity(intent);
        }
        else // in sub-level
        {
            changePrefScreen(R.xml.preferences, R.string.settings);
        }
    }

    @Override
    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)
    {
        String key = preference.getKey();

        //
        // Top level PreferenceScreen
        //
        if(key.equals("top_key_0"))
        {
            changePrefScreen(R.xml.download_preference_screen, R.string.download_database); // descend into second level
        }

        // ...

        //
        // Second level PreferenceScreens
        //
        if (key.equals("second_level_key_0"))
        {
           // do something...
        }

        // ...
     }

SettingsActivity.java

public class SettingsActivity extends AppCompatActivity
{
    SettingsFragment settingsFragment;

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

        settingsFragment = new SettingsFragment();

        // Display the fragment as the main content.
        getFragmentManager().beginTransaction()
                .replace(android.R.id.content, settingsFragment)
                .commit();
    }

    //
    // Handle what happens on up button
    //
    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
    {
        switch (item.getItemId()) 
        {
            case android.R.id.home:
                settingsFragment.onUpButton();
                return true;
        }
        return true;
    }

    // ...
}

Technically it should work for all Android versions for which the PreferenceFragment is available.

查看更多
登录 后发表回答