No ActionBar in PreferenceActivity after upgrade to Support Library v21
Asked Answered
A

8

84

After I upgraded to the Support Library v21 my ActionBar in my PreferenceActivity is gone.

Did I miss some attributes in my theme to activate it again? I had some similar trouble with a black ActionBar.

I also tried to add it a little hackish by adding a Toolbar to the root layout, but that did not work as expected.

Aurie answered 22/10, 2014 at 13:55 Comment(20)
You should be using Preference Fragment: developer.android.com/reference/android/preference/…Baring
@JaredBurrows you cant use PreferenceFragment pre 3.0 thoughDrawl
I'm using them too. AFIK I need to link to a PreferenceActivity which uses the PreferenceFragments. However as tycyj points out I need them it also for legacy support.Aurie
@Drawl Yes, there are alternatives: #9783868Baring
@JaredBurrows well yeah with a 3rd party library, there is nothing native for that thoughDrawl
@JaredBurrows so you mean that android-support-v4-preferencefragment supports also the support library v21 I want that matrial like actionbar aka toolbar.Aurie
The toolbar is just a view. You can add it anywhere.Baring
I already tried it the result looked like a normal TextView.Aurie
You can check out the example I've made here: github.com/AndroidDeveloperLB/MaterialPreferenceLibraryKif
@Android that library looks nice, I'll possible use it for my next projectAurie
@Aurie Thank you. You can also see it in action on my "app manager" app. Can you show the project you'll work on? or the project you wanted to have this?Kif
@androiddeveloper I'm developing on this app.Aurie
@Aurie This looks awesome (on the screenshots). It's for fertility ?Kif
@androiddeveloper yes it is. If you have more question, please join this chat.Aurie
@Aurie Well I'm not a woman and not married, and also the app isn't available for my device for some reason, but could be useful in the future. Does it have an English language in it? If so, I might be able to translate to Hebrew.Kif
Chat doesn't work well. The blocking shows as if it's the device's fault and not country. Anyway thanks and good luck !Kif
Preferences in support.v7 23 are pretty much screwed.Whacky
I raised a defect, please vote for it: code.google.com/p/android/issues/detail?id=207718Indeterminable
in case this might help to you. https://mcmap.net/q/176453/-add-action-bar-with-back-button-in-preference-activityFoldboat
i case above solution not work then use my answer . https://mcmap.net/q/176453/-add-action-bar-with-back-button-in-preference-activityFoldboat
S
169

Please find the GitHub Repo: Here


Very Similar to your own code but added xml to allow for set title:

Continuing to use 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 :

example


UPDATE (Gingerbread Compatibility) :

As pointed out here, Gingerbread Devices are returning NullPointerException on this line:

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

FIX:

SettingsActivity.java :

public class SettingsActivity extends PreferenceActivity {

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

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            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();
            }
        });
    }
}

Any issues with the above let me know!


UPDATE 2: TINTING WORKAROUND

As pointed out in many dev notes PreferenceActivity does not support tinting of elements, however by utilising a few internal classes you CAN achieve this. That is until these classes are removed. (Works using appCompat support-v7 v21.0.3).

Add the following imports:

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;

Then override the onCreateView method:

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

example 2


AppCompat 22.1

AppCompat 22.1 introduced new tinted elements, meaning that there is no longer a need to utilise the internal classes to achieve the same effect as the last update. Instead follow this (still overriding 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.JELLY_BEAN) {
        // 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;
}

NESTED PREFERENCE SCREENS

A lot of people are experiencing issues with including the Toolbar in nested <PreferenceScreen />s however, I have found a solution!! - After a lot of trial and error!

Add the following to your 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();
        }
    });
}

The reason that PreferenceScreen's are such a pain is because they are based as a wrapper dialog, so we need to capture the dialog layout to add the toolbar to it.


Toolbar Shadow

By design importing the Toolbar does not allow for elevation and shadowing in pre-v21 devices, so if you would like to have elevation on your Toolbar you need to wrap it in a AppBarLayout:

`settings_toolbar.xml :

<android.support.design.widget.AppBarLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

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

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

Not forgetting to add the add the Design Support library as a dependency in build.gradle file:

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

I have investigated the reported overlapping issue and I cannot reproduce the issue.

The full code in use as above produces the following:

enter image description here

If I am missing something please let me know via this repo and I will investigate.

Stets answered 13/12, 2014 at 3:49 Comment(34)
Nice one: i have used the first approach (Gingerbread is not on my target). Thank you!Flutter
It appears that the up button is broken on Gingerbread using this example.Bledsoe
Update: The issue was that the ToolBar was placed underneath the content view due to the order in which they were added. The fix is to simply call root.addView(bar); after root.addView(content);Bledsoe
Thanks, after 3 nights of frustration I finally got it to work... :)Hitchhike
@TonyMorello Glad to be of assistance :)Stets
Amazing! I tried to solve this problem for a long time and this is by far the easiest approach. How about wrap it in a library?Oppilate
What about with appcompact 22.1.0 ? It should be easier to theme PreferenceActivity now. Maybe you could update the code? :)Duteous
Dropping by to say thanks. Your comment on my question guided me here (I fixed my problem following your answer) and I'm glad you've kept this answer updated. I was getting that NPE on Gingerbread, if by any chance you were curious. ;)Belong
Thank you for your great answer, I gave you some days ago the accepted state. I just copied the tinting part, to make sure that it will look great :)Aurie
Thanks suffian also :)Stets
I recently got a crash report in Dev console. Optimus Pad (l06c) running Android 3.1 crashed and the exception was Caused by: java.lang.ClassCastException: android.widget.LinearLayout cannot be cast to android.widget.ListView and the line it pointed to was LinearLayout root = (LinearLayout) findViewById(android.R.id.list).getParent().getParent().getParent();.Belong
@suffian I will create a virtual machine with 3.1 and test.Stets
I tried this approach and it works great, thank you. But I have an issue when using nested Preferences (by defining a <PreferenceScreen> in xml: The Actionbar won't show here. Is there any workaround (I'd like to keep using a PreferenceActivity).Kirstenkirsteni
@Kirstenkirsteni UPDATE: Wish I could find a solution, but as of yet there is none... :(Stets
@David Passmore Thank you anyways for trying! There are a couple methods mentioned here, I guess I have to use another approach. Too bad, your solution is the cleanest in my opinion, but ultimately we should blame Google..Kirstenkirsteni
@Kirstenkirsteni I will continue to investigate the options of using a custom layout as a possible workaroundStets
@DavidPassmore I'm wondering if it's possible to somehow include this: https://mcmap.net/q/174020/-how-to-add-action-bar-from-support-library-into-preferenceactivityKirstenkirsteni
@DavidPassmore You are a genius! It works like a charm. I had to slightly adapt it to my setup by moving the onPreferenceTreeClick to my PreferenceFragment and it does what it should! (My SettingsActivity inherits from Activity rather than SettingsActivity, the same code will result in the toolbar overlaying the text if I don't - but that's due to my setup and not for you to worry about I think). One note: With AppCombat v22.1.0+ you need to change app:theme to android:theme or the toolbar text will be black, see here.Kirstenkirsteni
I have to correct myself. The android:theme workaround only fixes the black text color at the toolbar for Android 5.0+. It appears as something changed with the new support library (I'm using 22.2.0).Kirstenkirsteni
@Kirstenkirsteni i saw the sam bug, i revertwd to 22.1 to resolveStets
We got a crash on Android 4.0.X, It appears that the gingerbread compatibility code needs to also be run for that version, so: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)Pleadings
On my Android 6, the above code displays the list with overlapping the ToolbarSelfregard
@Selfregard I will investigate as Android 6 has not been tested yet ;)Stets
@DavidPassmore after spending few hours on code, I finally found a very simple solution... :DSelfregard
@DavidPassmore I updated above post with my working codes for API > 10... :)Selfregard
@Selfregard I have reviewed you edit.. thanks for the input. But I am deliberately trying to avoid fragments :)Stets
How to change the Title of the PerferenceActivity to the selected preferencefragment Title? Because the toolbar is added onPostCreate() it does not excist on all the lifecycle events I tried in the fragments...Diluvium
@DalvikVM Can you elaborate as my examples do not use fragments... If there is an issue please raise an issue here github.com/davcpas1234/MaterialSettings/issues/new Thanks! :)Stets
I fixed it by updating the title in a handler.post( new Runnable { ... } ); I included your code in default generated code of Android studio (with fragments).Diluvium
I have followed the first approach given here. But i am getting a transparent toolbar and under that my preference items are displayed. Can anybody provide a solution for this?Majolica
android.R.id.list 's parent is FrameLayout in android NPericycle
It has to be that complicated?Consumedly
Just for the record. If you are using fragments you should modify this code a bit. Otherwise you will encounter overlapping problem that was mentioned. First of all you put the code in fragment's onActivityCreated method. Then you call getParent() twice instead of three times (layouts for PreferenceActivity and PreferenceFragment are slightly different). Other changes are minor and pretty obvious - because the code is in a fragment you use getView().findViewById() and getActivity().getLayoutInflater().Flume
@zys - it isn't. Most probably you were using fragments. Look at my comment above.Flume
T
45

Use AppCompatActivity & PreferenceFragment to solve the issue:

AppCompatActivity:

public class SettingsActivity extends AppCompatActivity {

@Override
protected void onPostCreate(Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    getFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
}}

PreferenceFragment:

public class SettingsFragment extends PreferenceFragment {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    addPreferencesFromResource(R.xml.settings_preferences);
}}
Triazine answered 24/4, 2015 at 18:6 Comment(9)
The ActionBarActivity is deprecated.Aurie
@Aurie its android.support.v7.app.ActionBarActivityTriazine
That one is deprecated since the version 22.1.0Aurie
Just extend AppCompatActivity instead of ActionBarActivity.Burget
Worked for me, and much simpler than the above.Strickland
ListPreferences stop to work inside PreferenceFragment. They do show up but the radio buttons cannot be selected.Morelos
what about for nested preference screens? This doesn't seem to work for themCuracy
For simple preference screens, this is by far the easiest solution to implement.Incinerate
@Curacy for nested preference screens I believe you'd have to re-wrap them in another AppCompatActvity and setup the ActionBar in a similar way. You can have a preference act as a button to launch the nested screen by using <intent android:action="com.foo.MyNestedPreferenceActivity"/> in your preferences.xml file. Note that this would require you to separate your nested preferences XML content into a new file.Hudis
A
7

I ended in adding the Toolbar myself with this simple code:

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

Here is my preferences_toolbar.xml:

<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:minHeight="?attr/actionBarSize"
    app:navigationContentDescription="@string/abc_action_bar_up_description"
    android:background="?attr/colorPrimary"
    app:navigationIcon="?attr/homeAsUpIndicator"
    app:theme="@style/Theme.Toolbar" />
Aurie answered 18/11, 2014 at 13:48 Comment(1)
I guess I put it in onCreate()Aurie
R
6

A better solution than "rolling your own" action bar is to use the AppCompatDelegate class, which lets you bring in an actual-factual action bar from the support library. Here is example code to use it, taken from Ľubomír Kučera's answer to this question.

...
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;
    }
}
Raver answered 11/7, 2015 at 22:48 Comment(1)
a.k.a AppCompatPreferenceActivity from official v7 samples.Par
M
3

Hi I am not if you still have this issue. But I figure I will post what I did to resolve this and hope it will help someone.

1) First off, you might have noticed that PreferenceActivity extends ListActivity which in turns extends from Activity.

According to the comments on the developers blog (http://android-developers.blogspot.com/2014/10/appcompat-v21-material-design-for-pre.html), to use v21 all your activities must inherit from ActionBarActivity. So there is your issue.

2) The steps I used to resolve are :

a) Make sure that you set the Theme of your PreferenceActivity to inherits one ot the Theme.AppCompat Themes.

b) Make your class PreferenceActivity extends ActionBarActivity.

c) Use the PreferenceFragment as your container for all your preferences.

This should resolve it.

Cheers!

Mathison answered 14/11, 2014 at 15:22 Comment(3)
"Make your class PreferenceActivity extends ActionBarActivity" - but HOW?Katabolism
Check out this link here: code.hootsuite.com/…Mathison
Thanks, I think this comes as close as possible. The real pain is that PreferenceActivity has built-in navigation through loadHeadersFromResource, with fragments you have to build the navigation yourself.Katabolism
A
1

I had the same issue. just try to change the Theme of the activity to the one that has any ActionBar in it. adding the following line in the activity tag of SettingsActivity worked out for me :

android:theme="Theme.AppCompat.DayNight.DarkActionBar"

Aggravate answered 3/1, 2019 at 18:30 Comment(0)
A
0

I did something similar to accepted question, but I'm using my layout in other activities as well (MainActivity too), so I cannot just hard-code an arrow in it.

What worked for me:

private void setupActionBar() {
    LinearLayout root = (LinearLayout)findViewById(android.R.id.list).getParent().getParent().getParent();
    Toolbar toolbar = (Toolbar)LayoutInflater.from(this).inflate(R.layout.app_bar, root, false);
    root.addView(toolbar, 0);
    setSupportActionBar(toolbar);
    ActionBar ab = getSupportActionBar();
    ab.setDisplayHomeAsUpEnabled(true);
}
Alliaceous answered 13/11, 2018 at 12:20 Comment(0)
W
-1

An easy way I figured out is overriding:

@android:style/Theme.Material.Light.DarkActionBar

In your style:

<style name="SettingsTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
    <item name="android:colorPrimary">@color/sunshine_blue</item>
    <item name="android:colorPrimaryDark">@color/sunshine_dark_blue</item>
</style>
Wrier answered 5/8, 2015 at 14:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.