Creazione di una schermata delle preferenze con la barra degli strumenti di supporto (v21)


116

Ho avuto problemi a utilizzare la nuova barra degli strumenti Material Design nella libreria di supporto in una schermata delle preferenze.

Ho un file settings.xml come di seguito:

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

Le stringhe sono definite altrove.


stackoverflow.com/a/27455363/2247612 Questa risposta ha una soluzione perfetta per Support Library
harishannam

Risposte:


110

Si prega di trovare il repository GitHub: qui


Un po 'tardi per la festa, ma questa è la mia soluzione che sto usando come soluzione per continuare a usare 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 :

esempio


AGGIORNAMENTO (Gingerbread Compatibility):

Secondo i commenti, i dispositivi Gingerbread stanno restituendo NullPointerException su questa riga:

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

FIX:

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

Eventuali problemi con quanto sopra fammi sapere!


AGGIORNAMENTO 2: LAVORO SULLA TINTA

Come sottolineato in molte note di sviluppo PreferenceActivitynon supporta la colorazione degli elementi, tuttavia utilizzando alcune classi interne PUOI raggiungere questo obiettivo. Questo fino a quando queste classi non vengono rimosse. (Funziona utilizzando appCompat support-v7 v21.0.3).

Aggiungi le seguenti importazioni:

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;

Quindi sovrascrivi il onCreateViewmetodo:

@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:

esempio 2


AppCompat 22.1

AppCompat 22.1 ha introdotto nuovi elementi colorati, il che significa che non è più necessario utilizzare le classi interne per ottenere lo stesso effetto dell'ultimo aggiornamento. Invece segui questo (ancora sovrascrivendo 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;
}

SCHERMATE DELLE PREFERENZE NIDIFICATE

Molte persone stanno riscontrando problemi con l'inclusione della barra degli strumenti in un annidato <PreferenceScreen />, tuttavia ho trovato una soluzione !! - Dopo molte prove ed errori!

Aggiungi quanto segue al tuo 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();
        }
    });
}

Il motivo PreferenceScreenper cui sono un tale problema è perché sono basati come una finestra di dialogo wrapper, quindi dobbiamo acquisire il layout della finestra di dialogo per aggiungervi la barra degli strumenti.


Ombra della barra degli strumenti

In fase di progettazione, l'importazione di Toolbarnon consente l'elevazione e l'ombreggiatura nei dispositivi precedenti alla v21, quindi se desideri avere l'elevazione sul tuo Toolbarè necessario racchiuderla in 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>

Senza dimenticare di aggiungere l'aggiunta della libreria Design Support come dipendenza nel build.gradlefile:

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

Ho esaminato il problema di sovrapposizione segnalato e non posso riprodurre il problema.

Il codice completo in uso come sopra produce quanto segue:

inserisci qui la descrizione dell'immagine

Se mi manca qualcosa, fatemelo sapere tramite questo repository e indagherò.


dà un'eccezione nullpointer in gingebread, root è null..una soluzione?
Qlimax

la tua soluzione funziona alla grande. ma c'è un problema con questo approccio, infatti senza estendere ActionBarActivity che è obbligatorio (dalla documentazione) per ottenere il tema del materiale su <5.0, colorAccent (solo per fare un esempio) non viene applicato alle caselle di controllo nei dispositivi <5.0. Sembra una vera seccatura ... forse devo rimuovere l'attività delle preferenze e utilizzare un layout lineare per simulare una schermata delle preferenze, altrimenti non vedo alcun modo per utilizzare il tema del materiale nei dispositivi dal livello api 8 al 21. Il frammento di preferenza è "only"> 11 :(
andQlimax

1
@andQlimax Ho aggiornato la mia risposta con una soluzione per il problema della colorazione
David Passmore

3
@DavidPassmore per me l'elenco delle preferenze si sovrappone sulla barra degli strumenti
Shashank Srivastava

1
@ShashankSrivastava Questo si riflette se stai usando Android 6, sto lavorando a una soluzione per questo. Grazie per l'aggiornamento.
David Passmore

107

Puoi usare un PreferenceFragment, in alternativa a PreferenceActivity. Quindi, ecco l' Activityesempio di avvolgimento :

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

Ed ecco il file di layout (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>

E infine PreferenceFragment:

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

Spero che questo aiuti qualcuno.


39
Ho provato questo approccio. Il problema è che non visualizza la barra degli strumenti nelle schermate delle preferenze figlio.
Madhur Ahuja

2
Penso che stia parlando di PreferenceScreen incorporato nell'XML delle preferenze di root.
Lucas S.

5
Mi è piaciuto l'approccio, ma purtroppo non funzionerà se l'API di destinazione è inferiore a API 11
midhunhk

12
Non funzionerà con nessuno dei due. In pratica, non sembra esserci alcun modo per creare schermate delle preferenze nidificate, con barra degli strumenti e progettate materialmente. Se si utilizza un ActionBarActivityper ottenere la barra degli strumenti e le funzioni correlate, non ci sarà alcun onBuildHeaders()override e nessun supporto effettivo per le preferenze nell'attività. Se usi il vecchio PreferenceActivity, non hai la barra degli strumenti e le funzioni correlate (sì, puoi avere un Toolbarlayout e ma non puoi chiamare setSupportActionBar(). Quindi, con le intestazioni delle preferenze o le schermate delle preferenze annidate, sembriamo bloccati.
Gábor

1
Sono d'accordo con il commento di Gabor. Questa soluzione non funziona in generale. Ce n'è uno migliore con la barra degli strumenti di emulazione (non ActionBar, ma chi se ne frega) e anche la nuova lib di supporto rilasciata con AppCompatDelegate a bordo.
Eugene Wechsler

48

Aggiornamento completamente nuovo.

Con un po 'di sperimentazione, mi sembra di aver trovato la soluzione AppCompat 22.1+ funzionante per schermate di preferenze nidificate.

Innanzitutto, come menzionato in molte risposte (inclusa una qui), dovrai usare il nuovo AppCompatDelegate. Utilizza il AppCompatPreferenceActivity.javafile dalle demo di supporto ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/appva/AppativityPay ) e da esso, o copia le funzioni rilevanti nel tuo PreferenceActivity. Mostrerò il primo approccio qui:

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

Il layout di accompagnamento è piuttosto semplice e usuale ( 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>

Le preferenze stesse sono definite come al solito ( 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>

Nessuna vera differenza rispetto alle soluzioni in rete fino a questo punto. In realtà, puoi usarlo anche se non hai schermate nidificate, nessuna intestazione, solo una singola schermata.

Usiamo un comune PreferenceFragmentper tutte le pagine più profonde, differenziato dai extraparametri nelle intestazioni. Ogni pagina avrà un XML separato con un PreferenceScreeninterno comune ( xml/settings_page1.xmlet al.). Il frammento utilizza lo stesso layout dell'attività, inclusa la barra degli strumenti.

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

Infine, un breve riepilogo di come funziona effettivamente. Il nuovo AppCompatDelegateci permette di utilizzare qualsiasi attività con le funzionalità di AppCompat, non solo quelle estese dalle attività effettivamente in AppCompat. Ciò significa che possiamo trasformare il buon vecchio PreferenceActivityin uno nuovo e aggiungere la barra degli strumenti come al solito. Da quel momento in poi, possiamo attenerci alle vecchie soluzioni per quanto riguarda le schermate delle preferenze e le intestazioni, senza alcuna deviazione dalla documentazione esistente. C'è solo un punto importante: non usare onCreate()nell'attività perché porterà a errori. Utilizzare onBuildHeaders()per tutte le operazioni come l'aggiunta della barra degli strumenti.

L'unica vera differenza è, e questo è ciò che lo fa funzionare con gli schermi nidificati è che puoi usare lo stesso approccio con i frammenti. Puoi usarli onCreateView()allo stesso modo, gonfiando il tuo layout invece di quello di sistema, aggiungendo la barra degli strumenti allo stesso modo dell'attività.


2
Che grande piccola soluzione alternativa! Questa è l'unica soluzione che ho trovato che mostrerà la barra degli strumenti del materiale su un PreferenceScreen discendente. Ben fatto signore.
String

Uso la risorsa dalla libreria appcompat per l'icona su:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully

Con questa soluzione, penso che la barra degli strumenti scorrerà con il contenuto, giusto? Perché è solo un elemento nel ListView interno.
tasomaniac

Non con questa nuova soluzione aggiornata. Funziona proprio come previsto.
Gábor

Stranamente, questa soluzione non sembra riconoscere a PreferenceFragmentCompatinvece di PreferenceFragment. L'impostazione di preference-headercon xmlns:app="http://schemas.android.com/apk/res-auto" e poi app:fragmentinvece di android:fragmentnon carica nessuna nuova schermata delle preferenze. Quindi avere problemi con la retrocompatibilità ... suggerimenti?
fattire

18

Se desideri utilizzare PreferenceHeaders, puoi utilizzare il seguente approccio:

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>

Puoi utilizzare il layout che preferisci qui, assicurati di regolarlo anche nel codice Java.

E infine, il tuo file con le intestazioni (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>

root.addView (barra degli strumenti); perché? root.addView (toolbarContainer);
Crossle Song

Ops, ho perso una variabile durante la ridenominazione, l'ho risolta.
Sven Dubbeld

1
Ottima risposta. La chiave qui è android.R.id.content, considerando che eravamo soliti passare un ListViewcon android.R.id.listper l'elenco delle preferenze stesso (e lo facciamo ancora se si usa invece il modo senza frammenti, senza intestazione).
davidcsb

2
Penso che sia meglio controllare il codice di Android, per vedere di cosa ha bisogno, invece di scherzare con le sue viste assurde (rimuovere / aggiungere le visualizzazioni che ha). Penso che sia più sicuro in questo modo. Suggerisco di controllare il file "preferenza_list_content".
sviluppatore Android

2
Questa è la migliore risposta in questo thread. L'autore di questo articolo lo ha esteso all'implementazione di riferimento completo, che ho usato. In effetti, questa è l'unica soluzione funzionante per supportare preferenze sofisticate nella tua app.
Eugene Wechsler

17

Con il rilascio della libreria di supporto Android 22.1.0 e del nuovo AppCompatDelegate, qui puoi trovare un bel esempio di un'implementazione di PreferenceActivity con supporto materiale con retrocompatibilità.

Aggiorna Funziona anche su schermi annidati.

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


1
Oh, è un'ottima notizia! Quindi sembra che le soluzioni basate su "Estendi PreferenceActivity" siano migliori di quelle su "Estendi ActionBarActivity" in questa nuova prospettiva.
Eugene Wechsler

1
@EugeneWechsler Sì, infatti, ActionBarActivity è stato deprecato ora.
MrBrightside

Funziona questa soluzione anche su schermi annidati? C'è un esempio migliore?
Tomas

@Tomas non ho ancora provato, ma dovrebbe funzionare anche su schermi nidificati. Se funziona per te, faccelo sapere per favore.
MrBrightside

Molte grazie ! Lavorando per me su un Galaxy Nexus (4.3) e sull'emulatore con schermi nidificati (lecca-lecca).
Tim Autin

6

Sebbene le risposte di cui sopra sembrano elaborate, se desideri una soluzione rapida per utilizzare Toolbar con supporto API 7 e versioni successive PreferenceActivity, ho ricevuto aiuto da questo progetto di seguito.

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

6

Anch'io ho cercato una soluzione per aggiungere la barra degli strumenti di supporto v7 ( API 25 ) ad AppCompatPreferenceActivity (che viene creata automaticamente da AndroidStudio quando si aggiunge una SettingsActivity). Dopo aver letto diverse soluzioni e provato ciascuna di esse, ho faticato a far sì che gli esempi di PreferenceFragment generati venissero visualizzati anche con una barra degli strumenti.

Una soluzione modificata che ha funzionato era di " Gabor ".

Uno degli avvertimenti che ho dovuto affrontare è stato che "onBuildHeaders" si attiva solo una volta. Se si gira lateralmente un dispositivo (come un telefono), la visualizzazione viene ricreata e PreferenceActivity viene nuovamente lasciato senza una barra degli strumenti, tuttavia i PreferenceFragments manterranno i propri.

Ho provato a usare 'onPostCreate' per chiamare 'setContentView', mentre questo funzionava per ricreare la barra degli strumenti quando l'orientamento cambiava, PreferenceFragments veniva quindi reso vuoto.

Quello che ho trovato fa leva su quasi ogni suggerimento e risposta che ho potuto leggere su questo argomento. Spero che anche altri lo trovino utile.

Inizieremo con Java

Prima in (il generato) AppCompatPreferenceActivity.java ho modificato 'setSupportActionBar' in questo modo:

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

In secondo luogo , ho creato una nuova classe denominata AppCompatPreferenceFragment.java (è attualmente un nome inutilizzato, anche se potrebbe non rimanere così!):

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

Questa è la parte della risposta di Gabor che ha funzionato.

Infine , per ottenere coerenza dobbiamo apportare alcune modifiche a 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);
        }
    }
}

Parte del codice è stata lasciata fuori dall'attività per brevità. I componenti chiave qui sono " onAttachedFragment ", " onPostCreate " e "GeneralPreferenceFragment" ora estende " AppCompatPreferenceFragment " personalizzato anziché PreferenceFragment.

Riepilogo del codice : se è presente un frammento, il frammento inietta il nuovo layout e chiama la funzione "setSupportActionBar" modificata. Se il frammento non è presente, SettingsActivity inserisce il nuovo layout su "onPostCreate"

Ora passiamo all'XML (molto semplice):

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>

Risultato finale :

SettingsActivity

GeneralPreferenceFragment


Sembra promettente ma non funziona per me. imgur.com/lSSVCIo (emulatore Pixel C).
Thomas Vos


5

Ho una nuova soluzione (forse più ordinata), che utilizza gli AppCompatPreferenceActivityesempi di Support v7. Con questo codice in mano ho creato il mio layout che include una barra degli strumenti:

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

Quindi, in my AppCompatPreferenceActivity, ho modificato setContentViewper creare un mio nuovo layout e ho inserito il layout fornito all'interno di 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);
}

Quindi mi limito a estendere AppCompatPreferenceActivity, permettendomi di chiamare setSupportActionBar((Toolbar) findViewById(R.id.toolbar))e gonfiare anche le voci di menu nella barra degli strumenti. Il tutto mantenendo i vantaggi di un file PreferenceActivity.


5

Manteniamolo semplice e pulito qui, senza rompere alcun layout integrato

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

Non ha funzionato per me, root.getChildAt(0);ritorna null.
Eido95

4

Ho trovato questa semplice soluzione mentre lavoravo su questo. Per prima cosa dobbiamo creare un layout per l'attività delle impostazioni.

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>

Assicurati di aggiungere una visualizzazione elenco con android:id="@android:id/list", altrimenti verrà lanciataNullPointerException

Il passaggio successivo consiste nell'aggiungere il onCreatemetodo (Sostituisci) nell'attività delle impostazioni

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

Assicurati di importare android.suppoer.v7.widget.Toolbar. Questo dovrebbe funzionare praticamente su tutte le API superiori a 16 (Jelly Bean e superiori)


1

Vorrei continuare la soluzione marcata di James Cross, poiché dopo di ciò c'è un problema di chiudere solo lo schermo nidificato attivo (PreferenceFragment) in modo da non chiudere anche SettingsActivity.

In realtà funziona su tutte le schermate annidate (quindi non capisco la soluzione di Gábor che ho provato senza successo, beh funziona fino a un certo punto ma è un pasticcio di più barre degli strumenti), perché quando l'utente fa clic su una schermata delle preferenze secondarie , viene modificato solo il frammento (vedi <FrameLayout android:id="@+id/content_frame" .../>) non la Toolbar che rimane sempre attiva e visibile, ma dovrebbe essere implementato un comportamento personalizzato per chiudere ogni frammento di conseguenza.

Nella classe principale SettingsActivityche estende ActionBarActivityi seguenti metodi dovrebbero essere implementati. Nota che private setupActionBar()viene chiamato daonCreate()

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

Per il titolo della schermata nidificata scelta dovresti ottenere il riferimento della tua barra degli strumenti e impostare il titolo appropriato contoolbar.setTitle(R.string.pref_title_general); (ad esempio).

Non è necessario implementare ilgetSupportActionBar() PreferenceFragment in tutto poiché viene modificata solo la visualizzazione del frammento ad ogni commit, non la barra degli strumenti;

Non è necessario creare una falsa classe ToolbarPreference da aggiungere in ogni preferenza.xml (vedere la risposta di Gábor).


1

Ecco una libreria che ho creato che si basa sul codice AOSP, che aggiunge la colorazione sia alle preferenze che alle finestre di dialogo, aggiunge una barra delle azioni e supporta tutte le versioni dall'API 7:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary


Guardando il codice, non funziona per le preferenze annidate ...?
Tim Rae

@TimRae Non sono sicuro di aver testato ciò di cui stai parlando. Per favore, spiega cosa intendi. Qual è lo scenario che stai cercando di utilizzare esattamente?
sviluppatore Android

Quando hai un PreferenceScreeninterno PreferenceScreencome questo
Tim Rae

Non ho mai usato una cosa del genere. leggendo i documenti:: developer.android.com/reference/android/preference/… , vedo che può aiutare a passare da uno schermo all'altro. Dici che dovrei aggiungerlo anch'io? Controllerò . Grazie. Inoltre, per favore usa Github la prossima volta per cose del genere (richieste e problemi).
sviluppatore Android

Sì, è utile quando hai troppe preferenze per un singolo schermo ... Ci sono già diversi punti in questo thread in cui le persone menzionano schermi nidificati, quindi penso che qui sia un posto appropriato per commentare
Tim Rae

1

Bene, questo è ancora un problema per me oggi (18 novembre 2015). Ho provato tutte le soluzioni da questo thread ma c'erano due cose principali che non riuscivo a risolvere:

  • Le schermate delle preferenze nidificate apparivano senza barra degli strumenti
  • Le preferenze non avevano l'aspetto Materiale sui dispositivi pre-Lollipop

Quindi ho finito per creare una libreria con una soluzione più complicata. Fondamentalmente, ho dovuto applicare internamente gli stili alle preferenze se stiamo utilizzando un dispositivo pre-Lollipop e ho anche gestito le schermate nidificate utilizzando un frammento personalizzato (ripristinando tutta la gerarchia nidificata sfruttando il tasto PreferenceScreen ).

La libreria è questa: https://github.com/ferrannp/material-preferences

E se sei interessato al codice sorgente (troppo lungo per pubblicarlo qui), questo è fondamentalmente il nucleo di esso: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ java / com / FNP / materialpreferences / PreferenceFragment.java

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.