Android:如何在首选项屏幕中去除边距/填充

99
我在设计偏好设置界面时遇到了一个很奇怪的问题。尽管我在布局中没有设置边距,但左侧仍然留有一些空间。
如下图所示: enter image description here XML:
   <PreferenceScreen android:title="demo" >
       <CheckBoxPreference
           android:defaultValue="false"
            android:key="prefSync"`
            android:title="Auto Sync" />
    </PreferenceScreen>

在屏幕中添加复选框偏好设置的操作是否有误?


你可以在这里找到答案。 - Miral
嗨Miral,谢谢回复。但我不会为首选项屏幕创建自定义布局。 - Dory
你能展示一下你的布局文件吗? - Robin Dijkhof
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <CheckBoxPreference android:defaultValue="false" android:key="prefSync" android:title="自动同步" /> </PreferenceScreen> 这是代码 - Dory
看看这个答案,或许能帮助解决您的问题。 - KLM
18个回答

182

更新此内容以适应AndroidX。

经过大量试验,我通过将这个添加到每个具有多余缩进的偏好设置来解决了这个问题:

app:iconSpaceReserved="false"
当然,您还需要将此添加到您的xml顶部的PreferenceScreen声明本身中。
xmlns:app="http://schemas.android.com/apk/res-auto"

关于自定义偏好的后续处理

我注意到在自定义偏好的情况下,特别是在旧设备上,这个解决方案并不总是有效。例如,像这样的偏好仍然可能会出现缩进:

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"

问题出在自定义首选项类中使用的第三个构造函数参数。如果传递该第三个参数,则列表项将缩进。如果省略它,则列表将正确对齐:

问题源于自定义偏好类中第三个构造参数的使用。如果传入该第三个参数,列表项将缩进。如果省略该参数,则列表将正确对齐:

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

使用这个而不是那个:

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

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

这归功于@CommonsWare在这篇非常古老的帖子中。


13
我不知道你是谁,但我会找到你并感谢你! :) - David
这是正确的方法;我想不需要自定义类。现在似乎Android默认保留了图标的空间。 - Padmal
1
这也适用于支持库28.0.0PreferenceFragmentCompat。太好了! - jayeffkay
7
好的,我会尽力以最简洁明了的方式进行翻译。关于“Preference category”的问题?“Preference category”似乎无法正常工作。 - jknair0
1
在 androidx.preference:preference:1.1.0 中,Preference 类别正常工作。 - kalandar
显示剩余3条评论

54
我已经在这里报告了这个问题(你也可以在那里检查我的解决方案项目),但目前,我发现了一种有点hack-y的方法来克服这个问题:

  • 对于PreferenceCategory,我将其布局的开始/左填充设置为0。

  • 对于其他类型的首选项,如果它们没有图标,我选择隐藏icon_frame

这是代码。只需从这个类扩展,其余部分就是自动的:

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

以下是结果(XML示例可以在这里找到,我在这里创建了此Google示例以进行检查):

enter image description here

这段代码有些危险,因此在每次更新库时,请确保检查它是否正常工作。

同时,它可能在某些特殊情况下无法正常工作,例如当您为首选项定义自己的android:layout时,您需要对其进行修改。


有一个更好、更官方的解决方案:

对于每个偏好设置,使用app:iconSpaceReserved="false"。这应该可以正常工作,但由于某种原因,存在一个(已知的)错误,它不能用于PreferenceCategory。已在此处报告,应在不久的将来修复。

所以现在你可以使用我写的解决方法和这个标志的混合版本。


编辑:发现另一个解决方案。这个解决方案将覆盖所有偏好设置,并为每个偏好设置设置isIconSpaceReserved。不幸的是,如上所述,如果您使用PreferenceCategory,它会破坏它,但如果您不使用它,它应该可以正常工作:

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

编辑:在Google终于修复库之后(链接在这里),你可以为每个偏好设置标志,或者使用这个解决方案来为所有偏好设置标志:

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

只需从中扩展,您就不必为首选项设置无用的填充。示例项目在这里,我还要求有一种官方方法来避免它,而不是使用那些技巧。请考虑加星标。


2
我甚至不明白为什么会出现这种情况。当没有替代方案或者存在有缺陷的替代方案时,谷歌废除/移除/阻止API是怎么回事? - TheWanderer
1
@androiddeveloper 我能理解,除了PreferenceCategories。它们不应该对齐。 - TheWanderer
PreferenceCategory的错误已经在2018年11月5日修复了,现在在AndroidX上可以正常工作。我已经进行了迁移后测试,结果符合预期。请参见https://developer.android.com/jetpack/androidx/androidx-rn#2018-nov-preference。如果您不想进行迁移并且正在使用自定义布局,则建议使用负填充约为120来消除间隙。 - E.T.
如何在Java中覆盖onPreferenceHierarchyChange方法 - Emmanuel Mtali
为什么要重写onCreateAdapter方法?这是否涵盖了通过代码创建的首选项和XML创建的首选项的创建? - ShellDude
显示剩余7条评论

37

这里提供一个简单的工作解决方案,来自这里
它可以适用于所有偏好设置,无需编写所有偏好设置的app:iconSpaceReserved="false"

创建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>

<bool>元素为所有Preference类型的iconSpacePreserved属性设置了默认值;<dimen>元素为PreferenceCategory类型设置了默认间距。

编辑:如果您正在使用androidx.preference:preference:1.1.1,则不需要使用preference_category_padding_start尺寸。测试可用于Android 6-10。


1
我也试过了,可以用。非常感谢。但是我不明白为什么它必须放在名为“values-sw360dp-v13”的目录中,而不是只放在“values”目录中。(将其放在“values”中对我没有影响。)有人知道原因吗? - Gereon99
完美的全屏解决方案。自动固定所有内容,但不知道如何实现? - Md Mohsin

7

试试这个:

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

您可以使用setPadding();来设置内边距。

2
嘿@David,谢谢回复。但是我没有为首选项屏幕创建任何自定义视图。我正在使用默认视图。 - Dory
同意,我也没有自定义视图。为 Android 视图设置这个选项会很不错。 - lostintranslation
@lostintranslation 我同意仅为此目的创建自定义视图并不是很合乎逻辑,但我进行了测试,这样做效果很好。 - defhlt
遗憾的是,当使用PreferenceFragmentCompat时,这不再是原因。现在,填充(或仅图标框架)是首选项视图的一部分。 - android developer

5
如果您正在使用com.android.support:preference-v7库,请确保托管偏好设置的活动主题具有设置为v14材料偏好设置主题覆盖的preferenceTheme

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


1
使用新的PreferenceFragmentCompat类时是否有类似的解决方案? - android developer
1
抱歉,我没有尝试过那个...现在我也使用PreferenceFragmentCompat,但是我已经重新设计了我的设置屏幕,并且它包括图标,这使得这种填充对我很有用(它有助于保持左对齐的一致性)。 - mtrewartha
谢谢@mtrewartha,最终我选择了一些半适当的Material图标来为我的设置字段添加左侧间距,而不是试图找到消除它的方法。 - gregdev
没问题,很高兴能帮助你 @gregdev! - mtrewartha

5
很遗憾,以上所有方法对我都没有用。
简短回答:
我通过在自定义偏好设置布局的最上层容器上使用负边距解决了这个问题。
步骤:
1.创建一个用于偏好设置的自定义布局(例如preference_category.xml)。 2.通过在XML中添加android:layout参数到偏好设置标签来指定您的偏好设置使用自定义布局。 3.应用负边距。
详细回答:
偏好设置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>

自定义偏好行的布局,去除不必要的左右边距:

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

自定义偏好类别布局,去除不必要的左右边距:

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

太好了,你救了我的命! :D - Kacper
这需要为所有类型的首选项都有一个布局。你在哪里找到它们的?你有这方面的库吗?我在这里做了一个hack-y的解决方法:https://dev59.com/bWMl5IYBdhLWcg3wTljY#51568782,但我认为你的解决方案更安全。只需要大量复制东西... - android developer
你可以在AOSP的GitHub镜像上找到它:检查带有preference_前缀的xml文件。https://github.com/aosp-mirror/platform_frameworks_base/tree/82834baa358f55acb542e17da828b2d497cf8332/core/res/res/layout - Gord

4

按照以下方式更改偏好主题。确保使用最新版本的偏好库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>

2
很不幸,我发现了一个androidx.preference:preference:1.1.0-alpha01的关键性错误。在应用程序中任何第二个片段堆栈上的设备旋转都会导致崩溃。希望它能在以后的版本中得到修复。我回退到androidx.preference:preference:1.0.0 - Joonsoo
2
@Joonsoo:可以确认。一直出现一些模糊的“片段保存状态失败”错误。因此,在修复之前,我将保持在1.0.0版本,并同时遵循此解决方案来处理边距。 - Ganesh Mohan
1
iconSpaceReserved仅适用于版本26及以上。 - Pranav P

3

这个内边距是由包含你的设置的 PreferenceScreen 中的父 ListView 引起的。如果你正在使用 PreferenceFragment 创建你的偏好设置,只需要进入 PreferenceFragementonActivityCreated 函数,并进行以下操作来移除内边距:

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

当使用PreferenceFragmentCompat时,这不再是原因。现在填充(或仅图标框架)是首选项视图的一部分。 - android developer

2

编译 'com.android.support:preference-v7:24.2.1'

使用PreferenceFragmentCompat可以实现此功能:

1.定义一个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.重写PreferenceFragmentCompat的onCreateAdapter方法:

   @Override
   protected Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {

      return new CustomAdapter(preferenceScreen);
   }

使用style.xml可以实现这个功能:
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>

这适用于所有的偏好设置吗?你有相关的库吗? - android developer

2
只需在首选项标签中添加此属性。
app:iconSpaceReserved="false"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接