Crear una pantalla de preferencias con la barra de herramientas de soporte (v21)


116

Tenía problemas para usar la nueva barra de herramientas de Material Design en la biblioteca de soporte en una pantalla de Preferencias.

Tengo un archivo settings.xml como se muestra a continuación:

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

Las cadenas se definen en otro lugar.


stackoverflow.com/a/27455363/2247612 Esta respuesta tiene una solución perfecta para la biblioteca de soporte
harishannam

Respuestas:


110

Encuentre el repositorio de GitHub: aquí


Un poco tarde para la fiesta, pero esta es mi solución que estoy usando como una solución para continuar usando 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 :

ejemplo


ACTUALIZACIÓN (compatibilidad con pan de jengibre):

Según los comentarios, los dispositivos Gingerbread están devolviendo NullPointerException en esta línea:

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

REPARAR:

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

¡Cualquier problema con lo anterior, avíseme!


ACTUALIZACIÓN 2: SOLUCIÓN DE TINTADO

Como se señaló en muchas notas de desarrollo, PreferenceActivityno es compatible con el tinte de elementos, sin embargo, al utilizar algunas clases internas, PUEDE lograrlo. Eso es hasta que se eliminen estas clases. (Funciona con appCompat support-v7 v21.0.3).

Agregue las siguientes importaciones:

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;

Luego anule el onCreateViewmétodo:

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

ejemplo 2


AppCompat 22.1

AppCompat 22.1 introdujo nuevos elementos tintados, lo que significa que ya no es necesario utilizar las clases internas para lograr el mismo efecto que la última actualización. En su lugar, siga esto (aún anulandoonCreateView ):

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

PANTALLAS DE PREFERENCIAS ANIDADAS

Mucha gente está experimentando problemas al incluir la barra de herramientas en un <PreferenceScreen /> , sin embargo, he encontrado una solución. - ¡Después de mucho ensayo y error!

Agregue lo siguiente a su 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 razón que PreferenceScreen son tan molestos es porque se basan como un diálogo contenedor, por lo que necesitamos capturar el diseño del diálogo para agregarle la barra de herramientas.


Sombra de la barra de herramientas

Por diseño, la importación Toolbarno permite la elevación y el sombreado en dispositivos anteriores a la v21, por lo que si desea tener elevación en su Toolbar, debe envolverlo en unAppBarLayout :

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>

Sin olvidar agregar la biblioteca Add the Design Support como una dependencia en el build.gradlearchivo:

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

Investigué el problema de superposición informado y no puedo reproducirlo.

El código completo en uso como arriba produce lo siguiente:

ingrese la descripción de la imagen aquí

Si me falta algo, hágamelo saber a través de este repositorio e investigaré.


da una excepción de puntero nulo en gingebread, la raíz es nula ... ¿alguna solución?
andQlimax

su solución funciona muy bien. pero hay un problema con este enfoque, de hecho sin extender ActionBarActivity, que es obligatorio (de la documentación) para obtener el tema del material en <5.0, colorAccent (solo para hacer un ejemplo) no se aplica a las casillas de verificación en dispositivos <5.0. Esto parece un verdadero dolor ... tal vez tenga que eliminar la actividad de preferencias y usar un diseño lineal para simular una pantalla de preferencias; de lo contrario, no veo ninguna forma de usar el tema material en dispositivos desde el nivel de API 8 al 21. El fragmento de preferencia es "solo"> 11 :(
Qlimax

1
@andQlimax He actualizado mi respuesta con una solución para el problema del tinte
David Passmore

3
@DavidPassmore para mí, la lista de preferencias se superpone en la barra de herramientas
Shashank Srivastava

1
@ShashankSrivastava Esto se refleja si está usando Android 6, estoy trabajando en una solución para esto. Gracias por la actualización.
David Passmore

107

Puede utilizar a PreferenceFragment, como alternativa a PreferenceActivity. Entonces, aquí está el Activityejemplo de envoltura :

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

Y aquí está el archivo de diseño (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>

Y finalmente el PreferenceFragment:

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

Espero que esto ayude a alguien.


39
He probado este enfoque. El problema es que no muestra la barra de herramientas en las pantallas de preferencias de los niños.
Madhur Ahuja

2
Creo que está hablando de PreferenceScreen incrustado en el XML de preferencia raíz.
Lucas S.

5
Me gustó el enfoque, pero lamentablemente no funcionará si la API de destino es menor que API 11
midhunhk

12
No funcionará con ninguno. Prácticamente, no parece haber ninguna forma de crear pantallas de preferencias anidadas, sin herramientas y con diseño material. Si usa un ActionBarActivitypara obtener la barra de herramientas y las funciones relacionadas, no habrá ningún tipo onBuildHeaders()de anulación ni soporte de preferencias reales en la actividad. Si usa el antiguo PreferenceActivity, no tiene la barra de herramientas y las funciones relacionadas (sí, puede tener un Toolbardiseño y pero no puede llamar setSupportActionBar(). Entonces, ya sea con encabezados de preferencia o pantallas de preferencia anidadas, parece que estamos atascados.
Gábor

1
Estoy de acuerdo con el comentario de Gabor. Esta solución no funciona en general. Hay uno mejor a continuación con la emulación de la barra de herramientas (sin ActionBar, pero a quién le importa) y también una nueva biblioteca de soporte lanzada con AppCompatDelegate a bordo.
Eugene Wechsler

48

Actualización completamente nueva.

Con algo de experimentación, parece que he encontrado la solución AppCompat 22.1+ funcional para pantallas de preferencias anidadas.

Primero, como se menciona en muchas respuestas (incluida una aquí), deberá usar el nuevo AppCompatDelegate. Utilice el AppCompatPreferenceActivity.javaarchivo de las demostraciones de soporte ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/app/AppCompatPva simplemente referencia) de él, o copie las funciones relevantes en la suya PreferenceActivity. Mostraré el primer enfoque aquí:

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

El diseño adjunto es bastante simple y habitual ( 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>

Las preferencias en sí mismas se definen como de costumbre ( 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>

No hay una diferencia real con las soluciones en la red hasta este momento. En realidad, puede usar esto incluso si no tiene pantallas anidadas, ni encabezados, solo una pantalla.

Usamos un común PreferenceFragmentpara todas las páginas más profundas, diferenciado por los extraparámetros en los encabezados. Cada página tendrá un XML separado con un PreferenceScreeninterior común ( xml/settings_page1.xmlet al.). El fragmento utiliza el mismo diseño que la actividad, incluida la barra de herramientas.

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

Finalmente, un breve resumen de cómo funciona esto realmente. Lo nuevo AppCompatDelegatenos permite usar cualquier actividad con las funciones de AppCompat, no solo las que se extienden desde las actividades que se encuentran realmente en AppCompat. Esto significa que podemos convertir lo bueno PreferenceActivityen uno nuevo y agregar la barra de herramientas como de costumbre. A partir de ese momento, podemos ceñirnos a las viejas soluciones en cuanto a pantallas de preferencias y encabezados, sin desviarse de la documentación existente. Solo hay un punto importante: no lo use onCreate()en la actividad porque dará lugar a errores. UtilizaronBuildHeaders() para todas las operaciones, como agregar la barra de herramientas.

La única diferencia real es que, y eso es lo que lo hace funcionar con pantallas anidadas, es que puede usar el mismo enfoque con los fragmentos. Puede usarlos de onCreateView()la misma manera, inflando su propio diseño en lugar del sistema, agregando la barra de herramientas de la misma manera que en la actividad.


2
¡Qué gran solución! Esta es la única solución que he encontrado que mostrará la barra de herramientas de material en una PreferenceScreen descendiente. Bien hecho señor.
Cadena

Utilizo el recurso de la biblioteca appcompat para el ícono arriba:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully

Con esta solución, creo que la barra de herramientas se desplazará con el contenido, ¿verdad? Porque es solo un elemento en el ListView interno.
tasomaniac

No con esta nueva solución actualizada. Eso funciona como se esperaba.
Gábor

Curiosamente, esta solución no parece reconocer un en PreferenceFragmentCompatlugar de PreferenceFragment. Configurar un preference-headercon xmlns:app="http://schemas.android.com/apk/res-auto" y luego en app:fragmentlugar de android:fragmentno carga ninguna nueva pantalla de preferencias. Entonces, ¿tiene problemas con la compatibilidad con versiones anteriores ... sugerencias?
fattire

18

Si desea utilizar PreferenceHeaders, puede utilizar el siguiente enfoque:

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>

Puede usar el diseño que prefiera aquí, solo asegúrese de ajustarlo también en el código Java.

Y finalmente, su archivo con encabezados (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 de herramientas); ¿por qué? root.addView (toolbarContainer);
Crossle Song

Vaya, se perdió una variable al cambiar el nombre, la arreglé.
Sven Dubbeld

1
Excelente respuesta. La clave aquí es android.R.id.content, considerando que solíamos pasar un ListViewcon android.R.id.listpara la lista de preferencias en sí (y aún lo hacemos si usamos la forma sin fragmentos, sin encabezado) en su lugar.
davidcsb

2
Creo que es mejor comprobar el código de Android, para ver qué necesita, en lugar de perder el tiempo con la contratación de vistas (eliminar / añadir vistas que tiene). Creo que así es más seguro. Sugiero consultar el archivo "preferencia_lista_contenido".
desarrollador de Android

2
Esta es la mejor respuesta en este hilo. El autor de este artículo lo amplió a la implementación de referencia completa, que utilicé. De hecho, esta es la única solución que funciona para admitir preferencias sofisticadas en su aplicación.
Eugene Wechsler

17

Con el lanzamiento de Android Support Library 22.1.0 y el nuevo AppCompatDelegate, aquí puede encontrar una buena muestra de una implementación de PreferenceActivity con soporte de material con compatibilidad con versiones anteriores.

Actualizar también funciona en pantallas anidadas.

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


1
¡Qué buenas noticias! Entonces, parece que las soluciones basadas en "extender PreferenceActivity" son mejores que las de "extender ActionBarActivity" en esta nueva perspectiva.
Eugene Wechsler

1
@EugeneWechsler Sí, de hecho, ActionBarActivity ha quedado obsoleto ahora.
MrBrightside

¿Funciona esta solución también en pantallas anidadas? ¿Existe un mejor ejemplo?
Tomas

@Tomas No lo he probado todavía, pero también debería funcionar en pantallas anidadas. Si funciona para usted, díganos por favor.
MrBrightside

Muchas gracias ! Trabajando para mí en un Galaxy Nexus (4.3) y en el emulador con pantallas anidadas (lollipop).
Tim Autin

6

Si bien las respuestas anteriores parecen elaboradas, si desea una solución de solución rápida para usar la barra de herramientas con soporte API 7 y superior mientras se extiende PreferenceActivity, obtuve ayuda de este proyecto a continuación.

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

Yo también he estado buscando una solución para agregar la barra de herramientas de soporte v7 ( API 25 ) a AppCompatPreferenceActivity (que es creada automáticamente por AndroidStudio al agregar una SettingsActivity). Después de leer varias soluciones y probar cada una de ellas, luché para que los ejemplos de PreferenceFragment generados se mostraran también con una barra de herramientas.

Una solución modificada que funcionó fue de " Gabor ".

Una de las advertencias a las que me enfrenté fue que 'onBuildHeaders' solo se activa una vez. Si gira un dispositivo (como un teléfono) hacia los lados, la vista se vuelve a crear y PreferenceActivity se vuelve a dejar sin una barra de herramientas, sin embargo, PreferenceFragments conservaría la suya.

Intenté usar 'onPostCreate' para llamar a 'setContentView', mientras que esto funcionaba para recrear la barra de herramientas cuando cambiaba la orientación, PreferenceFragments se mostraba en blanco.

Lo que se me ocurrió aprovecha casi todos los consejos y respuestas que pude leer sobre este tema. Espero que otros también lo encuentren útil.

Empezaremos con Java

Primero en (el generado) AppCompatPreferenceActivity.java modifiqué 'setSupportActionBar' así:

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

En segundo lugar , creé una nueva clase llamada AppCompatPreferenceFragment.java (actualmente es un nombre sin usar, ¡aunque puede que no permanezca así!):

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

Esta es la parte de la respuesta de Gabor que funcionó.

Por último , para obtener coherencia, debemos realizar algunos cambios en 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);
        }
    }
}

Se ha dejado algún código fuera de la actividad por brevedad. Los componentes clave aquí son ' onAttachedFragment ', ' onPostCreate ', y que 'GeneralPreferenceFragment' ahora extiende el ' AppCompatPreferenceFragment ' personalizado en lugar de PreferenceFragment.

Resumen de código : si hay un fragmento, el fragmento inyecta el nuevo diseño y llama a la función modificada 'setSupportActionBar'. Si el fragmento no está presente, SettingsActivity inyecta el nuevo diseño en 'onPostCreate'

Ahora pasemos al XML (muy 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>

Resultado final :

Configuración Actividad

GeneralPreferenceFragment


Parece prometedor pero no me funciona. imgur.com/lSSVCIo (emulador de Pixel C).
Thomas Vos

Enlace de Github para los perezosos
Martin Sing

5

Tengo una solución nueva (posiblemente más ordenada), que utiliza las AppCompatPreferenceActivitymuestras de Support v7. Con este código en la mano, creé mi propio diseño que incluye una barra de herramientas:

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

Luego, en mi AppCompatPreferenceActivity, modifiqué setContentViewpara crear mi nuevo diseño y coloqué el diseño proporcionado dentro de mi 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);
}

Luego simplemente extiendo AppCompatPreferenceActivity, permitiéndome llamar setSupportActionBar((Toolbar) findViewById(R.id.toolbar))e inflar los elementos del menú en la barra de herramientas también. Todo ello manteniendo los beneficios de a PreferenceActivity.


5

Mantengamos las cosas simples y limpias aquí, sin romper ningún diseño incorporado

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

No funcionó para mí, root.getChildAt(0);regresa null.
Eido95

4

Encontré esta solución simple mientras trabajaba en esto. Primero necesitamos crear un diseño para la actividad de configuración.

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>

Asegúrese de agregar una vista de lista con android:id="@android:id/list", de lo contrario arrojaráNullPointerException

El siguiente paso es agregar el onCreatemétodo (Anular) en su actividad de configuración

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

Asegúrese de importar android.suppoer.v7.widget.Toolbar. Esto debería funcionar prácticamente en todas las API por encima de 16 (Jelly Bean y superiores)


1

Me gustaría continuar con la solución marcada de James Cross, ya que después de eso hay un problema de cerrar solo la pantalla anidada activa (PreferenceFragment) en la forma de no cerrar la SettingsActivity también.

En realidad funciona en todas las pantallas anidadas (así que no entiendo la solución de Gábor que probé sin éxito, bueno, funciona hasta cierto punto pero es un lío de múltiples barras de herramientas), porque cuando el usuario hace clic en una pantalla de subpreferencia , solo se cambia el fragmento (ver <FrameLayout android:id="@+id/content_frame" .../>) no la barra de herramientas que permanece siempre activa y visible, sino debe implementar un comportamiento personalizado para cerrar cada fragmento en consecuencia.

En la clase principal SettingsActivityque se extiende ActionBarActivity, se deben implementar los siguientes métodos. Tenga en cuenta que setupActionBar()se llama a privado desdeonCreate()

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

Para el título de la pantalla anidada elegida, debe obtener la referencia de su barra de herramientas y establecer el título apropiado con toolbar.setTitle(R.string.pref_title_general);(por ejemplo).

No es necesario implementar elgetSupportActionBar() en todo el PreferenceFragment ya que solo se cambia la vista del fragmento en cada confirmación, no la barra de herramientas;

No es necesario crear una clase ToolbarPreference falsa para agregar en cada preferencia.xml (ver la respuesta de Gábor).


1

Aquí hay una biblioteca que hice que se basa en el código AOSP, que agrega tinte tanto a las preferencias como a los cuadros de diálogo, agrega una barra de acción y es compatible con todas las versiones de API 7:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary


Al mirar el código, ¿no funciona para las preferencias anidadas ...?
Tim Rae

@TimRae No estoy seguro de haber probado de lo que estás hablando. Por favor explica que quieres decir. ¿Cuál es el escenario que está intentando utilizar exactamente?
desarrollador de Android

Cuando tienes un PreferenceScreeninterior PreferenceScreencomo este
Tim Rae

Nunca usé tal cosa. leyendo los documentos:: developer.android.com/reference/android/preference/… , veo que puede ayudar a pasar de una pantalla a otra. ¿Dices que debería añadirlo también? Yo lo comprobaré . Gracias. Además, use Github la próxima vez para tal cosa (solicitudes y problemas).
desarrollador de Android

Sí, es útil cuando tienes demasiadas preferencias para una sola pantalla ... Ya hay varios lugares en este hilo donde la gente menciona pantallas anidadas, así que creo que este es un lugar apropiado para comentar
Tim Rae

1

Bueno, esto sigue siendo un problema para mí hoy (18 de noviembre de 2015). Probé todas las soluciones de este hilo, pero hubo dos cosas principales que no pude resolver:

  • Las pantallas de preferencias anidadas aparecieron sin barra de herramientas
  • Las preferencias no tenían el aspecto Material en los dispositivos anteriores a Lollipop

Así que terminé creando una biblioteca con una solución más complicada. Básicamente, tuve que aplicar estilos internamente a las preferencias si estamos usando un dispositivo anterior a Lollipop y también manejé las pantallas anidadas usando un fragmento personalizado (restaurando toda la jerarquía anidada aprovechando la clave PreferenceScreen ).

La biblioteca es esta: https://github.com/ferrannp/material-preferences

Y si está interesado en el código fuente (demasiado largo para publicarlo aquí), este es básicamente el núcleo del mismo: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ java / com / fnp / materialpreferences / PreferenceFragment.java

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.