I would like to display a preference screen like the one in the Android settings app : using headers, PreferenceActivity, PreferenceFragment and headers categories.
I wan't this result on a tablet :
And this one on a smartphone :
It works if I just use the basic headers, but if I try to add categories, it works on the smartphone, and crash on the tablet, where I get the exception "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
Bellow are the pieces of code involved. I got them mostly from the Android settings app source.
Any idea ?
Thanks in advance
MainPreferenceActivity :
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));
}
}
}
PreferencesFragment :
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);
}
}
}
preference_headers.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>
Last but not least, 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;
}
}
As bestofbest1 said, the problem was that Android tried to show the first element in the preferences_headers.xml, which did not contain a fragment.
To fix it, I added in MainPreferenceActivity's onCreate the line below (BEFORE super.onCreate) to select a default fragment when using a tablet :
if(onIsMultiPane()) getIntent().putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, PreferencesFragment.class.getName());
I also set a default fragment in PreferencesFragment :
String settings = "DIVE";
if(getArguments() != null) settings = getArguments().getString("settings");
Then a last problem, PreferenceActivity.EXTRA_SHOW_FRAGMENT does not select the header in the left side. To fix it in MainPreferencesActivity save a reference to your headers (in onBuildHeaders), and add :
@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;
}
}
}
}
}
I had issues getting Tim's solution to work for me (the program would still crash). I worked around this in a different way by just selecting the first non-category header by default instead of the first in the list. To do this I overrided the method onGetInitialHeader
in my 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
is just a reference to the header list saved in the call to onBuildHeaders
. It should also be noted that this is only an issue pre 4.3, it has since been fixed. Hope this helps someone out
Maybe first header is default selected menu. If so, it should have fragment attribute to show it right side.
As a simpler form of Tim Autin's solution, disable multi-pane altogether to produce a single-pane, phone-like display on tablets.
public class PreferencesActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if(onIsMultiPane())
getIntent().putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true);
super.onCreate(savedInstanceState);
}
...
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.