How to manage dividers in a PreferenceFragment?

2020-01-28 08:27发布

问题:

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

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.

回答1:

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

    }


回答2:

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


回答3:

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



回答4:

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"/>


回答5:

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>


回答6:

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


回答7:

(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)
    }
}


回答8:

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



回答9:

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() {
    this@YourPreferenceActivity.fragmentManager.findFragmentById(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() {
        this@YourPreferenceActivity.removeDividerInCurrentFragment()
    }
}

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!



回答10:

You can re-style your divider using this theme.

<style name="PreferenceFragment.Material">
    <item name="android:divider">@drawable/preference_list_divider_material</item>
</style>