Search code examples
androidandroid-fragmentspreferenceactivitypreferencefragmentpreferencescreen

How to force sub PreferenceScreen to be contained in Fragment layout (Android)


I'm quite new to Android's Preference system and I am currently facing a problem.

As advised by Android Guide (http://developer.android.com/guide/topics/ui/settings.html), I use Preference Fragment. Thus, my SettingsActivity contains some stuff (headers like title, tabs, etc.) and the PreferenceFragment below.

The thing is when I click on a preference associated to a "sub" PreferenceScreen, the "new preference screen" does not respect my fragment's layout but instead it fills the entire activity.

Here's an example: let's say I have a PreferenceFragment that invokes addPreferenceFromResource(R.xml.preferences). preferences.xml does contain a "Change password" preference, which is a PreferenceScreen containing 3 TextEditPreference.

preferences.xml

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">   
    <EditTextPreference android:title="@string/pref_one"/>
    <EditTextPreference android:title="@string/pref_two"/>

    <PreferenceScreen android:title="@string/pref_change_password">

        <EditTextPreference android:title="@string/pref_current_pass"
            android:inputType="textPassword"
            android:hint="@string/hint_current_password" />

        <EditTextPreference android:title="@string/pref_new_pass"
            android:inputType="textPassword"
            android:hint="@string/hint_new_password" />

        <EditTextPreference android:title="@string/pref_confirm_new_pass"
            android:inputType="textPassword"
            android:hint="@string/hint_confirm_new_password" />

    </PreferenceScreen>
</PreferenceScreen>

So when I click on the PreferenceScreen, it does this: The entire activity is filled by the preferenceScreen

But I want it to do that: enter image description here

How can I do that? Thank you in advance for your answers!


Solution

  • Okay so, after a deep dive in Android Reference, I found the solution.

    It appears that when a sub PreferenceScreen is clicked, a Dialog containing the new Preference objects is opened, not a new Activity or whatever.

    Thus the solution is to retrieve the dialog and to make it fits into the original PreferenceFragment's layout. To do so, I used the onPreferenceTreeClick callback method to detect if a PreferenceScreen is clicked:

    public class SettingsFragment extends PreferenceFragment {
    
        // onCreate and other stuff...
    
        @Override
        public boolean onPreferenceTreeClick (PreferenceScreen preferenceScreen, 
                                              Preference preference) {
    
            // Initiating Dialog's layout when any sub PreferenceScreen clicked
            if(preference.getClass() == PreferenceScreen.class) {
                // Retrieving the opened Dialog
                Dialog dialog = ((PreferenceScreen) preference).getDialog();
                if(dialog == null) return false;
    
                initDialogLayout(dialog);   // Initiate the dialog's layout
            }
            return true;
        }
    
        private void initDialogLayout(Dialog dialog) {
            View fragmentView = getView();
    
            // Get absolute coordinates of the PreferenceFragment
            int fragmentViewLocation [] = new int[2];
            fragmentView.getLocationOnScreen(fragmentViewLocation);
    
            // Set new dimension and position attributes of the dialog
            WindowManager.LayoutParams wlp = dialog.getWindow().getAttributes();
            wlp.x       = fragmentViewLocation[0]; // 0 for x
            wlp.y       = fragmentViewLocation[1]; // 1 for y
            wlp.width   = fragmentView.getWidth();
            wlp.height  = fragmentView.getHeight();
    
            dialog.getWindow().setAttributes(wlp);
    
            // Set flag so that you can still interact with objects outside the dialog
            dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
        }
    }
    

    And that's it, that made the trick. Please comment if you think there is a better way.