Criação de uma tela de preferência com a barra de ferramentas de suporte (v21)

116

Eu estava tendo problemas para usar a nova barra de ferramentas do Material Design na biblioteca de suporte em uma tela de preferência.

Eu tenho um arquivo settings.xml conforme abaixo:

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

As strings são definidas em outro lugar.

James Cross
fonte
stackoverflow.com/a/27455363/2247612 Esta resposta tem uma solução perfeita para Biblioteca de Suporte
harishannam

Respostas:

110

Encontre o Repo GitHub: Aqui


Um pouco tarde para a festa, mas esta é a minha solução que estou usando como uma alternativa 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 :

exemplo


ATUALIZAÇÃO (compatibilidade com pão de mel):

De acordo com os comentários, os dispositivos Gingerbread estão retornando NullPointerException nesta linha:

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

CONSERTAR:

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

Qualquer problema com o acima me avise!


ATUALIZAÇÃO 2: CORREÇÃO DE TINTURA

Como apontado em muitas notas de desenvolvimento PreferenceActivity, não suporta a coloração de elementos, no entanto, utilizando algumas classes internas, você PODE conseguir isso. Isso até que essas classes sejam removidas. (Funciona usando appCompat support-v7 v21.0.3).

Adicione as seguintes importações:

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;

Em seguida, substitua o 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:

exemplo 2


AppCompat 22.1

O AppCompat 22.1 introduziu novos elementos coloridos, o que significa que não há mais necessidade de utilizar as classes internas para obter o mesmo efeito da última atualização. Em vez disso, siga isto (ainda substituindo 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;
}

TELAS DE PREFERÊNCIA ANINHADAS

Muitas pessoas estão tendo problemas para incluir a barra de ferramentas em um aninhado <PreferenceScreen />, entretanto, eu encontrei uma solução !! - Depois de muita tentativa e erro!

Adicione o seguinte ao seu 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();
        }
    });
}

O motivo disso PreferenceScreenser tão chato é porque eles são baseados em uma caixa de diálogo de invólucro, então precisamos capturar o layout da caixa de diálogo para adicionar a barra de ferramentas a ela.


Sombra da Barra de Ferramentas

Por design, a importação de Toolbarnão permite elevação e sombreamento em dispositivos anteriores à v21, então, se você quiser ter elevação em seu, Toolbarprecisa envolvê-lo em 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>

Não esquecendo de adicionar o add a biblioteca Design Support como uma dependência no build.gradlearquivo:

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

Eu investiguei o problema de sobreposição relatado e não consigo reproduzi-lo.

O código completo em uso conforme acima produz o seguinte:

insira a descrição da imagem aqui

Se algo estiver faltando, entre em contato por meio deste repositório e investigarei.

David Passmore
fonte
dá exceção nullpointer em gingebread, root é null..qualquer solução?
andQlimax
sua solução funciona muito bem. mas há um problema com esta abordagem, de fato, sem estender ActionBarActivity que é obrigatório (da documentação) para obter o tema do material em <5.0, colorAccent (apenas para dar um exemplo) não é aplicado a caixas de seleção em dispositivos <5.0. Isso parece uma verdadeira dor ... talvez eu tenha que remover a atividade de preferência e usar um layout linear para simular uma tela de preferência, caso contrário, não vejo nenhuma maneira de usar o tema material em dispositivos de nível API 8 a 21. O fragmento de preferência é "only"> 11 :(
andQlimax
1
@andQlimax Eu atualizei minha resposta com uma solução para o problema de tingimento
David Passmore
3
@DavidPassmore para mim, a lista de preferências está se sobrepondo na barra de ferramentas
Shashank Srivastava
1
@ShashankSrivastava Isso se reflete se você estiver usando o Android 6, estou trabalhando em uma solução para isso. Obrigado pela atualização.
David Passmore
107

Você pode usar um PreferenceFragment, como alternativa a PreferenceActivity. Então, aqui está o Activityexemplo de embalagem :

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

E aqui está o arquivo de 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 finalmente PreferenceFragment:

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

Espero que isso ajude alguém.

James Cross
fonte
39
Eu tentei essa abordagem. O problema é que ele não exibe a barra de ferramentas nas telas de preferências infantis.
Madhur Ahuja
2
Acho que ele está falando sobre PreferenceScreen embutido no XML de preferência de raiz.
Lucas S.
5
Gostei da abordagem, mas infelizmente não funcionará se a API de destino for menor que a API 11
midhunhk
12
Não funcionará com nenhum dos dois. Praticamente, não parece haver nenhuma maneira de criar telas de preferências aninhadas materialmente projetadas, com barra de ferramentas. Se você usar um ActionBarActivitypara obter a barra de ferramentas e funções relacionadas, não haverá nenhum onBuildHeaders()para substituir e nenhum suporte de preferência real na atividade. Se você usa o antigo PreferenceActivity, não tem a barra de ferramentas e funções relacionadas (sim, você pode ter um Toolbarlayout e, mas não pode chamar setSupportActionBar(). Então, seja com cabeçalhos de preferência ou telas de preferência aninhadas, parecemos presos.
Gábor
1
Eu concordo com o comentário de Gabor. Esta solução não funciona em geral. Há um melhor abaixo com emulação da barra de ferramentas (sem ActionBar, mas quem se importa) e também uma nova biblioteca de suporte lançada com AppCompatDelegate a bordo.
Eugene Wechsler
48

Atualização completamente nova.

Com alguma experimentação, parece que encontrei a solução AppCompat 22.1+ funcional para telas de preferência aninhadas.

Primeiro, como é mencionado em muitas respostas (incluindo uma aqui), você precisará usar o novo AppCompatDelegate. Use o AppCompatPreferenceActivity.javaarquivo das demos de suporte ( https://android.googlesource.com/platform/development/+/58bf5b99e6132332afb8b44b4c8cedf5756ad464/samples/Support7Demos/src/com/example/android/supportv7/app/ApptivityPersonalize . a partir dele ou copie as funções relevantes para você PreferenceActivity. Vou mostrar a primeira abordagem aqui:

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

O layout que acompanha é bastante simples e usual ( 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>

As próprias preferências são definidas como de costume ( 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>

Nenhuma diferença real para soluções na rede até este ponto. Na verdade, você pode usar isso mesmo se não tiver telas aninhadas, sem cabeçalhos, apenas uma única tela.

Usamos um comum PreferenceFragmentpara todas as páginas mais profundas, diferenciadas pelos extraparâmetros nos cabeçalhos. Cada página terá um XML separado com um PreferenceScreeninterior comum ( xml/settings_page1.xmlet al.). O fragmento usa o mesmo layout da atividade, incluindo a barra de ferramentas.

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, um breve resumo de como isso realmente funciona. O novo AppCompatDelegatenos permite usar qualquer atividade com recursos do AppCompat, não apenas aquelas que se estendem das atividades realmente no AppCompat. Isso significa que podemos transformar o bom e velho PreferenceActivityem um novo e adicionar a barra de ferramentas como de costume. A partir daí, podemos nos ater às antigas soluções de telas de preferência e cabeçalhos, sem nenhum desvio da documentação existente. Só há um ponto importante: não use onCreate()na atividade porque isso levará a erros. Use onBuildHeaders()para todas as operações, como adicionar a barra de ferramentas.

A única diferença real é, e isso é o que o faz funcionar com telas aninhadas, é que você pode usar a mesma abordagem com os fragmentos. Você pode usá-los onCreateView()da mesma forma, inflando seu próprio layout em vez do do sistema, adicionando a barra de ferramentas da mesma forma que na atividade.

Gábor
fonte
2
Que ótima solução alternativa! Esta é a única solução que encontrei que mostrará a barra de ferramentas do material em uma tela de preferência descendente. Muito bem, senhor.
String
Eu uso o recurso da biblioteca appcompat para o ícone de ativação:R.drawable.abc_ic_ab_back_mtrl_am_alpha
Ridcully
Com esta solução, acho que a Barra de Ferramentas irá rolar com o conteúdo certo? Porque é apenas um item no ListView interno.
tasomaníaco de
Não com esta solução nova e atualizada. Isso funciona exatamente como esperado.
Gábor
Estranhamente, essa solução não parece reconhecer um em PreferenceFragmentCompatvez de PreferenceFragment. Configurar um preference-headercom xmlns:app="http://schemas.android.com/apk/res-auto" e em app:fragmentvez de android:fragmentnão carrega nenhuma nova tela de preferências. Então, tendo problemas com a compatibilidade com versões anteriores ... sugestões?
fattire 01 de
18

Se você deseja usar PreferenceHeaders, pode usar a seguinte abordagem:

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>

Você pode usar qualquer layout de sua preferência aqui, apenas certifique-se de ajustá-lo no código Java também.

E, finalmente, seu arquivo com cabeçalhos (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
fonte
root.addView (barra de ferramentas); porque? root.addView (toolbarContainer);
Crossle Song
Opa, perdi uma variável ao renomear, consertei.
Sven Dubbeld
1
Excelente resposta. A chave aqui é android.R.id.content, considerando que costumávamos passar um ListViewwith android.R.id.listpara a própria lista de preferências (e ainda fazemos se usarmos a forma fragment-less, headerless).
davidcsb
2
Eu acho que é melhor verificar o código do Android, para ver o que ele precisa, em vez de mexer em suas visualizações hirerchy (remover / adicionar visualizações que possui). Eu acho que é mais seguro assim. Eu sugiro verificar o arquivo "preferência_list_content".
desenvolvedor Android
2
Esta é a melhor resposta neste tópico. O autor deste artigo o expandiu para uma implementação de referência completa, que usei. Na verdade, esta é a única solução que funciona para oferecer suporte a preferências sofisticadas em seu aplicativo.
Eugene Wechsler
17

Com o lançamento da Android Support Library 22.1.0 e o novo AppCompatDelegate, aqui você pode encontrar um bom exemplo de uma implementação de PreferenceActivity com suporte de material com compatibilidade com versões anteriores.

Atualizar funciona em telas aninhadas também.

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

Sr. Brightside
fonte
1
Oh, isso é uma ótima notícia! Portanto, parece que as soluções baseadas em "estender PreferenceActivity" são melhores do que aquelas em "estender ActionBarActivity" nesta nova perspectiva.
Eugene Wechsler
1
@EugeneWechsler Sim, de fato, ActionBarActivity foi preterido agora.
MrBrightside
Essa solução funciona também em telas aninhadas? Existe um exemplo melhor?
Tomas
@Tomas ainda não tentei, mas deve funcionar em telas aninhadas também. Se funciona para você, diga-nos por favor.
MrBrightside
Muito obrigado ! Trabalhando para mim em um Galaxy Nexus (4.3) e no emulador com telas aninhadas (pirulito).
Tim Autin
6

Embora as respostas acima pareçam elaboradas, se você quiser uma solução rápida para usar a Barra de Ferramentas com suporte API 7 e superior enquanto a amplia PreferenceActivity, recebi ajuda neste projeto abaixo.

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

Eu também estou procurando uma solução para adicionar a barra de ferramentas de suporte v7 ( API 25 ) à AppCompatPreferenceActivity (que é criada automaticamente pelo AndroidStudio ao adicionar uma SettingsActivity). Depois de ler várias soluções e experimentar cada uma delas, tive dificuldade em fazer com que os exemplos de PreferenceFragment gerados também fossem exibidos com uma barra de ferramentas.

Uma solução modificada que deu certo veio do " Gabor ".

Uma das advertências que enfrentei foi 'onBuildHeaders' dispara apenas uma vez. Se você virar um dispositivo (como um telefone) de lado, a visualização é recriada e PreferenceActivity fica sem uma barra de ferramentas novamente, no entanto, os PreferenceFragments manteriam a sua.

Tentei usar 'onPostCreate' para chamar 'setContentView', enquanto isso funcionava para recriar a barra de ferramentas quando a orientação mudava, PreferenceFragments seria então renderizado em branco.

O que descobri aproveita quase todas as dicas e respostas que pude ler sobre esse assunto. Espero que outros também o considerem útil.

Vamos começar com o Java

Primeiro em AppCompatPreferenceActivity.java (gerado) , modifiquei 'setSupportActionBar' assim:

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

Em segundo lugar , criei uma nova classe chamada AppCompatPreferenceFragment.java (atualmente é um nome não utilizado, embora possa não permanecer assim!):

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 é a parte da resposta de Gabor que funcionou.

Por último , para obter consistência, precisamos fazer algumas alterações em 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);
        }
    }
}

Algum código foi deixado de fora da atividade por questões de brevidade. Os principais componentes aqui são ' onAttachedFragment ', ' onPostCreate ' e o 'GeneralPreferenceFragment' agora estende o ' AppCompatPreferenceFragment ' personalizado em vez de PreferenceFragment.

Resumo do código : se um fragmento estiver presente, o fragmento injeta o novo layout e chama a função modificada 'setSupportActionBar'. Se o fragmento não estiver presente, SettingsActivity injeta o novo layout em 'onPostCreate'

Agora vamos ao XML (muito simples):

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 :

SettingsActivity

GeneralPreferenceFragment

SilverX
fonte
Parece promissor, mas não funciona para mim. imgur.com/lSSVCIo (emulador de Pixel C).
Thomas Vos
Github Link para preguiçosos
Martin Sing
5

Eu tenho uma solução nova (possivelmente mais limpa), que usa as AppCompatPreferenceActivityamostras do Suporte v7. Com este código em mãos, criei meu próprio layout que inclui uma barra de ferramentas:

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

Então, no meu AppCompatPreferenceActivity, alterei setContentViewpara criar um meu novo layout e coloquei o layout fornecido dentro do meu 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);
}

Então eu apenas estendo AppCompatPreferenceActivity, permitindo que eu chame setSupportActionBar((Toolbar) findViewById(R.id.toolbar)), e aumentei os itens de menu na barra de ferramentas também. Tudo isso mantendo os benefícios de a PreferenceActivity.

Bryan
fonte
5

Vamos mantê-lo simples e limpo aqui, sem quebrar nenhum layout embutido

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
fonte
Não funcionou para mim, root.getChildAt(0);retorna null.
Eido95
4

Eu encontrei esta solução simples enquanto trabalhava nisso. Primeiro, precisamos criar um layout para a atividade de configurações.

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>

Certifique-se de adicionar uma visualização de lista com android:id="@android:id/list", caso contrário, ele irá lançarNullPointerException

O próximo passo é adicionar o onCreatemétodo (substituir) na sua atividade de configurações

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

Certifique-se de importar android.suppoer.v7.widget.Toolbar. Isso deve funcionar muito bem em todas as APIs acima de 16 (Jelly Bean e superior)

Apurva
fonte
1

Gostaria de continuar a solução marcada de James Cross, pois depois disso há um problema de fechar apenas a tela aninhada ativa (PreferenceFragment) no sentido de não fechar também o SettingsActivity.

Na verdade funciona em todas as telas aninhadas (então não entendo a solução do Gábor que tentei sem sucesso, bem funciona até certo ponto mas é uma bagunça de várias barras de ferramentas), porque quando o usuário clica em uma tela de subprefeituras , apenas o fragmento é alterado (consulte <FrameLayout android:id="@+id/content_frame" .../>), não a barra de ferramentas que permanece sempre ativa e visível, mas um comportamento personalizado deve ser implementado para fechar cada fragmento de acordo.

Na classe principal SettingsActivityque se estende, ActionBarActivityos seguintes métodos devem ser implementados. Observe que privado setupActionBar()é chamado deonCreate()

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 o título da tela aninhada escolhida, você deve obter a referência de sua barra de ferramentas e definir o título apropriado com toolbar.setTitle(R.string.pref_title_general);(por exemplo).

Não há necessidade de implementar getSupportActionBar()em todos os PreferenceFragment, uma vez que apenas a visualização do fragmento é alterada a cada commit, não a barra de ferramentas;

Não há necessidade de criar uma classe ToolbarPreference falsa para adicionar em cada preferência.xml (veja a resposta de Gábor).

Davideas
fonte
1

Aqui está uma biblioteca que fiz baseada no código AOSP, que adiciona tonalidade às preferências e aos diálogos, adiciona uma barra de ação e oferece suporte a todas as versões da API 7:

https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary

desenvolvedor android
fonte
Olhando para o código, ele não funciona para preferências aninhadas ...?
Tim Rae
@TimRae Não tenho certeza se testei o que você está falando. Por favor, explique o que você quer dizer. Qual é o cenário que você está tentando usar exatamente?
desenvolvedor de Android
Quando você tem um PreferenceScreeninterior PreferenceScreencomo este
Tim Rae
Eu nunca usei tal coisa. lendo os documentos:: developer.android.com/reference/android/preference/… , vejo que pode ajudar a ir entre as telas. Você diz que eu deveria adicionar também? Vou dar uma olhada . Obrigado. Além disso, use o Github da próxima vez para tal (solicitações e problemas).
desenvolvedor Android de
Sim, é útil quando você tem muitas preferências para uma única tela ... Já existem vários lugares neste tópico onde as pessoas mencionam telas aninhadas, então eu acho que aqui é um lugar apropriado para comentar
Tim Rae
1

Bem, isso ainda é um problema para mim hoje (18 de novembro de 2015). Eu tentei todas as soluções deste tópico, mas havia duas coisas principais que não consegui resolver:

  • Telas de preferência aninhadas apareceram sem barra de ferramentas
  • Preferências não tinham aparência de material em dispositivos pré-Lollipop

Então acabei criando uma biblioteca com uma solução mais complicada. Basicamente, eu tive que aplicar estilos internamente às preferências se estivermos usando um dispositivo pré-Lollipop e também manipulei as telas aninhadas usando um fragmento personalizado (restaurando toda a hierarquia aninhada aproveitando a chave PreferenceScreen ).

A biblioteca é esta: https://github.com/ferrannp/material-preferences

E se você estiver interessado no código-fonte (muito longo para postá-lo aqui), este é basicamente o núcleo dele: https://github.com/ferrannp/material-preferences/blob/master/library/src/main/ java / com / fnp / materialpreferences / PreferenceFragment.java

Ferran Negre
fonte