升级到Support Library v21后,PreferenceActivity中没有ActionBar

84

我升级到Support Library v21后,PreferenceActivity中的ActionBar消失了。

我是否错过了一些属性来重新激活它?我曾经遇到过黑色ActionBar类似的问题。

我还尝试通过将Toolbar添加到根布局来进行一些hackish操作,但效果不如预期。


1
@JaredBurrows 在3.0之前不能使用PreferenceFragment。 - tyczj
1
我也在使用它们。据我所知,我需要链接到一个使用PreferenceFragments的PreferenceActivity。然而,正如tycyj指出的,我还需要它们来支持旧版。 - rekire
1
工具栏只是一个视图,你可以将其添加到任何位置。 - Jared Burrows
1
你可以在这里查看我制作的示例:https://github.com/AndroidDeveloperLB/MaterialPreferenceLibrary - android developer
@Android,那个库看起来很不错,我可能会在我的下一个项目中使用它。 - rekire
显示剩余15条评论
8个回答

169
请查看GitHub Repo: 这里
与您自己的代码非常相似,但是添加了XML以允许设置标题: 继续使用PreferenceActivitysettings_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"
    />

设置活动.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();
            }
        });
    }

}

结果:

example


更新 (Gingerbread 兼容性) :

正如在这里所指出的,Gingerbread 设备在此行代码中返回 NullPointerException:

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

修复:

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

如果以上有任何问题,请告诉我!


更新2:着色解决方法

正如许多开发者笔记中指出的那样,PreferenceActivity不支持对元素进行着色,但是通过利用一些内部类,您可以实现这一点。直到这些类被删除为止。(使用appCompat support-v7 v21.0.3可行)。

添加以下导入:

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;

然后重写 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 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;
}

结果:

example 2


AppCompat 22.1

AppCompat 22.1引入了新的着色元素,这意味着不再需要使用内部类来实现与上次更新相同的效果。代替方法是按照以下步骤(仍然覆盖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;
}

嵌套的偏好设置屏幕

很多人在嵌套的 <PreferenceScreen /> 中包含工具栏时会遇到问题,但是我找到了一个解决方法!- 经过了很多的尝试和错误!

将以下内容添加到您的 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();
        }
    });
}

PreferenceScreen非常麻烦的原因是因为它们被作为包装对话框来实现,因此我们需要捕获对话框布局以添加工具栏。


工具栏阴影

按设计,导入Toolbar不允许在v21之前的设备上使用高程和阴影,因此如果您想要在Toolbar上使用高程,则需要将其包装在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>

别忘了在build.gradle文件中将设计支持库作为依赖项添加:

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

我调查了报告的重叠问题,但我无法复现该问题。

使用上述完整代码会产生如下结果:

enter image description here

如果我有遗漏,请通过此 repo让我知道,我将进行调查。


1
谢谢你的出色回答,我几天前已经将其标记为被接受的状态。我只是复制了着色部分,以确保它看起来很棒 :) - rekire
@tony 更新:希望我能找到解决方案,但目前还没有... :( - David Passmore
@David Passmore,无论如何感谢您的尝试!这里提到了几种方法,我想我必须采用另一种方法。太糟糕了,我认为您的解决方案是最干净的,但最终我们应该责怪Google.. - tony
1
我必须纠正自己。 android:theme 解决方案仅适用于 Android 5.0+ 工具栏的黑色文本颜色。似乎新的支持库(我正在使用22.2.0)发生了一些变化。 - tony
4
android.N 中 android.R.id.list 的父布局是 FrameLayout。 - zys
显示剩余30条评论

45

使用AppCompatActivity和PreferenceFragment来解决问题:

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

1
ActionBarActivity已被弃用。 - rekire
@rekire 这是 android.support.v7.app.ActionBarActivity。 - Abdullah
4
请继承AppCompatActivity而不是ActionBarActivity。 - SammyT
1
对我来说很有效,而且比上面的方法简单多了。 - Aaron Blenkush
1
对于简单的偏好设置屏幕,这绝对是最容易实现的解决方案。 - Chris Cirefice
显示剩余4条评论

7
我用这段简单的代码自己添加了工具栏: ```html

我用这段简单的代码自己添加了工具栏:

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

这是我的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" />

我想我把它放在onCreate()里面。 - rekire

6
使用AppCompatDelegate类比自己编写操作栏更好,它可以让您从支持库中导入实际的操作栏。以下是使用它的示例代码,取自Ľubomír Kučera对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;
    }
}

4
官方v7示例中的a.k.a [AppCompatPreferenceActivity](https://github.com/android/platform_development/blob/master/samples/Support7Demos/src/com/example/android/supportv7/app/AppCompatPreferenceActivity.java)。 - Avinash R

3

你好,我不确定你是否还遇到这个问题。但是我想分享一下我解决这个问题的方法,希望能帮助到其他人。

1) 首先,你可能已经注意到PreferenceActivity继承自ListActivity,而ListActivity又继承自Activity。

根据开发者博客上的评论(http://android-developers.blogspot.com/2014/10/appcompat-v21-material-design-for-pre.html),为了使用v21,所有的活动都必须从ActionBarActivity继承。所以这就是你的问题所在。

2) 我解决这个问题的步骤如下:

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.

这应该可以解决它。

干杯!


4
将你的类PreferenceActivity继承自ActionBarActivity。具体步骤如何实现? - vbence
1
谢谢,我认为这已经尽可能接近了。真正的痛点在于PreferenceActivity内置了通过loadHeadersFromResource进行导航,而对于片段,您必须自己构建导航。 - vbence

1

我曾经遇到过同样的问题。只需将活动的主题更改为具有任何ActionBar的主题即可。在SettingsActivity的activity标签中添加以下行即可解决:

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


0

我做了类似于被接受的问题,但是我也在其他活动中使用我的布局(包括MainActivity),所以我不能只是在其中硬编码一个箭头。

对我有用的是:

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

-1

我发现的一种简单方法是覆盖:

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

以你的风格:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接