Android – Заголовки категорий в PreferenceActivity с PreferenceFragment

Я хотел бы отобразить экран предпочтений, подобный таковому в приложении настроек Android: с использованием заголовков, категорий PreferenceActivity, PreferenceFragment и headers.

Я не получил этого результата на планшете:

Введите описание изображения здесь

И это на смартфоне:

Введите описание изображения здесь

Он работает, если я просто использую основные заголовки, но если я пытаюсь добавить категории, он работает на смартфоне и падает на планшет, где я получаю исключение «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.

Есть идеи ?

заранее спасибо


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> 

И последнее, но не менее важное: 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; } } 

Возможно, первый заголовок выбран по умолчанию. Если это так, он должен иметь атрибут фрагмента, чтобы показать его правую сторону.

Как сказал bestofbest1, проблема заключалась в том, что Android попытался показать первый элемент в файле preferences_headers.xml, который не содержал фрагмент.

Чтобы исправить это, я добавил в MainPreferenceActivity onCreate строку ниже (BEFORE 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; } } } } } 

У меня были проблемы с тем, чтобы решение Тима работало для меня (программа все равно будет разбиваться). Я работал над этим по-другому, просто выбрав первый заголовок без категории по умолчанию вместо первого в списке. Для этого я переопределил метод 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, с тех пор он был исправлен. Надеюсь, это поможет кому-то

В качестве более простой формы решения Тима Аутина отключите многопанельную панель, чтобы создать одноплатный телефонный дисплей на планшетах.

 public class PreferencesActivity extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { if(onIsMultiPane()) getIntent().putExtra(PreferenceActivity.EXTRA_NO_HEADERS, true); super.onCreate(savedInstanceState); } ... } 
Intereting Posts