Como adicionar a Barra de Ação da biblioteca de suporte ao PreferenceActivity?

128

A compatibilidade da Barra de ação foi adicionada à biblioteca de suporte, revisão 18. Agora ela possui ActionBarActivityclasse para criar atividades com a Barra de ação em versões mais antigas do Android.

Existe alguma maneira de adicionar a Barra de Ação da biblioteca de suporte PreferenceActivity?

Anteriormente eu usei ActionBarSherlock e ele tem SherlockPreferenceActivity.

romano
fonte
talvez ainda seja relevante: ActionBar em PreferenceActivity
Matthias Robbers
1
Acabei de editar minha resposta, encontrei uma implementação de fragmento de preferências de trabalho baseada no support-v4 que funciona muito bem com o ActionBarActivity. Estou usando sozinho.
Ostkontentitan
Se desejar, você pode usar a minha solução: github.com/AndroidDeveloperLB/MaterialStuffLibrary
desenvolvedor android

Respostas:

128

EDIT: No appcompat-v7 22.1.0, o Google adicionou a classe abstrata AppCompatDelegate como um delegado que você pode usar para estender o suporte do AppCompat a qualquer atividade.

Use-o assim:

...
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
...

public class SettingsActivity extends PreferenceActivity {

    private AppCompatDelegate mDelegate;

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

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

    public ActionBar getSupportActionBar() {
        return getDelegate().getSupportActionBar();
    }

    public void setSupportActionBar(@Nullable Toolbar toolbar) {
        getDelegate().setSupportActionBar(toolbar);
    }

    @Override
    public MenuInflater getMenuInflater() {
        return getDelegate().getMenuInflater();
    }

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

    @Override
    public void addContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().addContentView(view, params);
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        getDelegate().onPostResume();
    }

    @Override
    protected void onTitleChanged(CharSequence title, int color) {
        super.onTitleChanged(title, color);
        getDelegate().setTitle(title);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        getDelegate().onConfigurationChanged(newConfig);
    }

    @Override
    protected void onStop() {
        super.onStop();
        getDelegate().onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        getDelegate().onDestroy();
    }

    public void invalidateOptionsMenu() {
        getDelegate().invalidateOptionsMenu();
    }

    private AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, null);
        }
        return mDelegate;
    }
}

Chega de hackers. Código extraído de AppCompatPreferenceActivity.java .

Ľubomír Kučera
fonte
coloque seus trechos de código essenciais em sua resposta. para evitar um possível problema de link quebrado.
Yohanes Khosiawan,
obrigado, isso funciona para mim - eu mudaria o novo LinearLayout na primeira chamada de (ViewGroup) getWindow().getDecorView().getRootView()
aumento
2
@petrsyn Na verdade, ele funciona. Olhe aqui e aqui para descobrir como você deve fazê-lo.
22614
1
@swooby Você pode sofrer com esse bug.
2191515
1
Ok, então onde coloco a barra de ferramentas no xml? Estou recebendo uma NullPointerException
Martin Erlic 21/11
78

No momento, não há como alcançar com o AppCompat. Eu abri um bug internamente.

Chris Banes
fonte
6
Obrigado @ Chris. Será bom ter esse recurso.
266 Pablo Pablo
12
@ Chris, você tem alguma idéia de quando poderíamos esperar PreferenceActivityser adicionados ActionBarCompat?
imbryk
3
Eu acho muito improvável que esse recurso seja implementado. O número de dispositivos executando versões mais antigas do Android (<4.0) é inferior a 30% neste momento, e esse número está diminuindo todos os meses.
Roman
1
Isso foi inserido quase um ano atrás e sem resolução?
Alguém em algum lugar
1
wtf ainda está esperando?
Broak
24

Consegui criar uma solução alternativa semelhante à usada pela Google Play Store. Link para a resposta original

Encontre o repositório GitHub: Aqui


Muito parecido com o seu próprio código, mas adicionou xml para permitir o título do conjunto:

Continuando a usar 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


UPDATE (Compatibilidade com Gingerbread):

Conforme indicado aqui , 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();
            }
        });
    }
}

Quaisquer problemas com o acima exposto me avise!


ATUALIZAÇÃO 2: SOLUÇÃO DE SOLUÇÃO

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 o 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 é mais necessário 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 NESTADAS

Muitas pessoas estão enfrentando problemas com a inclusão da barra de ferramentas em <PreferenceScreen />s aninhados. No entanto, 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 desse problema PreferenceScreené que eles são baseados em uma caixa de diálogo de wrapper, portanto, precisamos capturar o layout da caixa de diálogo para adicionar a barra de ferramentas a ele.


Sombra da barra de ferramentas

Por design, a importação de Toolbarnão permite elevação e sombreamento em dispositivos anteriores à v21, portanto, se você deseja elevar o seu, Toolbarprecisará 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 a biblioteca de suporte a design 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 sobreposto relatado e não consigo reproduzi-lo.

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

insira a descrição da imagem aqui

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

David Passmore
fonte
1
Ótimo! Funciona como um encanto :)
Tobi
Por que isso não funciona para o PreferenceScreen aninhado, mas apenas para o PreferenceScreen principal?
Tobi
@Tobi Atualizei minha resposta para resolver o problema aninhado e expliquei o porquê. :)
precisa
Obrigado! Seu conselho "AppCompat 22.1" fez o truque para mim :) Muito útil!
Martin Pfeffer 23/06
1
por que é PreferenceActivity tão difícil usar ??? isso deve economizar tempo. Eu também poderia fazer uma atividade regular e organizar manualmente todas as configurações em um layout linear. Fuuuuck!
Alguém em algum lugar
12

Foi encontrada uma implementação do PreferenceFragment com base no fragmento support-v4:

https://github.com/kolavar/android-support-v4-preferencefragment

Edit: Acabei de testar e está funcionando muito bem!

Ostkontentitan
fonte
@ Konstantin, você pode adicionar algum exemplo de código? Fiz o download, mudei a importação de android.preference.PreferenceFragment para android.support.v4.preference.PreferenceFragment e vejo que ele adicionou alguns cabeçalhos no meio da tela, mas não a ActionBar na parte superior
Gavriel
Ele não adiciona a barra de ação que é o trabalho da atividade. Unfortunally eu não tenho nenhuma SampleCode na mão, mas ele deve funcionar semelhante a: developer.android.com/reference/android/preference/...
Ostkontentitan
3

A integração PreferenceActivitycom o ABC não é possível, pelo menos para mim. Tentei as duas possibilidades que encontrei, mas nenhuma funcionou:

Opção 1:

ActionBarPreferenceActivityse estende PreferenceActivity. Quando você faz isso, fica restrito ActionBarActivityDelegate.createDelegate(ActionBarActivity activity). Além disso, você precisa implementar o ActionBar.Callbacksque não está acessível

Opção 2:

ActionBarPreferenceActivityse estende ActionBarActivity. Esta abordagem requer reescrever um novo inteiro PreferenceActivity, PreferenceManagere pode ser PreferenceFragmentque significa que você precisa ter acesso a aulas escondidos como com.android.internal.util.XmlUtils A solução para este só pode vir de Google devs implementar um ActionBarWrapperque pode ser adicionado a qualquer atividade.

Se você realmente precisa de uma atividade preferencial, meu conselho por enquanto é ActionBarSherlock.

No entanto, eu consegui implementá-lo aqui .

Maxwell Weru
fonte
1
Per4.0 traz falha. Eu bifurquei e melhorei. gist.github.com/0e9fe2119b901921b160
TeeTracker
Verifique minha solução. Ele não usa nenhuma solução alternativa stackoverflow.com/a/25603448/1276636
Sufian
Não resolve nada! Espero que você entenda o assunto em questão
Maxwell Weru
3

Antecedentes do problema:

O OP quer saber como podemos colocar MenuItem s na ActionBarda PreferenceActivitypara pré-Honeycomb porque biblioteca de suporte do Android tem um bug que não permite que isso aconteça.

Minha solução:

Encontrei uma maneira muito mais limpa, do que o proposto anteriormente, de atingir a meta (e a encontrei no Android Docs ):

android:parentActivityName

O nome da classe do pai lógico da atividade. O nome aqui deve corresponder ao nome da classe dado ao atributo android: name do elemento correspondente.

O sistema lê esse atributo para determinar qual atividade deve ser iniciada quando o uso pressiona o botão Para cima na barra de ação. O sistema também pode usar essas informações para sintetizar uma pilha traseira de atividades com o TaskStackBuilder.

Para oferecer suporte aos níveis de API 4 - 16, você também pode declarar a atividade pai com um elemento que especifica um valor para "android.support.PARENT_ACTIVITY". Por exemplo:

<activity
    android:name="com.example.app.ChildActivity"
    android:label="@string/title_child_activity"
    android:parentActivityName="com.example.myfirstapp.MainActivity" >
    <!-- Parent activity meta-data to support API level 4+ -->
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value="com.example.app.MainActivity" />
</activity>

Agora faça o que você normalmente faria em seu onOptionsItemSelected() . Como faz parte do Android Docs, não tem efeitos colaterais.

Feliz codificação. :)

Atualizar:

Essa solução não funciona mais se você estiver segmentando o Lollipop. Se você estiver usando AppCompat, esta resposta é o que você deveria procurar.

Sufiano
fonte
Como isso ajuda a obter a barra de ação na atividade Preferências?
Frozen Crayon
@ArjunU. solução fácil - experimente e descubra.
Sufian
@ArjunU. O problema era que PreferencesActivitynão havia como colocar itens ActionBar, especialmente o botão Voltar. Minha resposta é uma boa solução para isso.
Sufian
Obrigado pelo voto negativo. Alguma explicação de como eu poderia melhorar minha resposta ou o que estava errado?
Sufian
1
Graças @Sufian para ligar para a minha resposta, contente ele está ajudando todo mundo:)
David Passmore
1

Eu era capaz de android.app.Actionbarusar getActionBar(). Ele retornou um valor nulo no início ... depois fui ao manifesto e alterei o tema para:

android:theme="@style/Theme.AppCompat"

Então pude ter a barra de ação novamente. Estou assumindo que isso funcionará apenas para determinados níveis de compilação. Portanto, convém verificar o número da compilação ou verificar se o valor retornado é nulo.

Tudo bem para mim, porque o aplicativo no qual estou trabalhando é para ICS/4.0+.

RCB
fonte
Aqui está uma solução mais simples, que não usa nenhuma solução alternativa stackoverflow.com/a/25603448/1276636
Sufian