I've reported about this issue here (you can also check my workaround project there), but for now, I've found a bit hack-y, yet easy way to overcome this:
For PreferenceCategory
, I set the start/left padding of its layouts to 0.
For other types of preferences, I choose to hide the icon_frame
in case they don't have an icon.
Here's the code. Just extend from this class and the rest is automatic :
Kotlin
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> {
return object : PreferenceGroupAdapter(preferenceScreen) {
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
val preference = getItem(position)
if (preference is PreferenceCategory)
setZeroPaddingToLayoutChildren(holder.itemView)
else
holder.itemView.findViewById<View?>(R.id.icon_frame)?.visibility = if (preference.icon == null) View.GONE else View.VISIBLE
}
}
}
private fun setZeroPaddingToLayoutChildren(view: View) {
if (view !is ViewGroup)
return
val childCount = view.childCount
for (i in 0 until childCount) {
setZeroPaddingToLayoutChildren(view.getChildAt(i))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
view.setPaddingRelative(0, view.paddingTop, view.paddingEnd, view.paddingBottom)
else
view.setPadding(0, view.paddingTop, view.paddingRight, view.paddingBottom)
}
}
}
Java
public abstract class BasePreferenceFragmentCompat extends PreferenceFragmentCompat {
@Override
protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
return new PreferenceGroupAdapter(preferenceScreen) {
@SuppressLint("RestrictedApi")
@Override
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
super.onBindViewHolder(holder, position);
Preference preference = getItem(position);
if (preference instanceof PreferenceCategory)
setZeroPaddingToLayoutChildren(holder.itemView);
else {
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
if (iconFrame != null) {
iconFrame.setVisibility(preference.getIcon() == null ? View.GONE : View.VISIBLE);
}
}
}
};
}
private void setZeroPaddingToLayoutChildren(View view) {
if (!(view instanceof ViewGroup))
return;
ViewGroup viewGroup = (ViewGroup) view;
int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
setZeroPaddingToLayoutChildren(viewGroup.getChildAt(i));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
viewGroup.setPaddingRelative(0, viewGroup.getPaddingTop(), viewGroup.getPaddingEnd(), viewGroup.getPaddingBottom());
else
viewGroup.setPadding(0, viewGroup.getPaddingTop(), viewGroup.getPaddingRight(), viewGroup.getPaddingBottom());
}
}
}
And the result (XML sample can be found here of this Google sample, which I've created here to check out) :

This code is a bit dangerous, so make sure that upon each update of the library, you check that it works fine.
Also, it might not work well on some special cases, such as when you define android:layout
of your own for the preference, so you will have to modify it for this matter.
Got a better, more official solution:
For each preference, use app:iconSpaceReserved="false"
. This should work fine, but for some reason there is a (known) bug that it doesn't work for PreferenceCategory. It was reported here, and should be fixed in the near future.
So for now you can use a mix version of the workaround I wrote and this flag.
EDIT: Found yet another solution. This one will get over all of the preferences, and set isIconSpaceReserved
for each. Sadly, as I've written above, if you use PreferenceCategory, it ruins it, but it should work fine if you don't use it:
Kotlin
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
super.setPreferenceScreen(preferenceScreen)
if (preferenceScreen != null) {
val count = preferenceScreen.preferenceCount
for (i in 0 until count)
preferenceScreen.getPreference(i)!!.isIconSpaceReserved = false
}
}
Java
public class BasePreferenceFragment extends PreferenceFragmentCompat {
@Override
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
super.setPreferenceScreen(preferenceScreen);
if (preferenceScreen != null) {
int count = preferenceScreen.getPreferenceCount();
for (int i = 0; i < count; i++)
preferenceScreen.getPreference(i).setIconSpaceReserved(false);
}
}
}
EDIT: after Google finally fixed the library (link here), you can set the flag for each preference, or use this solution which does it for all, for you :
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) {
preference.isIconSpaceReserved = false
if (preference is PreferenceGroup)
for (i in 0 until preference.preferenceCount)
setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i))
}
override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) {
if (preferenceScreen != null)
setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen)
super.setPreferenceScreen(preferenceScreen)
}
override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> =
object : PreferenceGroupAdapter(preferenceScreen) {
@SuppressLint("RestrictedApi")
override fun onPreferenceHierarchyChange(preference: Preference?) {
if (preference != null)
setAllPreferencesToAvoidHavingExtraSpace(preference)
super.onPreferenceHierarchyChange(preference)
}
}
}
Just extend from it, and you won't have the useless padding for your preferences. Sample project here, where I also request to have an official way to avoid it, instead of those tricks. Please consider starring it.