Android: How to remove margin/padding in Preference Screen

35.6k views Asked by At

I am facing very weird problem in designing preference screen. Though I am not giving any margin in layout,it is leaving some space in left.

As you can see in image below: enter image description here

XML:

   <PreferenceScreen android:title="demo" >
       <CheckBoxPreference
           android:defaultValue="false"
            android:key="prefSync"`
            android:title="Auto Sync" />
    </PreferenceScreen>

Am I doing something wrong in adding check-box preference in screen?

17

There are 17 answers

0
Shashi Kumar On

If you are using AndroidX, you can add/remove extra padding by simply adding the following attribute.

 <Preference
    ...
    app:iconSpaceReserved="false/true"
    .../>
3
Libin On

Change the preference theme like this . Make sure to use the latest version of the preference library androidx.preference 1.1.0-alpha01

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="preferenceTheme">@style/CustomPreferenceTheme</item>
</style>

<style name="CustomPreferenceTheme" parent="@style/PreferenceThemeOverlay">
    <item name="preferenceFragmentCompatStyle">@style/CustomPreferenceFragmentCompatStyle</item>
    <item name="preferenceCategoryStyle">@style/CustomPreferenceCategory</item>
    <item name="preferenceStyle">@style/CustomPreference</item>
    <item name="checkBoxPreferenceStyle">@style/CustomCheckBoxPreference</item>
    <item name="dialogPreferenceStyle">@style/CustomDialogPreference</item>
    <item name="switchPreferenceCompatStyle">@style/CustomSwitchPreferenceCompat</item>  <!-- for pre lollipop(v21) -->
    <item name="switchPreferenceStyle">@style/CustomSwitchPreference</item>
</style>

 <style name="CustomPreferenceFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
    <item name="android:layout">@layout/fragment_settings</item>
</style>

<style name="CustomPreferenceCategory" parent="Preference.Category.Material">
    <item name="iconSpaceReserved">false</item>
</style>

<style name="CustomPreference" parent="Preference.Material">
    <item name="iconSpaceReserved">false</item>
</style>

<style name="CustomCheckBoxPreference" parent="Preference.CheckBoxPreference.Material">
    <item name="iconSpaceReserved">false</item>
</style>

<style name="CustomDialogPreference" parent="Preference.DialogPreference.Material">
    <item name="iconSpaceReserved">false</item>
</style>

<style name="CustomSwitchPreferenceCompat" parent="Preference.SwitchPreferenceCompat.Material">
    <item name="iconSpaceReserved">false</item>
</style>

<style name="CustomSwitchPreference" parent="Preference.SwitchPreference.Material">
    <item name="iconSpaceReserved">false</item>
</style>
8
AndroidDev On

Updating this for androidx.

After a lot of experimentation, I resolved this issue by adding this to each preference that had the excess indentation:

app:iconSpaceReserved="false"

Of course, you'll also need to add this to the PreferenceScreen declaration itself at the top of your xml:

xmlns:app="http://schemas.android.com/apk/res-auto"

Follow Up For Custom Preferences

I noticed that in the case of custom preferences, particularly on older devices, this solution wasn't always working. For example, a preference like this could still be indented:

com.example.preference.SomeCustomPreference
    android:id="@+id/some_custom_preference"
    android:name="Custom Preference"
    android:key="@string/custom_pref_key"
    android:summary="@string/custom_pref_summary"
    android:title="@string/preference_custom_title"
    app:iconSpaceReserved="false"

The problem traces to the usage of the third constructor parameter in the custom preference class. If you pass that third parameter, the list item will be indented. If you omit it, the list will be aligned correctly:

class SomeCustomPreference
@JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = android.R.attr.someCustomPreferenceStyle
) : DialogPreference(context, attrs, defStyle) {

    override fun getDialogLayoutResource(): Int {
        return R.layout.my_layout
    }
}

Instead of that, use this:

class SomeCustomPreference
@JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : DialogPreference(context, attrs) {

    override fun getDialogLayoutResource(): Int {
        return R.layout.my_layout
    }
}

Credit for this goes to @CommonsWare in this very old post.

1
Guy On

Asked quite a while ago, but what helped me solve this was negative margins, no need for custom files.

When you create your SettingsActivity class (which has the PreferenceFragment inside), you simply set it's layout to be a fragment and add a negative margin.

settings_activity:

<?xml version="1.0" encoding="utf-8"?>
<fragment
    android:layout_marginLeft="-40dp"
    android:name="com.example.USER.APPNAME.SettingsActivity$PrefsFragment"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.USER.APPNAME.SettingsActivity">
</fragment>
1
Helmut Schottmüller On

The padding is caused by the parent ListView in the PreferenceScreen containing your settings. If you are using a PreferenceFragment to create your Preferences, just go to the onActivityCreated function of the PreferenceFragement and do the following to remove the padding:

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

  View lv = getView().findViewById(android.R.id.list);
  if (lv != null) lv.setPadding(0, 0, 0, 0);
}
1
Francesco Florio On

I don't know if it's too late for an answer but today I had the same problem and nothing of this answers help me.

So I used this solution:

In my DisclaimerPreference that extends Preference, I ovveride the onBindView(View view) method in this way:

    @Override protected void onBindView(View view) {
        super.onBindView(view);
        if(view instanceof ViewGroup){
            ViewGroup vg = (ViewGroup)view;
            View currView;
            for(int i=0; i<vg.getChildCount(); i++){
               currView = vg.getChildAt(i);
               if (currView instanceof RelativeLayout)
                   currView.setVisibility(View.GONE);
            }
         }
    }

The key is to set the RelativeLayout inside the main View as GONE. Please let me know if something is wrong :)

12
android developer On

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

enter image description here

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.

0
s-hunter On

None of the above answer worked, I had to copy the preference layout, create a new layout file such as custom_preference.xml, override the layout content by setting the visibility of the icon frame to GONE. And then in the pref.xml, set the layout for the preference to use this custom layout.

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.v7.preference.EditTextPreference
        android:title="@string/pref_label"
        android:hint="@string/pref_hint"
        android:key="@string/pref_key"
        android:inputType="text"
        android:singleLine="true"
        android:layout="@layout/custom_preference_layout"
        />
</PreferenceScreen>
2
Gord On

Unfortunately, nothing from above worked for me.

Short answer:

I solved this by using a negative margins on the uppermost container of the custom preference layout.

Steps:

  • Create a custom layout for the preference (e.g. preference_category.xml)
  • specify that your preference uses the custom layout by adding android:layout param to a preference tag in XML
  • Apply negative margins

Detailed answer:

The preference XML:

    <android.support.v7.preference.PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/settings_title" >

    <android.support.v7.preference.PreferenceCategory
        android:layout="@layout/preference_category"
        android:key="settings_list"
        android:title="@string/pref_category_settings" />
    <android.support.v7.preference.SwitchPreferenceCompat
        android:layout="@layout/preference_row"
        android:icon="@drawable/ic_nav_switch"
        android:key="pref_switch"
        android:title="@string/pref_switch_title" />

    </android.support.v7.preference.PreferenceScreen>

The custom layout for preference row which removes unnecessary left and right margins:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginStart="-12dp"
    android:layout_marginEnd="-8dp"
    android:minHeight="?android:attr/listPreferredItemHeight"
    android:gravity="center_vertical">

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@android:id/icon"
            android:layout_width="58dp"
            android:layout_height="58dp"
            android:padding="8dp"
            android:layout_gravity="center"
            android:visibility="visible" />

    </RelativeLayout>

    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="15dp"
        android:layout_marginEnd="6dp"
        android:layout_marginTop="6dp"
        android:layout_marginBottom="6dp"
        android:layout_weight="1">

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

        <TextView
            android:id="@android:id/summary"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@android:id/title"
            android:layout_alignStart="@android:id/title"
            android:textAppearance="?android:attr/textAppearanceListItemSecondary"
            android:textColor="?android:attr/textColorSecondary"
            android:maxLines="2"/>

    </RelativeLayout>

    <LinearLayout
        android:id="@android:id/widget_frame"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="end|center_vertical"
        android:orientation="vertical">

    </LinearLayout>
</LinearLayout>

The custom layout for preference category which removes unnecessary left and right margins:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textColor="@color/colorAccent"
    android:textStyle="bold"
    android:textSize="12sp"
    android:gravity="center_vertical"
    android:textAllCaps="true"
    android:layout_marginStart="-4dp"
    android:layout_marginEnd="-8dp"
    android:id="@+android:id/title" />
4
mtrewartha On

If you're using the com.android.support:preference-v7 library, make sure the theme for the activity that's hosting your preferences has a preferenceTheme set to the v14 Material preference theme overlay:

<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>

2
t0m On

Simple working solution from here.
It works across all preferences without need write to all preferences app:iconSpaceReserved="false"

Create res/values-sw360dp/values-preference.xml:

<resources xmlns:tools="http://schemas.android.com/tools">
    <bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,PrivateResource">false</bool>
    <dimen name="preference_category_padding_start" tools:ignore="MissingDefaultResource,PrivateResource">0dp</dimen>
</resources>

The <bool> fixes the default value of iconSpacePreserved for all Preference; The <dimen> fixes the PreferenceCategory.

EDIT: If you are using androidx.preference:preference:1.1.1, you won't need the dimen preference_category_padding_start. Tested on Android 6-10.

1
John On

Apparently, adding android:layout="@null"to your preference in xml would do the trick. But consequently, it will also make title size larger..

1
zonda On

compile 'com.android.support:preference-v7:24.2.1'

Use PreferenceFragmentCompat can do this:

1.Define a PreferenceGroupAdapter:

    static class CustomAdapter extends PreferenceGroupAdapter {

    public CustomAdapter(PreferenceGroup preferenceGroup) {
      super(preferenceGroup);
    }

    @Override
    public PreferenceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      PreferenceViewHolder preferenceViewHolder = super.onCreateViewHolder(parent, viewType);
      parent.setPadding(0, 0, 0, 0);
      preferenceViewHolder.itemView.setPadding(20, 5, 20, 5);
      return preferenceViewHolder;
    }
  }

2.Override PreferenceFragmentCompat's onCreateAdapter method:

   @Override
   protected Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {

      return new CustomAdapter(preferenceScreen);
   }

Use style.xml can do this:

1.values/styles.xml:

  <style name="customPreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay">
    <item name="preferenceFragmentListStyle">@style/customPreferenceFragmentList</item>
  </style>
  <style name="customPreferenceFragmentList">
    <item name="android:paddingLeft">0dp</item>
    <item name="android:paddingRight">0dp</item>
  </style>

2.values-v17/styles.xml :

  <style name="customPreferenceFragmentList">
    <item name="android:paddingLeft">0dp</item>
    <item name="android:paddingRight">0dp</item>
    <item name="android:paddingStart">0dp</item>
    <item name="android:paddingEnd">0dp</item>
  </style>

3.value/styles.xml:

  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    ... ...
    <item name="preferenceTheme">@style/customPreferenceThemeOverlay</item>
  </style>
1
Bao Doan On

I try using

android:icon="@null"

with CheckBoxPreference in Android 4.4 but can't padding from left. I tricked use a small transparent icon and add

android:icon="@drawable/transparent_icon" 

and it seems work for me. I hope it can help. Thanks.

3
jayeffkay On

The 'padding' is initiated in PreferenceFrameLayout.java:

public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    TypedArray a = context.obtainStyledAttributes(attrs,
            com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0);

    float density = context.getResources().getDisplayMetrics().density;
    int defaultBorderTop = (int) (density * DEFAULT_BORDER_TOP + 0.5f);
    int defaultBottomPadding = (int) (density * DEFAULT_BORDER_BOTTOM + 0.5f);
    int defaultLeftPadding = (int) (density * DEFAULT_BORDER_LEFT + 0.5f);
    int defaultRightPadding = (int) (density * DEFAULT_BORDER_RIGHT + 0.5f);

    mBorderTop = a.getDimensionPixelSize(
            com.android.internal.R.styleable.PreferenceFrameLayout_borderTop,
            defaultBorderTop);
    mBorderBottom = a.getDimensionPixelSize(
            com.android.internal.R.styleable.PreferenceFrameLayout_borderBottom,
            defaultBottomPadding);
    mBorderLeft = a.getDimensionPixelSize(
            com.android.internal.R.styleable.PreferenceFrameLayout_borderLeft,
            defaultLeftPadding);
    mBorderRight = a.getDimensionPixelSize(
            com.android.internal.R.styleable.PreferenceFrameLayout_borderRight,
            defaultRightPadding);

    a.recycle();
}

If you know how to override styles defined by Android, you could probably solve this. It is not very straight forward however.

0
Lasitha Lakmal On

Just add this attirubte in the preference tag

app:iconSpaceReserved="false"
0
MDXDave On

Add android:defaultValue="false" to your <PreferenceScreen [...] Tag:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:defaultValue="false">