如何将支持库中的Action Bar添加到PreferenceActivity中?

128

支持库版本18中增加了Action Bar的兼容性。现在有一个名为ActionBarActivity 的类,用于在旧版Android上创建带有Action Bar的活动。

是否有办法将支持库中的Action Bar添加到PreferenceActivity中?

以前我使用过ActionBarSherlock,它有SherlockPreferenceActivity


也许仍然相关:PreferenceActivity中的ActionBar - Matthias Robbers
1
我刚刚编辑了我的答案,找到了一个基于support-v4的工作PreferenceFragment实现,非常适用于ActionBarActivity。 我自己正在使用它。 - Ostkontentitan
如果您希望,您可以使用我的解决方案: https://github.com/AndroidDeveloperLB/MaterialStuffLibrary - android developer
8个回答

127

编辑:在 appcompat-v7 22.1.0 中,Google 添加了 AppCompatDelegate 抽象类作为代理,您可以使用它来扩展 AppCompat 对任何活动的支持。

使用方法如下:

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

不再进行黑客攻击。代码取自AppCompatPreferenceActivity.java


2
@petrsyn 它确实有效。请查看这里这里,了解如何操作。 - Ľubomír Kučera
1
@swooby 你可能会遭受这个 bug 的影响。 - Ľubomír Kučera
你有没有想过为什么在应用这种方法后,ListPreferences的单选按钮会停止工作?(Android 4.0.3) - Kurovsky
当我注释掉 //getDelegate().installViewFactory(); 后,ListPreference 开始工作了。行为很奇怪。 - Kurovsky
1
好的,我应该在XML文件中放置工具栏?我得到了一个NullPointerException。 - Martin Erlic
显示剩余10条评论

78

目前无法通过AppCompat实现。我已经在内部提交了一个错误报告。


6
谢谢@Chris。拥有这个功能会很不错。 - Pablo
12
@Chris,你知道我们可以期待PreferenceActivity何时添加到ActionBarCompat中吗? - imbryk
3
我认为这个功能被实现的可能性非常小。目前运行较旧版 Android (< 4.0) 的设备数量不到30%,而且这个数字每个月都在下降。 - Roman
1
这是近一年前输入的,还没有解决吗? - Someone Somewhere
1
w.t.f 还在等什么? - Broak
显示剩余6条评论

24

我已经成功创建了一个类似于Google Play商店使用的解决方案。原始回答链接

请在GitHub Repo中查找:这里


与您自己的代码非常相似,但添加了XML以允许设置标题:

继续使用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();
            }
        });
    }

}

结果 :

示例


更新(Gingerbread 兼容性):

正如此处所指出的,Gingerbread 设备在这一行上返回了空指针异常:

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

如果上述内容有任何问题,请让我知道!


更新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;
}

结果:

示例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.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;
}

嵌套偏好设置屏幕

许多人在包含工具栏的嵌套<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是一个痛点,因为它们基于包装对话框,所以我们需要捕获对话框布局以添加工具栏。

工具栏阴影

默认情况下,在 Android 版本低于 21 的设备上导入Toolbar不能实现高程和阴影效果。如果你想要在你的Toolbar上实现高程效果,你需要将它包装在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>

不要忘记在build.gradle文件中将Design Support库添加为依赖项:

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

如果我漏掉了什么,请通过此存储库让我知道,我会进行调查。


1
太好了!像魔法一样地运作 :) - Tobi
为什么这对于嵌套的PreferenceScreen无效,但对于主PreferenceScreen有效? - Tobi
谢谢!你的建议“AppCompat 22.1”对我很有帮助 :) 非常实用! - Martin Pfeffer
@DavidPassmore 为什么要在AppBarLayout中包装它以获取阴影,如果它在v21+上有效?为什么不直接使用“elevation”? - Jemshit Iskenderov
1
为什么PreferenceActivity这么难用???它应该能够节省时间。我还不如自己制作一个常规活动,并手动在线性布局中排列所有设置。他妈的! - Someone Somewhere
显示剩余6条评论

12

@Konstantin,你能添加一些代码示例吗?我已经下载了它,并将导入从android.preference.PreferenceFragment更改为android.support.v4.preference.PreferenceFragment,我看到它在屏幕中间添加了一些标题,但没有在顶部添加ActionBar。 - Gavriel
它不会添加操作栏,这是活动的工作。不幸的是,我手头没有示例代码,但它应该类似于:http://developer.android.com/reference/android/preference/PreferenceFragment.html - Ostkontentitan

3

问题背景:

提问者想知道如何在Android的支持库中,将MenuItem放置在PreferenceActivityActionBar中,因为Android的支持库存在一个bug,不允许这样做。

我的解决方案:

我已经找到了一个比已经提出的方法更加简洁的方式来实现这个目标(并且在Android文档中找到了它):

android:parentActivityName

The class name of the logical parent of the activity. The name here must match the class name given to the corresponding element's android:name attribute.

The system reads this attribute to determine which activity should be started when the use presses the Up button in the action bar. The system can also use this information to synthesize a back stack of activities with TaskStackBuilder.

To support API levels 4 - 16, you can also declare the parent activity with a element that specifies a value for "android.support.PARENT_ACTIVITY". For example:

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

现在按照你通常在onOptionsItemSelected()中做的事情就可以了。由于它是Android文档的一部分,所以没有任何副作用。

祝编码愉快。 :)

更新:

如果您的目标是Lollipop,则这种解决方案将不再适用。如果您正在使用AppCompat,则应查看此答案


如何在设置活动中获取操作栏? - Frozen Crayon
@ArjunU。简单的解决方案-尝试一下并自己找出答案。 - Sufian
@ArjunU。问题在于PreferencesActivity没有任何方法将项目放入ActionBar,特别是返回按钮。我的答案是一个很好的解决办法。 - Sufian
谢谢你的负评。能否解释一下我应该如何改进我的回答或者哪里出了问题? - Sufian
@ArjunU。我已经更新了我的答案。请撤销你的负评。 - Sufian
1
@Sufian 感谢您链接到我的答案,很高兴它能帮助大家 :) - David Passmore

3

PreferenceActivity 与 ABC 集成是不可能的,至少对我来说是这样。我尝试了我能找到的两种可能性,但都没有成功:

选项1:

ActionBarPreferenceActivity 扩展了 PreferenceActivity。当你这样做时,你受到 ActionBarActivityDelegate.createDelegate(ActionBarActivity activity) 的限制。此外,您需要实现不可访问的 ActionBar.Callbacks

选项2:

ActionBarPreferenceActivity 扩展了 ActionBarActivity。这种方法需要重写一个全新的 PreferenceActivityPreferenceManager 以及可能还要重写 PreferenceFragment,也就是需要访问隐藏类,例如 com.android.internal.util.XmlUtils。解决方案只能来自 Google 开发人员实现一个可以添加到任何活动中的 ActionBarWrapper

如果你确实需要一个 preference activity,我现在的建议是使用 ActionBarSherlock

然而,我在这里实现了它。


1
Per4.0带来了崩溃。我已经进行了分支并进行了改进。https://gist.github.com/0e9fe2119b901921b160 - TeeTracker
请检查我的解决方案。它不使用任何变通方法 https://dev59.com/CGMm5IYBdhLWcg3wIsQ2#25603448 - Sufian
它并没有解决任何问题!希望你能理解手头的问题。 - Maxwell Weru

1

我通过使用getActionBar()得到了android.app.Actionbar。一开始它返回了一个空值...然后我去了清单并将主题更改为:

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

然后我又能够使用操作栏了。我猜这只适用于某些版本的构建。因此,您可能需要检查构建编号或检查返回的值是否为空。

对我来说没问题,因为我正在开发的应用程序是针对 ICS/4.0+ 的。


这里有一个更简单的解决方案,它不使用任何变通方法 https://dev59.com/CGMm5IYBdhLWcg3wIsQ2#25603448 - Sufian

0

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