Création d'un écran de préférences avec la barre d'outils de support (v21)

116

J'avais du mal à utiliser la nouvelle barre d'outils Material Design dans la bibliothèque de support sur un écran de préférences.

J'ai un fichier settings.xml comme ci-dessous:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory
        android:title="@string/AddingItems"
        android:key="pref_key_storage_settings">

        <ListPreference
            android:key="pref_key_new_items"
            android:title="@string/LocationOfNewItems"
            android:summary="@string/LocationOfNewItemsSummary"
            android:entries="@array/new_items_entry"
            android:entryValues="@array/new_item_entry_value"
            android:defaultValue="1"/>

    </PreferenceCategory>
</PreferenceScreen>

Les chaînes sont définies ailleurs.

James Cross
la source
stackoverflow.com/a/27455363/2247612 Cette réponse a une solution parfaite pour la bibliothèque de support
harishannam

Réponses:

110

Veuillez trouver le dépôt GitHub: ici


Un peu tard à la fête, mais c'est ma solution que j'utilise pour contourner le fait de continuer à utiliser PreferenceActivity:

settings_toolbar.xml :

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="?attr/actionBarSize"
    app:navigationContentDescription="@string/abc_action_bar_up_description"
    android:background="?attr/colorPrimary"
    app:navigationIcon="?attr/homeAsUpIndicator"
    app:title="@string/action_settings"
    />

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

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

        LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
        Toolbar bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
        root.addView(bar, 0); // insert at top
        bar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }

}

Result :

exemple


MISE À JOUR (compatibilité pain d'épice):

Selon les commentaires, Gingerbread Devices renvoie NullPointerException sur cette ligne:

LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();

RÉPARER:

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        Toolbar bar;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();
            bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
            root.addView(bar, 0); // insert at top
        } else {
            ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
            ListView content = (ListView) root.getChildAt(0);

            root.removeAllViews();

            bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
            

            int height;
            TypedValue tv = new TypedValue();
            if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
                height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
            }else{
                height = bar.getHeight();
            }

            content.setPadding(0, height, 0, 0);

            root.addView(content);
            root.addView(bar);
        }

        bar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

Tout problème avec ce qui précède, faites-le moi savoir!


MISE À JOUR 2: CONTOURNEMENT DE TEINTE

Comme indiqué dans de nombreuses notes de développement, PreferenceActivityne prend pas en charge la teinte des éléments, mais en utilisant quelques classes internes, vous POUVEZ y parvenir. C'est jusqu'à ce que ces classes soient supprimées. (Fonctionne avec le support appCompat-v7 v21.0.3).

Ajoutez les importations suivantes:

import android.support.v7.internal.widget.TintCheckBox;
import android.support.v7.internal.widget.TintCheckedTextView;
import android.support.v7.internal.widget.TintEditText;
import android.support.v7.internal.widget.TintRadioButton;
import android.support.v7.internal.widget.TintSpinner;

Remplacez ensuite la onCreateViewméthode:

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    // Allow super to try and create a view first
    final View result = super.onCreateView(name, context, attrs);
    if (result != null) {
        return result;
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        // If we're running pre-L, we need to 'inject' our tint aware Views in place of the
        // standard framework versions
        switch (name) {
            case "EditText":
                return new TintEditText(this, attrs);
            case "Spinner":
                return new TintSpinner(this, attrs);
            case "CheckBox":
                return new TintCheckBox(this, attrs);
            case "RadioButton":
                return new TintRadioButton(this, attrs);
            case "CheckedTextView":
                return new TintCheckedTextView(this, attrs);
        }
    }

    return null;
}

Result:

exemple 2


AppCompat 22.1

AppCompat 22.1 a introduit de nouveaux éléments teintés, ce qui signifie qu'il n'est plus nécessaire d'utiliser les classes internes pour obtenir le même effet que la dernière mise à jour. Au lieu de cela, suivez ceci (toujours prioritaire onCreateView):

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    // Allow super to try and create a view first
    final View result = super.onCreateView(name, context, attrs);
    if (result != null) {
        return result;
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        // If we're running pre-L, we need to 'inject' our tint aware Views in place of the
        // standard framework versions
        switch (name) {
            case "EditText":
                return new AppCompatEditText(this, attrs);
            case "Spinner":
                return new AppCompatSpinner(this, attrs);
            case "CheckBox":
                return new AppCompatCheckBox(this, attrs);
            case "RadioButton":
                return new AppCompatRadioButton(this, attrs);
            case "CheckedTextView":
                return new AppCompatCheckedTextView(this, attrs);
        }
    }

    return null;
}

ÉCRANS DE PRÉFÉRENCE EMBARQUÉS

Beaucoup de gens rencontrent des problèmes pour inclure la barre d'outils dans un imbriqué, <PreferenceScreen />cependant, j'ai trouvé une solution !! - Après de nombreux essais et erreurs!

Ajoutez ce qui suit à votre SettingsActivity:

@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
    super.onPreferenceTreeClick(preferenceScreen, preference);

    // If the user has clicked on a preference screen, set up the screen
    if (preference instanceof PreferenceScreen) {
        setUpNestedScreen((PreferenceScreen) preference);
    }

    return false;
}

public void setUpNestedScreen(PreferenceScreen preferenceScreen) {
    final Dialog dialog = preferenceScreen.getDialog();

    Toolbar bar;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
        LinearLayout root = (LinearLayout) dialog.findViewById(android.R.id.list).getParent();
        bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);
        root.addView(bar, 0); // insert at top
    } else {
        ViewGroup root = (ViewGroup) dialog.findViewById(android.R.id.content);
        ListView content = (ListView) root.getChildAt(0);

        root.removeAllViews();

        bar = (Toolbar) LayoutInflater.from(this).inflate(R.layout.settings_toolbar, root, false);

        int height;
        TypedValue tv = new TypedValue();
        if (getTheme().resolveAttribute(R.attr.actionBarSize, tv, true)) {
            height = TypedValue.complexToDimensionPixelSize(tv.data, getResources().getDisplayMetrics());
        }else{
            height = bar.getHeight();
        }

        content.setPadding(0, height, 0, 0);

        root.addView(content);
        root.addView(bar);
    }

    bar.setTitle(preferenceScreen.getTitle());

    bar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            dialog.dismiss();
        }
    });
}

La raison pour laquelle cela PreferenceScreenest si pénible est parce qu'ils sont basés comme une boîte de dialogue wrapper, nous devons donc capturer la disposition de la boîte de dialogue pour y ajouter la barre d'outils.


Ombre de la barre d'outils

De par sa conception, l'importation de Toolbarne permet pas d'élévation et d'ombrage dans les appareils antérieurs à la v21, donc si vous souhaitez avoir une élévation sur votre, Toolbarvous devez l'envelopper dans un AppBarLayout:

settings_toolbar.xml :

<android.support.design.widget.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

   <android.support.v7.widget.Toolbar
       .../>

</android.support.design.widget.AppBarLayout>

Sans oublier d'ajouter l'ajout de la bibliothèque Design Support en tant que dépendance dans le build.gradlefichier:

compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'

Android 6.0

J'ai étudié le problème de chevauchement signalé et je ne peux pas reproduire le problème.

Le code complet utilisé comme ci-dessus produit les éléments suivants:

entrez la description de l'image ici

S'il me manque quelque chose, veuillez me le faire savoir via ce dépôt et j'enquêterai .

David Passmore
la source
il donne une exception nullpointer dans gingebread, la racine est nulle .. toute solution?
andQlimax
votre solution fonctionne très bien. mais il y a un problème avec cette approche, en fait sans étendre ActionBarActivity qui est obligatoire (de la documentation) pour obtenir le thème matériel sur <5.0, colorAccent (juste pour faire un exemple) n'est pas appliqué aux cases à cocher dans les périphériques <5.0. Cela semble vraiment pénible ... je dois peut-être supprimer l'activité de préférence et utiliser une disposition linéaire pour simuler un écran de préférence, sinon je ne vois aucun moyen d'utiliser le thème matériel dans les appareils du niveau 8 à 21 de l'API. Le fragment de préférence est "only"> 11 :(
andQlimax
1
@andQlimax J'ai mis à jour ma réponse avec une solution pour le problème de la teinture
David Passmore
3
@DavidPassmore pour moi, la liste de préférences se chevauche sur la barre d'outils
Shashank Srivastava
1
@ShashankSrivastava Cela se reflète si vous utilisez Android 6, je travaille sur une solution pour cela. Merci pour la mise à jour.
David Passmore
107

Vous pouvez utiliser a PreferenceFragment, comme alternative à PreferenceActivity. Alors, voici l' Activityexemple d' emballage :

public class MyPreferenceActivity extends ActionBarActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pref_with_actionbar);

        android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(uk.japplications.jcommon.R.id.toolbar);
        setSupportActionBar(toolbar);

        getFragmentManager().beginTransaction().replace(R.id.content_frame, new MyPreferenceFragment()).commit();
    }
}

Et voici le fichier de mise en page (pref_with_actionbar):

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="@dimen/action_bar_height"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:theme="@style/ToolbarTheme.Base"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_below="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

Et enfin le PreferenceFragment:

public static class MyPreferenceFragment extends PreferenceFragment{
    @Override
    public void onCreate(final Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.settings);
    }
}

J'espère que ça aidera quelqu'un.

James Cross
la source
39
J'ai essayé cette approche. Le problème est qu'il n'affiche pas la barre d'outils dans les écrans de préférence enfant.
Madhur Ahuja
2
Je pense qu'il parle de PreferenceScreen intégré dans le XML de préférence racine.
Lucas S.
5
J'ai aimé l'approche, mais ne fonctionnera malheureusement pas si l'API cible est inférieure à l'API 11
midhunhk
12
Cela fonctionnera avec aucun des deux. En pratique, il ne semble pas y avoir de moyen de créer des écrans de préférences matériellement conçus, dotés d'une barre d'outils et imbriqués. Si vous utilisez un ActionBarActivitypour obtenir la barre d'outils et les fonctions associées, il n'y aura pas onBuildHeaders()de remplacement ni de prise en charge réelle des préférences dans l'activité. Si vous utilisez l'ancien PreferenceActivity, vous n'avez pas la barre d'outils et les fonctions associées (oui, vous pouvez avoir une Toolbarmise en page et mais vous ne pouvez pas appeler setSupportActionBar(). Donc, que ce soit avec des en-têtes de préférence ou des écrans de préférences imbriqués, nous semblons bloqués.
Gábor
1
Je suis d'accord avec le commentaire de Gabor. Cette solution ne fonctionne pas en général. Il y en a un meilleur ci-dessous avec émulation de la barre d'outils (pas d'ActionBar, mais qu'importe) et aussi une nouvelle bibliothèque de support publiée avec AppCompatDelegate à bord.
Eugene Wechsler
48

Complètement nouvelle mise à jour.

Avec quelques expérimentations, il me semble avoir trouvé la solution AppCompat 22.1+ fonctionnelle pour les écrans de préférences imbriqués.

Tout d'abord, comme cela est mentionné dans de nombreuses réponses (dont une ici), vous devrez utiliser le nouveau AppCompatDelegate. Soit utiliser le AppCompatPreferenceActivity.javafichier des démos de support ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/ extendCompatible et extendCreference ) à partir de celui-ci, ou copiez les fonctions pertinentes dans les vôtres PreferenceActivity. Je vais montrer la première approche ici:

public class SettingsActivity extends AppCompatPreferenceActivity {

  @Override
  public void onBuildHeaders(List<Header> target) {
    loadHeadersFromResource(R.xml.settings, target);

    setContentView(R.layout.settings_page);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    ActionBar bar = getSupportActionBar();
    bar.setHomeButtonEnabled(true);
    bar.setDisplayHomeAsUpEnabled(true);
    bar.setDisplayShowTitleEnabled(true);
    bar.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
    bar.setTitle(...);
  }

  @Override
  protected boolean isValidFragment(String fragmentName) {
    return SettingsFragment.class.getName().equals(fragmentName);
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case android.R.id.home:
        onBackPressed();
        break;
    }
    return super.onOptionsItemSelected(item);
  }
}

La mise en page d'accompagnement est plutôt simple et habituelle ( layout/settings_page.xml):

<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.support.v7.widget.Toolbar
      android:id="@+id/toolbar"
      android:layout_width="match_parent"
      android:layout_height="?attr/actionBarSize"
      android:background="?attr/colorPrimary"
      android:elevation="4dp"
      android:theme="@style/..."/>
  <ListView
      android:id="@id/android:list"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>
</LinearLayout>

Les préférences elles-mêmes sont définies comme d'habitude ( xml/settings.xml):

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
  <header
      android:fragment="com.example.SettingsFragment"
      android:summary="@string/..."
      android:title="@string/...">
    <extra
        android:name="page"
        android:value="page1"/>
  </header>
  <header
      android:fragment="com.example.SettingsFragment"
      android:summary="@string/..."
      android:title="@string/...">
    <extra
        android:name="page"
        android:value="page2"/>
  </header>
  ...
</preference-headers>

Pas de réelle différence avec les solutions sur le net jusqu'à présent. En fait, vous pouvez l'utiliser même si vous n'avez pas d'écrans imbriqués, pas d'en-têtes, juste un seul écran.

Nous utilisons un commun PreferenceFragmentpour toutes les pages plus profondes, différencié par les extraparamètres dans les en-têtes. Chaque page aura un XML séparé avec un PreferenceScreenintérieur commun ( xml/settings_page1.xmlet al.). Le fragment utilise la même disposition que l'activité, y compris la barre d'outils.

public class SettingsFragment extends PreferenceFragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getActivity().setTheme(R.style...);

    if (getArguments() != null) {
      String page = getArguments().getString("page");
      if (page != null)
        switch (page) {
          case "page1":
            addPreferencesFromResource(R.xml.settings_page1);
            break;
          case "page2":
            addPreferencesFromResource(R.xml.settings_page2);
            break;
          ...
        }
    }
  }

  @Override
  public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View layout = inflater.inflate(R.layout.settings_page, container, false);
    if (layout != null) {
      AppCompatPreferenceActivity activity = (AppCompatPreferenceActivity) getActivity();
      Toolbar toolbar = (Toolbar) layout.findViewById(R.id.toolbar);
      activity.setSupportActionBar(toolbar);

      ActionBar bar = activity.getSupportActionBar();
      bar.setHomeButtonEnabled(true);
      bar.setDisplayHomeAsUpEnabled(true);
      bar.setDisplayShowTitleEnabled(true);
      bar.setHomeAsUpIndicator(R.drawable.abc_ic_ab_back_mtrl_am_alpha);
      bar.setTitle(getPreferenceScreen().getTitle());
    }
    return layout;
  }

  @Override
  public void onResume() {
    super.onResume();

    if (getView() != null) {
      View frame = (View) getView().getParent();
      if (frame != null)
        frame.setPadding(0, 0, 0, 0);
    }
  }
}

Enfin, un bref résumé de la façon dont cela fonctionne réellement. Le nouveau AppCompatDelegatenous permet d'utiliser n'importe quelle activité avec les fonctionnalités AppCompat, pas seulement celles qui s'étendent à partir des activités réellement dans AppCompat. Cela signifie que nous pouvons transformer le bon vieux PreferenceActivityen un nouveau et ajouter la barre d'outils comme d'habitude. À partir de là, nous pouvons nous en tenir aux anciennes solutions concernant les écrans de préférences et les en-têtes, sans aucun écart par rapport à la documentation existante. Il y a juste un point important: ne pas utiliser onCreate()dans l'activité car cela entraînerait des erreurs. À utiliser onBuildHeaders()pour toutes les opérations telles que l'ajout de la barre d'outils.

La seule vraie différence est que, et c'est ce qui le fait fonctionner avec des écrans imbriqués, c'est que vous pouvez utiliser la même approche avec les fragments. Vous pouvez les utiliser de onCreateView()la même manière, en gonflant votre propre mise en page au lieu de celle du système, en ajoutant la barre d'outils de la même manière que dans l'activité.

Gábor
la source
2
Quelle belle petite solution de contournement! C'est la seule solution que j'ai trouvée qui affichera la barre d'outils matérielle sur un PreferenceScreen descendant. Bravo monsieur.
Chaîne du
J'utilise la ressource de la bibliothèque appcompat pour l'icône up:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully
Avec cette solution, je pense que la barre d'outils défilera avec le contenu, n'est-ce pas? Parce que ce n'est qu'un élément dans la ListView interne.
tasomaniac
Pas avec cette nouvelle solution mise à jour. Cela fonctionne comme prévu.
Gábor
Curieusement, cette solution ne semble pas reconnaître un PreferenceFragmentCompatau lieu de PreferenceFragment. La configuration d'un preference-headeravec xmlns:app="http://schemas.android.com/apk/res-auto" et puis app:fragmentau lieu de android:fragmentne charge aucun nouvel écran de préférences. Donc, avoir des problèmes avec la compatibilité ascendante ... des suggestions?
fattire
18

Si vous souhaitez utiliser PreferenceHeaders, vous pouvez utiliser l'approche suivante:

import android.support.v7.widget.Toolbar;

public class MyPreferenceActivity extends PreferenceActivity

   Toolbar mToolbar;

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

        ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
        LinearLayout content = (LinearLayout) root.getChildAt(0);
        LinearLayout toolbarContainer = (LinearLayout) View.inflate(this, R.layout.activity_settings, null);

        root.removeAllViews();
        toolbarContainer.addView(content);
        root.addView(toolbarContainer);

        mToolbar = (Toolbar) toolbarContainer.findViewById(R.id.toolbar);
    }

    @Override
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }

    // Other methods

}

layout / activity_settings.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_height="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:minHeight="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:theme="@style/AppTheme"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>

</LinearLayout>

Vous pouvez utiliser la mise en page que vous préférez ici, assurez-vous simplement de l'ajuster également dans le code Java.

Et enfin, votre fichier avec les en-têtes (xml / pref_headers.xml)

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">

    <header
        android:fragment="com.example.FirstFragment"
        android:title="@string/pref_header_first" />
    <header
        android:fragment="com.example.SecondFragment"
        android:title="@string/pref_header_second" />

</preference-headers>
Sven Dubbeld
la source
root.addView (barre d'outils); Pourquoi? root.addView (toolbarContainer);
Crossle Song
Oups, j'ai manqué une variable lors du changement de nom, l'a corrigé.
Sven Dubbeld
1
Excellente réponse. La clé ici est android.R.id.content, étant donné que nous avions l'habitude de passer un ListViewavec android.R.id.listpour la liste de préférences elle-même (et le faisons toujours si vous utilisez la méthode sans fragment, sans en-tête) à la place.
davidcsb
2
Je pense qu'il vaut mieux vérifier le code d'Android, pour voir ce dont il a besoin, au lieu de déconner avec ses vues hirerchy (supprimer / ajouter des vues qu'il a). Je pense que c'est plus sûr de cette façon. Je suggère de vérifier le fichier "préférence_list_content".
développeur android
2
C'est la meilleure réponse dans ce fil. L'auteur de cet article l'a étendu à l'implémentation de référence complète, que j'ai utilisée. En fait, c'est la seule solution qui fonctionne pour prendre en charge les préférences sophistiquées dans votre application.
Eugene Wechsler
17

Avec la sortie de la bibliothèque de support Android 22.1.0 et du nouveau AppCompatDelegate, vous pouvez trouver ici un bel exemple d'implémentation de PreferenceActivity avec prise en charge matérielle avec compatibilité descendante.

Mise à jour Cela fonctionne également sur les écrans imbriqués.

https://android.googlesource.com/platform/development/+/marshmallow-mr3-release/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java

Mr. Brightside
la source
1
Oh, c'est une excellente nouvelle! Il apparaît donc que les solutions basées sur "extend PreferenceActivity" sont meilleures que celles sur "extend ActionBarActivity" dans cette nouvelle perspective.
Eugene Wechsler
1
@EugeneWechsler Oui, en effet, ActionBarActivity est désormais obsolète.
MrBrightside
Cette solution fonctionne également sur des écrans imbriqués? Y a-t-il un meilleur exemple?
Tomas
@Tomas Je n'ai pas encore essayé, mais cela devrait aussi fonctionner sur les écrans imbriqués. Si cela fonctionne pour vous, dites-nous s'il vous plaît.
MrBrightside
Merci beaucoup ! Travailler pour moi sur un Galaxy Nexus (4.3) et sur l'émulateur avec des écrans imbriqués (sucette).
Tim Autin
6

Bien que les réponses ci-dessus semblent élaborées, si vous voulez une solution rapide pour utiliser la barre d'outils avec le support API 7 et plus tout en s'étendant PreferenceActivity, j'ai obtenu l'aide de ce projet ci-dessous.

https://github.com/AndroidDeveloperLB/ActionBarPreferenceActivity

activity_settings.xml

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

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/app_theme_light"
    app:popupTheme="@style/Theme.AppCompat.Light"
    app:theme="@style/Theme.AppCompat" />

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="@dimen/padding_medium" >

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>

SettingsActivity.java

public class SettingsActivity extends PreferenceActivity {

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

    setContentView(R.layout.activity_settings);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

    addPreferencesFromResource(R.xml.preferences);

    toolbar.setClickable(true);
    toolbar.setNavigationIcon(getResIdFromAttribute(this, R.attr.homeAsUpIndicator));
    toolbar.setTitle(R.string.menu_settings);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            finish();
        }
    });

}

private static int getResIdFromAttribute(final Activity activity, final int attr) {
    if (attr == 0) {
        return 0;
    }
    final TypedValue typedvalueattr = new TypedValue();
    activity.getTheme().resolveAttribute(attr, typedvalueattr, true);
    return typedvalueattr.resourceId;
}
}
midhunhk
la source
6

Moi aussi, j'ai cherché une solution pour ajouter la barre d'outils de support v7 ( API 25 ) à AppCompatPreferenceActivity (qui est automatiquement créée par AndroidStudio lors de l'ajout d'un SettingsActivity). Après avoir lu plusieurs solutions et essayé chacune d'elles, j'ai eu du mal à afficher les exemples PreferenceFragment générés avec une barre d'outils.

Une solution modifiée qui fonctionnait en quelque sorte provenait de " Gabor ".

L'une des mises en garde auxquelles j'ai été confronté était les incendies «onBuildHeaders» une seule fois. Si vous tournez un appareil (comme un téléphone) sur le côté, la vue est recréée et PreferenceActivity est à nouveau laissée sans barre d'outils, mais les PreferenceFragments conserveraient les leurs.

J'ai essayé d'utiliser «onPostCreate» pour appeler «setContentView», alors que cela fonctionnait pour recréer la barre d'outils lorsque l'orientation changeait, PreferenceFragments serait alors rendu vide.

Ce que j'ai trouvé tire parti de presque tous les conseils et réponses que j'ai pu lire sur ce sujet. J'espère que d'autres le trouveront également utile.

Nous allons commencer par le Java

D'abord dans (la) AppCompatPreferenceActivity.java, j'ai modifié 'setSupportActionBar' comme ceci:

public void setSupportActionBar(@Nullable Toolbar toolbar) {
    getDelegate().setSupportActionBar(toolbar);
    ActionBar bar = getDelegate().getSupportActionBar();
    bar.setHomeButtonEnabled(true);
    bar.setDisplayHomeAsUpEnabled(true);
}

Deuxièmement , j'ai créé une nouvelle classe nommée AppCompatPreferenceFragment.java (c'est actuellement un nom inutilisé, bien qu'il ne puisse pas rester ainsi!):

abstract class AppCompatPreferenceFragment extends PreferenceFragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_settings, container, false);
        if (view != null) {
            Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar_settings);
            ((AppCompatPreferenceActivity) getActivity()).setSupportActionBar(toolbar);
        }
        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        View frame = (View) getView().getParent();
        if (frame != null) frame.setPadding(0, 0, 0, 0);
    }
}

C'est la partie de la réponse de Gabor qui a fonctionné.

Enfin , pour obtenir la cohérence, nous devons apporter des modifications à SettingsActivity.java :

public class SettingsActivity extends AppCompatPreferenceActivity {

    boolean mAttachedFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        mAttachedFragment = false;
        super.onCreate(savedInstanceState);
    }

    @Override
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void onBuildHeaders(List<Header> target) {
        loadHeadersFromResource(R.xml.pref_headers, target);
    }

    @Override
    public void onAttachFragment(Fragment fragment) {
        mAttachedFragment = true;
        super.onAttachFragment(fragment);
    }

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

        //if we didn't attach a fragment, go ahead and apply the layout
        if (!mAttachedFragment) {
            setContentView(R.layout.activity_settings);
            setSupportActionBar((Toolbar)findViewById(R.id.toolbar_settings));
        }
    }

    /**
     * This fragment shows general preferences only. It is used when the
     * activity is showing a two-pane settings UI.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public static class GeneralPreferenceFragment extends AppCompatPreferenceFragment {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            addPreferencesFromResource(R.xml.pref_general);
            setHasOptionsMenu(true);

            bindPreferenceSummaryToValue(findPreference("example_text"));
            bindPreferenceSummaryToValue(findPreference("example_list"));
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == android.R.id.home) {
                startActivity(new Intent(getActivity(), SettingsActivity.class));
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    }
}

Certains codes ont été exclus de l'activité par souci de concision. Les composants clés ici sont « onAttachedFragment », « onPostCreate », et le «GeneralPreferenceFragment» étend désormais le « AppCompatPreferenceFragment » personnalisé au lieu de PreferenceFragment.

Résumé du code : si un fragment est présent, le fragment injecte la nouvelle mise en page et appelle la fonction modifiée 'setSupportActionBar'. Si le fragment n'est pas présent, SettingsActivity injecte la nouvelle mise en page sur 'onPostCreate'

Passons maintenant au XML (très simple):

activity_settings.xml :

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

    <include
        layout="@layout/app_bar_settings"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

app_bar_settings.xml :

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".SettingsActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.NoActionBar.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar_settings"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.NoActionBar.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_settings" />

</android.support.design.widget.CoordinatorLayout>

content_settings.xml :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context=".SettingsActivity"
    tools:showIn="@layout/app_bar_settings">

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

Résultat final :

ParamètresActivité

GénéralPréférenceFragment

SilverX
la source
Cela semble prometteur mais ne fonctionne pas pour moi. imgur.com/lSSVCIo (émulateur Pixel C).
Thomas Vos
Lien Github pour les paresseux
Martin Sing
5

J'ai une nouvelle solution (peut-être plus soignée), qui utilise les AppCompatPreferenceActivityexemples de Support v7. Avec ce code en main, j'ai créé ma propre mise en page qui comprend une barre d'outils:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:fitsSystemWindows="true" tools:context="edu.adelphi.Adelphi.ui.activity.MainActivity">

    <android.support.design.widget.AppBarLayout android:id="@+id/appbar"
        android:layout_width="match_parent" android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar android:id="@+id/toolbar"
            android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

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

</android.support.design.widget.CoordinatorLayout>

Ensuite, dans mon AppCompatPreferenceActivity, j'ai modifié setContentViewpour créer une nouvelle mise en page et placer la mise en page fournie dans mon FrameLayout:

@Override
public void setContentView(@LayoutRes int layoutResID) {
    View view = getLayoutInflater().inflate(R.layout.toolbar, null);
    FrameLayout content = (FrameLayout) view.findViewById(R.id.content);
    getLayoutInflater().inflate(layoutResID, content, true);
    setContentView(view);
}

Ensuite AppCompatPreferenceActivity, j'étends simplement , me permettant d'appeler setSupportActionBar((Toolbar) findViewById(R.id.toolbar))et de gonfler les éléments de menu dans la barre d'outils également. Tout en gardant les avantages d'un PreferenceActivity.

Bryan
la source
5

Gardons les choses simples et propres ici, sans casser aucune mise en page intégrée

import android.support.design.widget.AppBarLayout;
import android.support.v4.app.NavUtils;
import android.support.v7.widget.Toolbar;

private void setupActionBar() {
    Toolbar toolbar = new Toolbar(this);

    AppBarLayout appBarLayout = new AppBarLayout(this);
    appBarLayout.addView(toolbar);

    final ViewGroup root = (ViewGroup) findViewById(android.R.id.content);
    final ViewGroup window = (ViewGroup) root.getChildAt(0);
    window.addView(appBarLayout, 0);

    setSupportActionBar(toolbar);

    // Show the Up button in the action bar.
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
    });
}
Samuel
la source
Cela n'a pas fonctionné pour moi, root.getChildAt(0);revient null.
Eido95 le
4

J'ai trouvé cette solution simple en travaillant là-dessus. Nous devons d'abord créer une mise en page pour l'activité des paramètres.

activity_settings.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.my.package">

    <android.support.v7.widget.Toolbar
        android:id="@+id/tool_bar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:elevation="@dimen/appbar_elevation"
        app:navigationIcon="?attr/homeAsUpIndicator"
        app:navigationContentDescription="@string/abc_action_bar_up_description"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <ListView
        android:id="@android:id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tool_bar" />

</RelativeLayout>

Assurez-vous d'ajouter une vue de liste avec android:id="@android:id/list", sinon cela lanceraNullPointerException

La prochaine étape consiste à ajouter la onCreateméthode (Override) dans votre activité de paramètres

Settings.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_settings);
    Toolbar toolbar = (Toolbar) findViewById(R.id.tool_bar);
    toolbar.setTitle(R.string.action_settings);
    toolbar.setNavigationOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            finish();
        }
    });
}

Assurez-vous d'importer android.suppoer.v7.widget.Toolbar. Cela devrait fonctionner à peu près sur toutes les API au-dessus de 16 (Jelly Bean et plus)

Apurva
la source
1

Je voudrais continuer la solution marquée de James Cross, car après cela, il y a un problème de fermeture uniquement de l'écran imbriqué actif (PreferenceFragment) de manière à ne pas fermer également SettingsActivity.

En fait cela fonctionne sur tous les écrans imbriqués (donc je ne comprends pas la solution de Gábor que j'ai essayée sans succès, eh bien cela fonctionne jusqu'à un certain point mais c'est un désordre de plusieurs barres d'outils), car lorsque l'utilisateur clique sur un écran de sous-préférences , seul le fragment est modifié (voir <FrameLayout android:id="@+id/content_frame" .../>) pas la barre d'outils qui reste toujours active et visible, mais un comportement personnalisé doit être implémenté pour fermer chaque fragment en conséquence.

Dans la classe principale SettingsActivityqui étend ActionBarActivityles méthodes suivantes doivent être implémentées. Notez que private setupActionBar()est appelé depuisonCreate()

private void setupActionBar() {
    Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
    //Toolbar will now take on default Action Bar characteristics
    setSupportActionBar(toolbar);
    getSupportActionBar().setHomeButtonEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case android.R.id.home:
        onBackPressed();
        return true;
    }
    return super.onOptionsItemSelected(item);
}

@Override
public void onBackPressed() {
    if (getFragmentManager().getBackStackEntryCount() > 0) {
        getFragmentManager().popBackStackImmediate();
        //If the last fragment was removed then reset the title of main
        // fragment (if so the previous popBackStack made entries = 0).
        if (getFragmentManager().getBackStackEntryCount() == 0) {
            getSupportActionBar()
                .setTitle(R.string.action_settings_title);
        }
    } else {
        super.onBackPressed();
    }
}

Pour le titre de l'écran imbriqué choisi, vous devez obtenir la référence de votre barre d'outils et définir le titre approprié avec toolbar.setTitle(R.string.pref_title_general);(par exemple).

Il n'est pas nécessaire d'implémenter le getSupportActionBar()in all PreferenceFragment puisque seule la vue du fragment est modifiée à chaque validation, pas la barre d'outils;

Il n'est pas nécessaire de créer une fausse classe ToolbarPreference à ajouter dans chaque fichier preferences.xml (voir la réponse de Gábor).

Davideas
la source
1

Voici une bibliothèque que j'ai créée et basée sur le code AOSP, qui ajoute une teinte aux préférences et aux boîtes de dialogue, ajoute une barre d'action et prend en charge toutes les versions de l'API 7:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary

développeur android
la source
En regardant le code, cela ne fonctionne pas pour les préférences imbriquées ...?
Tim Rae
@TimRae Je ne suis pas sûr d'avoir testé de quoi vous parlez. Explique ce que tu veux dire, s'il te plait. Quel est le scénario que vous essayez d'utiliser exactement?
développeur android
Quand vous avez un PreferenceScreenintérieur PreferenceScreencomme ça
Tim Rae
Je n'ai jamais utilisé une telle chose. en lisant la documentation:: developer.android.com/reference/android/preference/… , je vois que cela peut aider à passer d'un écran à l'autre. Vous dites que je devrais l'ajouter aussi? Je vais vérifier cela . Je vous remercie. Veuillez également utiliser Github la prochaine fois pour une telle chose (demandes et problèmes).
développeur android
Oui, c'est utile lorsque vous avez trop de préférences pour un seul écran ... Il y a déjà plusieurs endroits dans ce fil où les gens mentionnent des écrans imbriqués, donc je pense que voici un endroit approprié pour commenter
Tim Rae
1

Eh bien, c'est toujours un problème pour moi aujourd'hui (18 novembre 2015). J'ai essayé toutes les solutions de ce fil, mais il y avait deux choses principales que je ne pouvais pas résoudre:

  • Les écrans de préférences imbriqués sont apparus sans barre d'outils
  • Les préférences n'avaient pas l'aspect matériel sur les appareils pré-Lollipop

J'ai donc fini par créer une bibliothèque avec une solution plus compliquée. Fondamentalement, j'ai dû appliquer en interne des styles aux préférences si nous utilisons un périphérique pré-Lollipop et j'ai également géré les écrans imbriqués à l'aide d'un fragment personnalisé (restauration de toute la hiérarchie imbriquée en profitant de la clé PreferenceScreen ).

La bibliothèque est celle-ci: https://github.com/ferrannp/material-preferences

Et si vous êtes intéressé par le code source (trop long pour le poster ici), c'est en gros le noyau: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ java / com / fnp / materialpreferences / PreferenceFragment.java

Ferran Negre
la source