Android: How to remove margin/padding in Preference Screen
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:
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?
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.
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.
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.
Try this:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
if(v != null) {
ListView lv = (ListView) v.findViewById(android.R.id.list);
lv.setPadding(10, 10, 10, 10);
}
return v;
}
You can set padding by using: setPadding();