36 votes

Problèmes avec les écrans de préférences à double volet

Problème
Faire tourner un appareil à partir d'un portrait à un volet PreferenceScreen à un paysage à deux volets PreferenceScreen Le paysage ne s'affiche alors que sur un seul volet. Cela ne se produit PAS lors de l'affichage de l'écran des en-têtes.

Configuration
C'est pour ICS et plus seulement. J'ai un PreferenceActivity qui charge preference-headers . Chaque en-tête est lié à un Fragment qui, à son tour, charge un PreferenceScreen . C'est assez courant.

Détails
Tout a bien fonctionné jusqu'à ce que je remarque qu'Android ne passe automatiquement à l'apparence à deux volets que pour certains écrans. Après quelques recherches, j'ai appris d'un Poste de commandement qu'Android ne le fera que pour le sw720dp. C'est un peu un gâchis si vous voulez mon avis puisque de nombreux appareils ont beaucoup de place pour deux panneaux. J'ai donc remplacé le onIsMultiPane() pour qu'elle renvoie vrai pour les w600dp et plus. Ça a marché comme sur des roulettes. ....kinda.

Avec un appareil qui affiche un seul panneau en mode portrait et deux panneaux en mode paysage, l'affichage des en-têtes en mode portrait et la rotation en mode paysage fonctionnent bien. Cependant, si l'on sélectionne un en-tête et que l'on charge son écran suivant en mode portrait, puis que l'on pivote en mode paysage, l'appareil reste à un seul volet au lieu de repasser en double volet. Si vous revenez ensuite à l'écran des en-têtes, vous retrouverez un aspect à double volet, mais sans présélectionner d'en-tête. Par conséquent, le volet détaillé reste vide.

Est-ce un comportement voulu ? Comment le contourner ? J'ai essayé de remplacer onIsHidingHeaders() mais cela a juste provoqué l'affichage d'un écran vide.

Code
Activité de préférence :

public class SettingsActivity extends PreferenceActivity {
@Override
public void onBuildHeaders(List<Header> target) {
    super.onBuildHeaders(target);
    loadHeadersFromResource(R.xml.preference, target);
}

@Override
public boolean onIsMultiPane() {
    return getResources().getBoolean(R.bool.pref_prefer_dual_pane);
}
}

Un en-tête de préférence Frag :

public class ExpansionsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.pref_expansions);
}

public static ExpansionsFragment newInstance() {
    ExpansionsFragment frag = new ExpansionsFragment();

    return frag;
}
}

2voto

Jay Soyer Points 2136

Problème résolu
Avec la popularité de cette question, j'ai décidé de revenir sur ce problème et de voir si je pouvais trouver une solution... et je l'ai fait. J'ai trouvé un petit contournement qui résout le problème de l'affichage d'un seul panneau au lieu de deux et qui garantit qu'un en-tête est toujours présélectionné en mode double panneau.

Si vous ne voulez pas d'explication, vous pouvez passer directement au code. Si vous ne vous souciez pas d'ICS, une grande partie du code de suivi des en-têtes peut être supprimée car JB a ajouté un getter pour la liste du tableau des en-têtes.

Problème de double vitrage
Lors de l'affichage de la liste d'en-tête des préférences en mode volet unique ou en mode double volet, une seule PreferenceActivity est créée et il s'agit de la même activité dans les deux cas. Par conséquent, il n'y a jamais de problème pour gérer les rotations d'écran qui changent le mode de volet.

Cependant, en mode volet unique, lorsque vous cliquez sur un en-tête, le fragment correspondant est attaché à une NOUVELLE PreferenceActivity. Ce nouveau fragment contenant PreferenceActivity n'invoque jamais onBuildHeaders() . Et pourquoi le ferait-il ? Il n'a pas besoin de les afficher. Ce mensonge est le problème.

Lorsque l'on fait pivoter ce fragment en mode double volet, il n'y a pas de liste d'en-tête à afficher et il continue donc à n'afficher que le fragment. Même si elle affichait la liste des en-têtes, vous auriez des problèmes de backstack car vous auriez maintenant deux copies de la PreferenceActivity affichant les en-têtes. Si vous cliquez sur un nombre suffisant d'en-têtes, vous obtiendrez une longue pile d'activités dans laquelle l'utilisateur devra naviguer. Par conséquent, la réponse est simple. Il suffit d'aller sur finish() l'activité. Il chargera ensuite l'activité PreferenceActivity originale qui contient la liste d'en-tête et affichera correctement le mode double volet.

Sélection automatique de l'en-tête
Le problème suivant à résoudre était que le passage du mode simple au mode double volet avec le nouveau correctif ne permettait pas de sélectionner automatiquement un en-tête. Vous vous retrouviez avec une liste d'en-têtes et aucun fragment de détails chargé. Cette correction n'est pas aussi simple. Fondamentalement, vous devez juste garder la trace de l'en-tête qui a été cliqué en dernier et s'assurer que pendant la création de PreferenceActivity... un en-tête est toujours sélectionné.

Cela finit par être un peu ennuyeux dans ICS puisque l'API n'expose pas un getter pour la liste d'en-têtes suivie en interne. Android fait déjà persister cette liste et vous pourriez techniquement la récupérer en utilisant la même clé de chaîne interne stockée en privé, mais c'est un mauvais choix de conception. Au lieu de cela, je suggère de la faire persister manuellement vous-même.

Si vous ne vous souciez pas de l'ICS, alors vous pouvez simplement utiliser l'option getHeaders() exposée dans JB et ne pas se préoccuper de cette histoire de sauvegarde/restauration d'état.

Code

public class SettingsActivity extends PreferenceActivity {
private static final String STATE_CUR_HEADER_POS = "Current Position";
private static final String STATE_HEADERS_LIST   = "Headers List";

private int mCurPos = AdapterView.INVALID_POSITION;  //Manually track selected header position for dual pane mode
private ArrayList<Header> mHeaders;  //Manually track headers so we can select one. Required to support ICS.  Otherwise JB exposes a getter instead.

@Override
public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.preference, target);
    mHeaders = (ArrayList<Header>) target;  //Grab a ref of the headers list
}

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

    //This is the only code required for ensuring a dual pane mode shows after rotation of a single paned preference screen
    if (onIsMultiPane() && onIsHidingHeaders()) {
        finish();
    }
}

@Override
public boolean onIsMultiPane() {
    //Override this if you want dual pane to show up on smaller screens
    return getResources().getBoolean(R.bool.pref_prefer_dual_pane);
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);

    //Intercept a header click event to record its position.
    mCurPos = position;
}

@Override
protected void onRestoreInstanceState(Bundle state) {
    super.onRestoreInstanceState(state);

    //Retrieve our saved header list and last clicked position and ensure we switch to the proper header.
    mHeaders = state.getParcelableArrayList(STATE_HEADERS_LIST);
    mCurPos = state.getInt(STATE_CUR_HEADER_POS);
    if (mHeaders != null) {
        if (mCurPos != AdapterView.INVALID_POSITION) {
            switchToHeader(mHeaders.get(mCurPos));
        } else {
            switchToHeader(onGetInitialHeader());
        }
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    //Persist our list and last clicked position
    if (mHeaders != null && mHeaders.size() > 0) {
        outState.putInt(STATE_CUR_HEADER_POS, mCurPos);
        outState.putParcelableArrayList(STATE_HEADERS_LIST, mHeaders);
    }
}
}

0voto

Mark Larter Points 173

L'idée principale qui sous-tend le code ci-dessous provient de l'article du blog Commonsware dont le lien figure dans la question, et elle semble donc pertinente. J'ai dû spécifiquement étendre le concept pour traiter un problème de changement d'orientation qui ressemble beaucoup à celui de la question, alors j'espère que cela vous donnera un début de réponse.

La classe Settings ne devrait pas avoir d'incidence sur la question de l'orientation, mais nous l'avons quand même incluse pour être clair.

Selon mon commentaire, vérifiez si le checkNeedsResource appeler onCreate ne vous aidera pas du tout :

public class SettingsActivity
extends 
    PreferenceActivity
{

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

    // Show settings without headers for single pane or pre-Honeycomb. Make sure to check the
    // single pane or pre-Honeycomb condition again after orientation change.
    if (checkNeedsResource()) {
        MyApp app = (MyApp)getApplication();
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
        Settings settings = new Settings();
        addPreferencesFromResource(R.xml.prefs_api);
        settings.setupPreference(findPreference(MyApp.KEY_USERNAME), prefs.getString(MyApp.KEY_USERNAME, null), true);
        settings.setupPreference(findPreference(MyApp.KEY_API_URL_ROOT), prefs.getString(MyApp.KEY_API_URL_ROOT, null), true);
        if (this.isHoneycomb) {
            // Do not delete this. We may yet have settings that only apply to Honeycomb or higher.
            //addPreferencesFromResource(R.xml.prefs_general);
        }
        addPreferencesFromResource(R.xml.prefs_about);
        settings.setupPreference(findPreference(MyApp.KEY_VERSION_NAME), app.getVersionName());
    }
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void onBuildHeaders(List<Header> target) {
    super.onBuildHeaders(target);

    // This check will enable showing settings without headers for single pane or pre-Honeycomb. 
    if (!checkNeedsResource()) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }
}

private boolean checkNeedsResource() {
    // This check will enable showing settings without headers for single pane or pre-Honeycomb. 
    return (!this.isHoneycomb || onIsHidingHeaders() || !onIsMultiPane());
}

private boolean isHoneycomb = (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB);

}

public class Settings {

public Settings() {
}

public void setupPreference(Preference pref, String summary, boolean setChangeListener) {
    if (pref != null) {
        if (summary != null) {
            pref.setSummary(summary);
        }

        pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {

            @Override
            public boolean onPreferenceChange(Preference pref, Object newValue) {
                pref.setSummary(newValue.toString());
                return true;
            }

        });
    }
}

public void setupPreference(Preference pref, String summary) {
    setupPreference(pref, summary, false);
}

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X