How to manage dividers in a PreferenceFragment?
Asked Answered
S

10

27

I started dealing with preferences in a PreferenceFragment. Here's what I have:

my_preferences

I'm trying to:

  1. get rid of the dividers between items. I suppose this can be defined from styles, but I can't figure out how. I tried getting the preference ListView at runtime calling findViewById(android.R.id.list), as I read somewhere, but it returns null.

  2. set new, full width dividers right on top of the headers, as seen here. For example in this case I want a full width divider right above "Statistiche", but not above "Generali" which is on top of the list.

The only way that comes to my mind is setting dividers as fake preferences, like with:

<Preference
    android:layout="@layout/divider" //here I set width and a divider resource
    />

<PreferenceCategory ... />

The main issue here is that my PreferenceFragment (or the ActionBarActivity it's in) has some left/right padding, that make any divider I add into preferences.xml not cover the entire width.

So my question are:

  • How can I get rid of default, item-item dividers that you can see in the image?

  • How can I set full width dividers right above headers, or how can I get rid of internal fragment/activity padding? Of course my activity layout has no (explicit) padding whatsoever.

Selassie answered 3/1, 2015 at 1:40 Comment(5)
Hi, did u get the answer?. can you help with the solution if you have?Epic
@Epic in short I used list = findViewById(android.R.id.list) and then list.setDivider(null). To avoid NPE, you need to call this onResume rather than onCreate. As for the other issue, I will try to post a full answer today.Selassie
Hey, thanks for reply. I'm using fragments not an activity. i.e my class looks like public class SettingsFragment extends PreferenceFragment. So in this case how can we achieve? any help.Epic
getview() returns me NULL, if i call View rootView = getView(); like thisEpic
@Epic see my answer below.Selassie
S
9

Had totally forgot about this question, will post an answer now to help others. I solved by moving my code in the onResume() method of the activity that hosts my PreferenceFragment. I think there are several other points at which you can recall a non-null ListView using findViewById(android.R.id.list).

public boolean mListStyled;

@Override
public void onResume() {
    super.onResume();
    if (!mListStyled) {
        View rootView = getView();
        if (rootView != null) {
            ListView list = (ListView) rootView.findViewById(android.R.id.list);
            list.setPadding(0, 0, 0, 0);
            list.setDivider(null);
            //any other styling call
            mListStyled = true;
        }
    }
}

You can probably get rid of the rootView check, but at the same time you might even want to check for list != null. I didn't face any NPE this way anyway.

So, setDivider(null) takes off the item-item dividers. I managed to add section dividers covering the full width of the screen by:

  • Removing padding from list;
  • Adding a custom preference in my XML:

n

 <Preference
     android:title="divider"
     android:selectable="false"
     android:layout="@layout/preference_divider"/>
Selassie answered 3/2, 2015 at 16:13 Comment(4)
I did it, but not show divider inside list, pls help me. thanks!Clubbable
after API 21 a recyclerview is returned, if 'android.R.id.list' will be searched. setDivider(null) in onStart of PreferenceFragmentCompat should help.Cosmism
Can you maybe share your preference_divider.xml ?Crosstree
Sorry @Crosstree , I can'tSelassie
F
58

AndroidX makes it simple, but I wish it was better documented.

In XML

To add/remove dividers between preferences in XML, use the following attributes:

<androidx.preference.PreferenceScreen
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Preference
        ...
        app:allowDividerAbove="true/false"
        app:allowDividerBelow="true/false"
        ... />

</androidx.preference.PreferenceScreen>

Note, a divider will only be shown between two preferences if the top divider has allowDividerBelow set to true and the bottom divider has allowDividerAbove set to true.

In Code

You can also change/remove dividers programmatically using the following methods in onActivityCreated of your PreferenceFragmentCompat:

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

    // To remove:
    setDivider(null);

    // To change:
    setDivider(ContextCompat.getDrawable(getActivity(), R.drawable.your_drawable));
    setDividerHeight(your_height);
}
Flori answered 4/5, 2019 at 10:6 Comment(1)
Good answer: Quick questions: 1. Do you know how to make the dividers appear in the layout preview of Android Studio? 2. Is there a way to call setDivider() in an XML theme, rather than in code? For item 2, I've tried "android:listDivider" and "android:divider" everywhere, but it has no effect.Fluoro
F
28

Add this code under the PreferenceFragment :

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

        // remove dividers
        View rootView = getView();
        ListView list = (ListView) rootView.findViewById(android.R.id.list);
        list.setDivider(null);

    }
Felipafelipe answered 14/1, 2015 at 20:59 Comment(7)
Perfect! Quick, easy, short. Every other answer (That didn't even work) that I've come across seemed excessive. Thanks!Lindberg
do we have to create a list view then set each element as layout for the same?!Hilel
After ANDROID 21 a RecyclerView is used to display the list-based content, so the R.id.list delieveres a RecyclerView reference, which can't be handled the same way we did with ListView.Cosmism
@Cosmism that's not what I'm experiencing... maybe it depends on the type of preference used? normal vs support v4 etc.?Dolichocephalic
@Dolichocephalic what exacatly, you are not experiencing?Cosmism
@Cosmism I'm using android > 21 and R.id.list still works.Dolichocephalic
For SDK 21 and below. the code works within onCreateView. For SDK 23, it only works within onActivityCreated. BTW, if you are getting RecyclerView, perhaps you are using the support library, which has it's own setDivider methods.Shandishandie
K
11

Although a bit late I`m having same troubles with dividers in preff screen and found this solution: Set custom style to the hosting activity and add to the style:

   <item name="android:listDivider">@null</item>

It actually does the same as setting it trough code but you save one findById and i think it looks clearer set trough styles

Kim answered 23/7, 2015 at 22:35 Comment(2)
Awesome. The easiest one.Eberhardt
The best solution since it also accounts for subscreens.Motif
S
9

Had totally forgot about this question, will post an answer now to help others. I solved by moving my code in the onResume() method of the activity that hosts my PreferenceFragment. I think there are several other points at which you can recall a non-null ListView using findViewById(android.R.id.list).

public boolean mListStyled;

@Override
public void onResume() {
    super.onResume();
    if (!mListStyled) {
        View rootView = getView();
        if (rootView != null) {
            ListView list = (ListView) rootView.findViewById(android.R.id.list);
            list.setPadding(0, 0, 0, 0);
            list.setDivider(null);
            //any other styling call
            mListStyled = true;
        }
    }
}

You can probably get rid of the rootView check, but at the same time you might even want to check for list != null. I didn't face any NPE this way anyway.

So, setDivider(null) takes off the item-item dividers. I managed to add section dividers covering the full width of the screen by:

  • Removing padding from list;
  • Adding a custom preference in my XML:

n

 <Preference
     android:title="divider"
     android:selectable="false"
     android:layout="@layout/preference_divider"/>
Selassie answered 3/2, 2015 at 16:13 Comment(4)
I did it, but not show divider inside list, pls help me. thanks!Clubbable
after API 21 a recyclerview is returned, if 'android.R.id.list' will be searched. setDivider(null) in onStart of PreferenceFragmentCompat should help.Cosmism
Can you maybe share your preference_divider.xml ?Crosstree
Sorry @Crosstree , I can'tSelassie
H
4

For PreferenceFragmentCompat, ListView doesn't work. But this works like a charm:

<style name="PrefsTheme" parent="PreferenceThemeOverlay.v14.Material">
    <item name="android:divider">@null</item>
    <item name="android:dividerHeight">0dp</item>
</style>

Add this to your app theme:

<item name="preferenceTheme">@style/PrefsTheme</item>
Herpes answered 14/2, 2017 at 14:47 Comment(2)
There is no such parent "PreferenceThemeOverlay.v14.Material"Petronella
Uh, sorry, you have to add dependency to build.gradle first. See https://mcmap.net/q/504987/-how-to-style-preferencefragmentcompatHerpes
P
3

I had this exact problem and wanted dividers between preference categories but not between the items themselves. I found that the accepted solution satisfied Question 1 by removing the dividers from preference items but did not fix question 2 and add the dividers between preference categories.

Fix below. Basically override the onCreateAdapter method of your PreferenceFragmentCompat and give it a custom PreferenceGroupAdapter that has an overridden onBindViewHolder method that uses the position and whatever else you need to set above and below permissions for each view holder. A divider will be drawn when both viewholders allow a divider between them.

Here is my fix

public class SettingsFragment extends PreferenceFragmentCompat {

    @Override
    protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
        return new CustomPreferenceGroupAdapter(preferenceScreen);
    }

    static class CustomPreferenceGroupAdapter extends PreferenceGroupAdapter {

    @SuppressLint("RestrictedApi")
    public CustomPreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
        super(preferenceGroup);
    }

    @SuppressLint("RestrictedApi")
    @Override
    public void onBindViewHolder(PreferenceViewHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        Preference currentPreference = getItem(position);
        //For a preference category we want the divider shown above.
        if(position != 0 && currentPreference instanceof PreferenceCategory) {
            holder.setDividerAllowedAbove(true);
            holder.setDividerAllowedBelow(false);
        } else {
            //For other dividers we do not want to show divider above 
            //but allow dividers below for CategoryPreference dividers.
            holder.setDividerAllowedAbove(false);
            holder.setDividerAllowedBelow(true);
        }
    }
}
Prefatory answered 14/8, 2018 at 2:0 Comment(0)
V
3

(AndroidX only)

Maksim Ivanov's answer got me most of the way there. But to remove dividers only for a specific Preference created in code, I had to do:

val pref = object : Preference(activity) {
    override fun onBindViewHolder(holder: PreferenceViewHolder) {
        super.onBindViewHolder(holder)
        // By default, preferences created in code show dividers
        holder.setDividerAllowedAbove(false)
        holder.setDividerAllowedBelow(false)
    }
}
Veradi answered 22/5, 2019 at 19:31 Comment(0)
I
0

if you use a PreferenceFragment ,you can use ListView.setDivider(null); if you use a PreferenceFragmentCompat ,you can use PreferenceFragmentCompat.setDivider(Drawable divider) or setDividerHeight(int height);

Innuendo answered 12/4, 2017 at 2:59 Comment(0)
A
0

New solution for new android API 24 and above (December 25th, 2017).

I've found after try many ways on stackoverflow, but not work, or it just work but not work in nestest PreferenceScreen.

Frist, you need to find listview from current displaying fragment, and remove the divider:

private fun removeDividerInCurrentFragment() {
    [email protected](android.R.id.content)?.let {
        it.view?.findViewById<ListView?>(android.R.id.list)?.let {
            it.divider = null
            it.dividerHeight = 0
        }
    }
}

Second, to remove the divider when fragment commited, call method above (removeDividerInCurrentFragment) to remove listview's divider.

To sure if you have nestest PreferenceScreen. Register listener when fragment changed in your PreferenceActivity by implement FragmentManager.OnBackStackChangedListener protocol:

class YourPreferenceActivity : PreferenceActivity(), FragmentManager.OnBackStackChangedListener {
    override fun onBackStackChanged() {
        [email protected]()
    }
}

Finally, register backstack changes listener by call fragmentManager.addOnBackStackChangedListener(this@ YourPreferenceActivity) in onCreate. And remove backstack changes listener by call fragmentManager.removeOnBackStackChangedListener in onDestroyed method.

Good luck!

Aid answered 25/12, 2017 at 15:3 Comment(0)
O
0

You can re-style your divider using this theme.

<style name="PreferenceFragment.Material">
    <item name="android:divider">@drawable/preference_list_divider_material</item>
</style>
Oxen answered 12/2, 2018 at 1:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.