26

I am using preference headers to create settings activity using PreferenceActivity. I am trying to divide the headers into categories/groups, like this one (there are categories Wireless & Networks, Device, Personal, ...):

Anyway, even that Android Developers site is about this way of creating preference activity, I couldn't find any way how to create the same preferences activity like they have on the image. The only I managed to do is simple list of preference headers.

The only thing I have found is this, but that works kinda... strange. So that does not seem as an option.

So my question is: How to create PreferenceActivity using preference headers with possibility of dividing headers into categories and with possibility of using master on/off switches?

Some of my code:

preference_headers.xml:

<?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
    <header 
        android:fragment="cz.vse.myevents.activity.SettingsActivity$EventsFragment"
        android:title="@string/settings_events"
        android:icon="@android:drawable/ic_menu_agenda" />
    <header 
        android:fragment="cz.vse.myevents.activity.SettingsActivity$OrganizationsFragment"
        android:title="@string/settings_subscribed_organizations"
        android:icon="@android:drawable/ic_menu_view"  />
</preference-headers>

SettingsActivity:

@Override
public void onBuildHeaders(List<Header> target) {
    super.onBuildHeaders(target);
    loadHeadersFromResource(R.xml.preference_headers, target);
}

I am not posting fragments resources, think it's unnecessary.

Community
  • 1
  • 1
Erveron
  • 1,908
  • 2
  • 25
  • 48

5 Answers5

10

This is preference category example, you can use preference category and set respective fragment and achieve this, let me know if I misunderstood your case.

Here is sample layout

<PreferenceCategory android:title="Heading1">
        <Preference 
            android:title="title1"
            android:summary="summary1"
            android:key="keyName"/>

       <Preference 
            android:title="title2"
            android:summary="summary2"
            android:key="keyName"/>
</PreferenceCategory>

<PreferenceCategory android:title="Heading2">
        <Preference 
            android:title="title3"
            android:summary="summary3"
            android:key="keyName"/>
</PreferenceCategory>
Pawan Maheshwari
  • 15,088
  • 1
  • 48
  • 50
  • This creates titles inside the fragment (property pane). The question is about creating titles in the headers secion (created by the `loadHeadersFromResource`). – vbence Dec 30 '14 at 21:10
5

Seems the best solution is creation of three different blocks of code - one for pre-Honeycomb, one for post-Honeycomb and one for tablets.

Usage of preference headers is effective on tablets only, so they remain on tablets only. No grouping is used here.

Preference headers on post-Honeycomb are kinda useless, so the best is usage of typical PreferenceScreen in a PreferenceFragment. Groups can be made easily by PreferenceCategory.

And finally, for the pre-Honeycomb, the deprecated way without using PrefrenceFragment is the only way.

Sadly there is a lot of code duplication, but the UnifiedPreference library mentioned in the answer by Leandros is buggy - it ignores PreferenceFragment totally so it is useless (at least for me).

Erveron
  • 1,908
  • 2
  • 25
  • 48
2

To elaborate on the answer from T. Folsom, here is my implementation:

res/layout/preference_header_item.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:attr/activatedBackgroundIndicator"
    android:baselineAligned="false"
    android:gravity="center_vertical"
    android:minHeight="48dp"
    android:paddingRight="?android:attr/scrollbarSize" >

    <LinearLayout
        android:layout_width="@dimen/header_icon_width"
        android:layout_height="wrap_content"
        android:layout_marginLeft="6dip"
        android:layout_marginRight="6dip" >

        <ImageView
            android:id="@+id/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
    </LinearLayout>

    <RelativeLayout
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_marginBottom="6dip"
        android:layout_marginLeft="2dip"
        android:layout_marginRight="6dip"
        android:layout_marginTop="6dip"
        android:layout_weight="1" >

        <TextView
            android:id="@+android:id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:ellipsize="marquee"
            android:fadingEdge="horizontal"
            android:singleLine="true"
            android:textAppearance="?android:attr/textAppearanceMedium" />

       <TextView
            android:id="@+android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@android:id/title"
            android:ellipsize="end"
            android:maxLines="2"
            android:textAppearance="?android:attr/textAppearanceSmall" />
    </RelativeLayout>

</LinearLayout>

res/values/dimens.xml

<resources>

    <dimen name="header_icon_width">28dp</dimen>

</resources>

in your PreferenceActivity class:

    @Override
protected void onCreate(Bundle savedInstanceState) {

    if (savedInstanceState != null) {
        /*
         *  the headers must be restored before the super call in order
         *  to be ready for the call to setListAdapter() 
         */
        if (savedInstanceState.containsKey("headers")) {
            setHeaders((ArrayList<Header>)savedInstanceState.getSerializable("headers"));
        }
    }

    // as suggest by https://stackoverflow.com/questions/15551673/android-headers-categories-in-preferenceactivity-with-preferencefragment
    if(onIsMultiPane()) getIntent().putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, PreferencesFragment.class.getName());

    super.onCreate(savedInstanceState);

    ...

}

@Override
protected void onResume() {
    super.onResume();

    // https://stackoverflow.com/questions/15551673/android-headers-categories-in-preferenceactivity-with-preferencefragment
    // Select the displayed fragment in the headers (when using a tablet) :
    // This should be done by Android, it is a bug fix
    if(getHeaders() != null) {

        final String displayedFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
        if (displayedFragment != null) {
            for (final Header header : getHeaders()) {
                if (displayedFragment.equals(header.fragment)) {
                    switchToHeader(header);
                    break;
                }
            }
        }
    }

    ...

}

/**
 * Populate the activity with the top-level headers.
 */
@Override
public void onBuildHeaders(List<Header> target) {
    // we have to save the headers as the API call getHeaders() is hidden.
    setHeaders(target);
    loadHeadersFromResource(R.xml.settings_headers, target);
}

private List<Header> headers;

private void setHeaders(List<Header> headers) {
    this.headers = headers;
}

private List<Header> getHeaders() {
    return headers;
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putSerializable("headers", (ArrayList<PreferenceActivity.Header>)headers);
    super.onSaveInstanceState(outState);
}

@Override
public void setListAdapter(ListAdapter adapter) {
    if (adapter == null) {
        super.setListAdapter(null);
    } else {
        super.setListAdapter(new HeaderAdapter(this, getHeaders()));
    }
}

private static 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 static class HeaderViewHolder {
        ImageView icon;
        TextView title;
        TextView summary;
    }

    private LayoutInflater mInflater;

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

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

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

    }

    @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(android.R.id.title);
                holder.summary = (TextView) view
                        .findViewById(android.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;
    }

}

With all this code in place, creating headers is simply:

<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
     <header android:title="atitle" />
</preference-headers>

Hope this helps someone. I know it took me some time to get working properly.

Community
  • 1
  • 1
cYrixmorten
  • 7,110
  • 3
  • 25
  • 33
1

This is actually pretty straightforward. From what I found, the root PreferenceActivity itself doesn't support adding category/section titles to it, it seems you can only add Headers -- which isn't very interesting.

So what you first need to do is not do any heavy lifting in your PreferenceActivity itself and go straight into loading a PreferenceFragment:

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

    setTitle("Settings");

    // Display the fragment as the main content.
    getFragmentManager().beginTransaction()
            .replace(android.R.id.content, new PreferencesFragment())
            .commit();

}

public static class PreferencesFragment extends PreferenceFragment {

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

        addPreferencesFromResource(R.xml.prefs);
    }
}

Once you've done this you can now do all the work in your PreferenceFragment, and the great news is that you can now use categories!

Your R.xml.prefs file should look something like this:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory
        android:summary="Login credentials"
        android:title="Login credentials" >
        <EditTextPreference
            android:key="username"
            android:summary="Username"
            android:title="Username" />
        <EditTextPreference
            android:key="password"
            android:summary="Password"
            android:title="Password" />
    </PreferenceCategory>

    <PreferenceCategory
        android:summary="Settings"
        android:title="Settings" >
        <CheckBoxPreference
            android:key="persist"
            android:summary="Yes/No"
            android:title="Keep me signed in" />

    </PreferenceCategory>
</PreferenceScreen>

Just create a PreferenceCategory for each new category you wish to add.

Jonathan Ellis
  • 5,221
  • 2
  • 36
  • 53
-2

AOSP settings_headers.xml implementation:

<preference-headers
        xmlns:android="http://schemas.android.com/apk/res/android">


    <!-- WIRELESS and NETWORKS -->
    <header android:title="@string/header_category_wireless_networks" />

    <!-- Wifi -->
    <header
        android:id="@+id/wifi_settings"
        android:fragment="com.android.settings.wifi.WifiSettings"
        android:title="@string/wifi_settings_title"
        android:icon="@drawable/ic_settings_wireless" />

    <!-- Bluetooth -->
    <header
        android:id="@+id/bluetooth_settings"
        android:fragment="com.android.settings.bluetooth.BluetoothSettings"
        android:title="@string/bluetooth_settings_title"
        android:icon="@drawable/ic_settings_bluetooth2" />

    <!-- Data Usage -->
    <header
        android:id="@+id/data_usage_settings"
        android:fragment="com.android.settings.DataUsageSummary"
        android:title="@string/data_usage_summary_title"
        android:icon="@drawable/ic_settings_data_usage" />

    <!-- Operator hook -->
    <header
        android:fragment="com.android.settings.WirelessSettings"
        android:id="@+id/operator_settings">
        <intent android:action="com.android.settings.OPERATOR_APPLICATION_SETTING" />
    </header>

    <!-- Other wireless and network controls -->
    <header
        android:id="@+id/wireless_settings"
        android:title="@string/radio_controls_title"
        android:breadCrumbTitle="@string/wireless_networks_settings_title"
        android:fragment="com.android.settings.WirelessSettings"
        android:icon="@drawable/empty_icon" />

   <!-- Ethernet -->
   <header
        android:id="@+id/ethernet_settings"
        android:title="@string/eth_radio_ctrl_title"
        android:icon="@drawable/ic_settings_ethernet"
        android:fragment="com.android.settings.ethernet.EthernetSettings"/>

    <!-- DEVICE -->
    <header android:title="@string/header_category_device" />

    <!-- Sound -->
    <header
        android:id="@+id/sound_settings"
        android:icon="@drawable/ic_settings_sound"
        android:fragment="com.android.settings.SoundSettings"
        android:title="@string/sound_settings" />

    <!-- Display -->
    <header
        android:id="@+id/display_settings"
        android:icon="@drawable/ic_settings_display"
        android:fragment="com.android.settings.DisplaySettings"
        android:title="@string/display_settings" />

    <!-- Storage -->
    <header
        android:id="@+id/storage_settings"
        android:fragment="com.android.settings.deviceinfo.Memory"
        android:icon="@drawable/ic_settings_storage"
        android:title="@string/storage_settings" />

    <!-- Battery -->
    <header
        android:id="@+id/battery_settings"
        android:fragment="com.android.settings.fuelgauge.PowerUsageSummary"
        android:icon="@drawable/ic_settings_battery"
        android:title="@string/power_usage_summary_title" />

    <!-- Application Settings -->
    <header
        android:fragment="com.android.settings.applications.ManageApplications"
        android:icon="@drawable/ic_settings_applications"
        android:title="@string/applications_settings"
        android:id="@+id/application_settings" />

    <!-- TEMPORARY FACTORY STARTER WILL BE REMOVED WITH UPDATED SETTINGS -->
    <header
        android:icon="@drawable/ic_settings_applications"
        android:title="Factory"
        android:id="@+id/application_settings" >
        <intent android:action="android.intent.action.MAIN"
                android:targetPackage="com.jamdeo.tv.sample.factory"
                android:targetClass="com.jamdeo.tv.sample.factory.TvFactoryMainActivity" />
    </header>

    <!-- Manufacturer hook -->
    <header
        android:fragment="com.android.settings.WirelessSettings"
        android:id="@+id/manufacturer_settings">
        <intent android:action="com.android.settings.MANUFACTURER_APPLICATION_SETTING" />
    </header>


    <!-- PERSONAL -->
    <header android:title="@string/header_category_personal" />

    <!-- Data Sync. The settings activity will ensure this is resolved to an
         activity on the system image, otherwise it will remove this
         preference. -->
    <header
        android:fragment="com.android.settings.accounts.ManageAccountsSettings"
        android:icon="@drawable/ic_settings_sync"
        android:title="@string/sync_settings"
        android:id="@+id/sync_settings" />

    <!-- Location -->
    <header
        android:fragment="com.android.settings.LocationSettings"
        android:icon="@drawable/ic_settings_location"
        android:title="@string/location_settings_title"
        android:id="@+id/location_settings" />

    <!-- Security -->
    <header
        android:fragment="com.android.settings.SecuritySettings"
        android:icon="@drawable/ic_settings_security"
        android:title="@string/security_settings_title"
        android:id="@+id/security_settings" />

    <!-- Language -->
    <header
        android:id="@+id/language_settings"
        android:fragment="com.android.settings.inputmethod.InputMethodAndLanguageSettings"
        android:icon="@drawable/ic_settings_language"
        android:title="@string/language_settings" />

    <!-- Backup and reset -->
    <header
        android:fragment="com.android.settings.PrivacySettings"
        android:icon="@drawable/ic_settings_backup"
        android:title="@string/privacy_settings"
        android:id="@+id/privacy_settings" />


    <!-- SYSTEM -->
    <header android:title="@string/header_category_system" />

    <!-- Dock -->
    <header
        android:id="@+id/dock_settings"
        android:fragment="com.android.settings.DockSettings"
        android:icon="@drawable/ic_settings_dock"
        android:title="@string/dock_settings" />

    <!-- Date & Time -->
    <header
        android:id="@+id/date_time_settings"
        android:fragment="com.android.settings.DateTimeSettings"
        android:icon="@drawable/ic_settings_date_time"
        android:title="@string/date_and_time_settings_title" />

    <!-- Accessibility feedback -->
    <header
        android:id="@+id/accessibility_settings"
        android:fragment="com.android.settings.AccessibilitySettings"
        android:icon="@drawable/ic_settings_accessibility"
        android:title="@string/accessibility_settings" />

    <!-- Development -->
    <header
        android:id="@+id/development_settings"
        android:fragment="com.android.settings.DevelopmentSettings"
        android:icon="@drawable/ic_settings_development"
        android:title="@string/development_settings_title" />

    <!-- About Device -->
    <header
        android:id="@+id/about_settings"
        android:fragment="com.android.settings.DeviceInfoSettings"
        android:icon="@drawable/ic_settings_about"
        android:title="@string/about_settings" />

</preference-headers>

Just use <header> only with android:title attribute.

babkin
  • 121
  • 1
  • 3
    This does not work - just another header that does nothing is created and does not show any group division. I think that in your example there are some programmatical stuff missing. – Erveron Feb 13 '13 at 13:17
  • I looked into the source code of the settings activity there is much you need to do for getting the same result. I ended in proxying the Adapter of the settings and I returned a different view for my headlines. – rekire Aug 11 '14 at 14:07
  • As mentioned by @James it doesn't work. Do not waste your time. – tomrozb Oct 14 '14 at 09:56
  • I think the best solution is not using headers. Only for tablets. – André Luiz Reis Jun 26 '17 at 17:21