6

我想显示一个类似于 Android 设置应用程序中的首选项屏幕:使用标题、PreferenceActivity、PreferenceFragment 和标题类别。

我不想在平板电脑上得到这个结果:

在此处输入图像描述

而这个在智能手机上:

在此处输入图像描述

如果我只使用基本标题,它可以工作,但如果我尝试添加类别,它可以在智能手机上工作,并在平板电脑上崩溃,我得到异常 "java.lang.NullPointerException: name == null" :

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity ComponentInfo{fr.ifremer.testandroid/fr.ifremer.testandroid.models.preferences.MainPreferenceActivity}: java.lang.NullPointerException: name == null
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2110)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2135)
    at android.app.ActivityThread.access$700(ActivityThread.java:140)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1237)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:137)
    at android.app.ActivityThread.main(ActivityThread.java:4921)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1038)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:805)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException: name == null
    at java.lang.VMClassLoader.findLoadedClass(Native Method)
    at java.lang.ClassLoader.findLoadedClass(ClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:491)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
    at android.app.Fragment.instantiate(Fragment.java:574)
    at android.preference.PreferenceActivity.switchToHeaderInner(PreferenceActivity.java:1222)
    at android.preference.PreferenceActivity.switchToHeader(PreferenceActivity.java:1255)
    at android.preference.PreferenceActivity.onCreate(PreferenceActivity.java:630)
    at fr.ifremer.testandroid.models.preferences.MainPreferenceActivity.onCreate(MainPreferenceActivity.java:19)
    at android.app.Activity.performCreate(Activity.java:5206)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1094)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2074)
    ... 11 more

下面是涉及的代码片段。我主要从 Android 设置应用程序源获得它们。

任何的想法 ?

提前致谢


主要偏好活动:

public class MainPreferenceActivity extends PreferenceActivity {

    private static List<Header> _headers;

    @Override
    public void onBuildHeaders(List<Header> headers) {

        _headers = headers;
        loadHeadersFromResource(R.xml.preference_headers, headers);
    }

    @Override
    public void setListAdapter(ListAdapter adapter) {

        if (adapter == null) {
            super.setListAdapter(null);
        } else {
            super.setListAdapter(new HeaderAdapter(this, _headers));
        }
    }
}

首选项片段:

public class PreferencesFragment extends PreferenceFragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String settings = getArguments().getString("settings");

        if (settings.equals("DIVE")) {

            addPreferencesFromResource(R.xml.preference_dive_tile);
        }
        else if (settings.equals("MAP")) {

            addPreferencesFromResource(R.xml.preference_map_tile);
        }
    }
}

首选项标题.xml:

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >

    <header
        android:id="@+id/header_section_1"
        android:title="Section 1" />

    <header
        android:fragment="fr.ifremer.testandroid.models.preferences.PreferencesFragment"
        android:summary="DIVE summary"
        android:title="DIVE title" >
        <extra
            android:name="settings"
            android:value="DIVE" />
    </header>
    <header
        android:fragment="fr.ifremer.testandroid.models.preferences.PreferencesFragment"
        android:summary="MAP summary"
        android:title="MAP title" >
        <extra
            android:name="settings"
            android:value="MAP" />
    </header>

</preference-headers>

最后但并非最不重要的一点是 HeaderAdapter :

public class HeaderAdapter extends ArrayAdapter<Header> {

    static final int HEADER_TYPE_CATEGORY = 0;
    static final int HEADER_TYPE_NORMAL = 1;
    private static final int HEADER_TYPE_COUNT = HEADER_TYPE_NORMAL + 1;

    private LayoutInflater mInflater;

    private static class HeaderViewHolder {
        ImageView icon;
        TextView title;
        TextView summary;
    }

    public HeaderAdapter(Context context, List<Header> objects) {

        super(context, 0, objects);

        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    static int getHeaderType(Header header) {

        if (header.fragment == null && header.intent == null) return HEADER_TYPE_CATEGORY;
        else return HEADER_TYPE_NORMAL;
    }

    @Override
    public int getItemViewType(int position) {
        Header header = getItem(position);
        return getHeaderType(header);
    }

    @Override
    public boolean areAllItemsEnabled() { return false; /* because of categories */ }

    @Override
    public boolean isEnabled(int position) { return getItemViewType(position) != HEADER_TYPE_CATEGORY; }

    @Override
    public int getViewTypeCount() { return HEADER_TYPE_COUNT; }

    @Override
    public boolean hasStableIds() { return true; }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        HeaderViewHolder holder;
        Header header = getItem(position);
        int headerType = getHeaderType(header);
        View view = null;

        if (convertView == null) {

            holder = new HeaderViewHolder();

            switch (headerType) {

                case HEADER_TYPE_CATEGORY:

                    view = new TextView(getContext(), null, android.R.attr.listSeparatorTextViewStyle);
                    holder.title = (TextView) view;
                    break;

                case HEADER_TYPE_NORMAL:

                    view = mInflater.inflate(R.layout.preference_header_item, parent, false);
                    holder.icon = (ImageView) view.findViewById(R.id.icon);
                    holder.title = (TextView) view.findViewById(R.id.title);
                    holder.summary = (TextView) view.findViewById(R.id.summary);
                    break;
            }

            view.setTag(holder);
        }
        else {

            view = convertView;
            holder = (HeaderViewHolder) view.getTag();
        }

        // All view fields must be updated every time, because the view may be recycled
        switch (headerType) {

            case HEADER_TYPE_CATEGORY :

                holder.title.setText(header.getTitle(getContext().getResources()));
                break;

            case HEADER_TYPE_NORMAL :

                holder.icon.setImageResource(header.iconRes);

                holder.title.setText(header.getTitle(getContext().getResources()));
                CharSequence summary = header.getSummary(getContext().getResources());

                if (!TextUtils.isEmpty(summary)) {

                    holder.summary.setVisibility(View.VISIBLE);
                    holder.summary.setText(summary);
                }
                else {
                    holder.summary.setVisibility(View.GONE);
                }
                break;
        }

        return view;
    }
}
4

4 回答 4

6

正如bestofbest1所说,问题在于Android试图显示preferences_headers.xml中的第一个元素,该元素不包含片段。

为了解决这个问题,我在 MainPreferenceActivity 的 onCreate 中添加了下面的行(在 super.onCreate 之前)以在使用平板电脑时选择默认片段:

if(onIsMultiPane()) getIntent().putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, PreferencesFragment.class.getName());

我还在 PreferencesFragment 中设置了一个默认片段:

String settings = "DIVE";
if(getArguments() != null) settings = getArguments().getString("settings");

然后是最后一个问题,PreferenceActivity.EXTRA_SHOW_FRAGMENT 没有选择左侧的表头。要在 MainPreferencesActivity 中修复它,请保存对标题的引用(在 onBuildHeaders 中),然后添加:

@Override
protected void onResume() {

    // Call super :
    super.onResume();

    // Select the displayed fragment in the headers (when using a tablet) :
    // This should be done by Android, it is a bug fix
    if(_headers != null) {

        final String displayedFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
        if (displayedFragment != null) {
            for (final Header header : _headers) {
                if (displayedFragment.equals(header.fragment)) {
                    switchToHeader(header);
                    break;
                }
            }
        }
    }
}
于 2013-05-28T13:51:10.213 回答
2

我在让 Tim 的解决方案为我工作时遇到了问题(程序仍然会崩溃)。我以不同的方式解决了这个问题,默认情况下只选择第一个非类别标题而不是列表中的第一个。为此,我覆盖了我的onGetInitialHeader方法PreferenceActivity

@Override
public Header onGetInitialHeader() {
    for (int i = 0; i < mHeaders.size(); i++) {
        Header h = mHeaders.get(i);
        if (!isCategory(h)) {
            return h;
        }
    }
}

protected static boolean isCategory(Header h) {
    return h.fragment == null;
}

mHeaders只是对保存在调用中的标题列表的引用onBuildHeaders。还应该注意的是,这只是 4.3 之前的一个问题,它已经被修复了。希望这可以帮助某人

于 2013-10-04T17:18:09.153 回答
1

也许第一个标题是默认选择的菜单。如果是这样,它应该有片段属性来显示它的右侧。

于 2013-04-02T08:03:20.163 回答
1

作为 Tim Autin 解决方案的一种更简单的形式,完全禁用多窗格以在平板电脑上生成类似手机的单窗格显示。

public class PreferencesActivity extends PreferenceActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if(onIsMultiPane())
            getIntent().putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true);
        super.onCreate(savedInstanceState);
    }
...
}
于 2015-11-10T02:32:36.240 回答