SwitchPreferenceCompat:android:switchTextOff / switchTextOn 不起作用

SwitchPreferenceCompat: android:switchTextOff / switchTextOn doesn't work

我正在尝试显示一个 switchPreference,它允许用户根据英里或公里显示距离。我正在使用 SwitchPreferenceCompat 支持库。根据库,我可以使用 textSwitchOff 和 textSwitchOn 向开关添加文本。我只想将 "km" 或 "miles" 添加到我的开关,以便用户知道显示哪个指标。

根据this doc,我只需要下面的代码:

<android.support.v7.preference.PreferenceCategory
    android:layout="@layout/preferences_category"
    android:title="Distance" >

    <android.support.v7.preference.SwitchPreferenceCompat android:title="KM or Miles"
        android:key="kmormiles"
        android:switchTextOff="miles"
        android:switchTextOn="km"
        android:defaultValue="true"/>

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

然而,开关看起来只是一个普通开关,开关本身没有额外的文字。

如何让它与 textOn 和 textOff 一起显示?

我还尝试了以下方法:

    addPreferencesFromResource(R.xml.preferences);
    kmormiles = (SwitchPreferenceCompat) findPreference("kmormiles");
    kmormiles.setSwitchTextOff("Km");
    kmormiles.setSwitchTextOn("miles");

还是不行。我在两个不同的 genymotion 模拟器上尝试它,API 16 和 API 21.

由于 SwitchPreferenceCompat 默认使用 SwitchCompat 小部件,Android Switch widget textOn and textOff not working in Lollipop 在这里也有一个应用程序。而第一个语句

Text is not shown by default under Material theme since the switch widget assets don't work well with text.

还解释了为什么结果看起来一点都不好。

SwitchPreferenceCompat class 本身不提供设置是否显示 on/off 文本的可能性。因此,使其工作的一种方法可能是覆盖 onBindViewHolder(PreferenceViewHolder) 方法以编程方式设置它。

另一种可能更好的方法是利用您无论如何都被迫与首选项兼容库一起使用的主题机制。您不能直接为视图设置任何属性,但可以定义要与 android:widgetLayout 一起使用的布局。所以只需创建您自己的偏好主题叠加层

<style name="MyPreferenceThemeOverlay" parent="PreferenceThemeOverlay">
    <item name="switchPreferenceCompatStyle">@style/MySwitchPreferenceCompat</item>
</style>

使用您自己的切换偏好样式

<style name="MySwitchPreferenceCompat" parent="Preference.SwitchPreferenceCompat">
    <item name="android:widgetLayout">@layout/pref_stack</item>
</style>

使用稍作修改的 default switch layout

<android.support.v7.widget.SwitchCompat
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/switchWidget"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@null"
    android:clickable="false"
    android:focusable="false"
    app:showText="true" />

您在编写代码时应牢记的另一件事是 compat 功能本身。通过显式使用 android.support.v7.preference.SwitchPreferenceCompat,您永远不会获得更适合充气机自动识别的较新设备的版本,例如当前唯一可用的替代方案 android.support.v14.preference.SwitchPreferenceCompat。不过,这可能需要您多做一些工作。

编辑:这是发帖人执行上述建议的结果,正如他正确提到的那样,结果看起来一点也不好:

基于 tynn 的覆盖 OnBindViewHolder 方法的选项,我已经实现了它并且它正在工作。因此,我将 post 此处包含带有解释性注释的代码,以防有人想使用它。

注意:我正在 Xamarin.Android 上开发我的应用程序,所以代码是用 C# 编写的,但将其转换为 Java(或 Kotlin)应该非常直观。

CustomSwitchPreferenceWidget.cs

namespace KeepTravelling.Ui
{
    class CustomSwitchPreferenceWidget : SwitchPreferenceCompat
    {
        private int TitleId = 0;
        private bool IsTitleFound => TitleId > 0; //equivalent to bool IsTitleFound(){ return TitleId > 0};
        public string TextWhenOn { get; set; }//getters and setters
        public string TextWhenOff { get; set; }
        
        public CustomSwitchPreferenceWidget(Context context, IAttributeSet attrs) : base(context, attrs)
        {
            TypedArray attrsArray = context.ObtainStyledAttributes(attrs, Resource.Styleable.CustomSwitchPreferenceWidget);
            TextWhenOn = attrsArray.GetString(Resource.Styleable.CustomSwitchPreferenceWidget_textWhenOn);
            TextWhenOff = attrsArray.GetString(Resource.Styleable.CustomSwitchPreferenceWidget_textWhenOff);
        }
        
        //Method that will search through holder element for a view with id = "title"
        //Once found it will store it in TitleId member
        private void FindTitleId(PreferenceViewHolder holder)
        {
            //Base element is a LinearLayout, but you can check it again to make sure it is
            LinearLayout layout = (LinearLayout)holder.ItemView;
            for (int i = 0; i < layout.ChildCount; i++)
            {
                var item = layout.GetChildAt(i);
                if (item.GetType().ToString().Contains("Layout")) //check if child element is a layout view
                {
                    ViewGroup group = (ViewGroup)item;
                    for (int j = 0; j < group.ChildCount; j++)
                    {
                        var nestedItem = group.GetChildAt(j);
                        string entryName = Context.Resources.GetResourceEntryName(nestedItem.Id);
                        if (entryName.Equals("title"))//we are looking for the TextView with id = "title"
                        {
                            //If we found it, store in TitleId member and return from the method
                            TitleId = nestedItem.Id;
                            return;
                        }
                        if (nestedItem.GetType().ToString().Contains("Layout"))
                        {
                            ViewGroup nestedGroup = (ViewGroup)nestedItem;
                            for (int k = 0; k < nestedGroup.ChildCount; k++)//3 levels should be enough and it actually never arrive here
                            {
                                var nestedNestedItem = nestedGroup.GetChildAt(k);
                                string nestedEntryName = Context.Resources.GetResourceEntryName(nestedNestedItem.Id);
                                if (entryName.Equals("title"))
                                {
                                    TitleId = nestedNestedItem.Id;
                                    return;
                                }
                            }

                        }
                    }

                }

            }
        }

        public override void OnBindViewHolder(PreferenceViewHolder holder)
        {
            base.OnBindViewHolder(holder);
            //Check if we already have found it
            if (!IsTitleFound)
            {
                //If not => find it!!
                FindTitleId(holder);
    

        //If for some reason it is not found, return from method
            if (!IsTitleFound) return;
        }
                    
        AppCompatTextView title = (AppCompatTextView)holder.FindViewById(TitleId);
        if (title != null)
        {
            if (MChecked)//MChecked value is self-explanatory
            {
                title.Text = TextWhenOn;
            }
            else
            {
                title.Text = TextWhenOff;
            }
        }
    }
}

}

那么你必须在values/attrs.xml中这样声明属性:

资源/values/attrs.xml

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <declare-styleable name="CustomSwitchPreferenceWidget">
    <attr name="textWhenOn" format="string"/>
    <attr name="textWhenOff" format="string"/>
  </declare-styleable>
</resources>

现在您可以在您的布局中使用它们(在我的例子中,我在首选项文件中使用它): Resources/xml/preferences.axml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:customAttrs="http://schemas.android.com/apk/res-auto2">
  <!-- More items -->
  <!--     ...    -->
  <KeepTravelling.Ui.CustomSwitchPreferenceWidget
      android:defaultValue="true"
      android:title="Start location service"
      android:key="start_stop_option"
      android:summary="If this option is turned off the service won't be running and thus you will not get new locations."
      customAttrs:textWhenOn="Text when ON"
      customAttrs:textWhenOff="Text when OFF">
  </KeepTravelling.Ui.CustomSwitchPreferenceWidget>
</PreferenceScreen>

请注意,您应该声明您正在使用的 xml 名称空间,这样它就不会与 android 的名称空间相匹配。 URL 不需要存在,它只需要是在项目中唯一的任何字符串。

结果:

有任何问题欢迎随时提问。

编辑:使代码通用,因此它可以用于任何目的。