问题
I'm trying to display a switchPreference which allows the user to display the distance according to miles or kms. I'm using the SwitchPreferenceCompat support library. According to the library, I can use the textSwitchOff and textSwitchOn to add text to the switch. I just want to add "km" or "miles" to my switch so that the user knows which metric is displayed.
According to this doc, all I need is the code below:
<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>
However, the switch just looks like a normal switch, there is no extra text on the switch itself.
How do I get it to display with the textOn and textOff?
I also tried the following:
addPreferencesFromResource(R.xml.preferences);
kmormiles = (SwitchPreferenceCompat) findPreference("kmormiles");
kmormiles.setSwitchTextOff("Km");
kmormiles.setSwitchTextOn("miles");
Still doesn't work. I'm trying it on two different genymotion emulators, API 16 and API 21.
回答1:
Because of SwitchPreferenceCompat
using a SwitchCompat
widget by default, Android Switch widget textOn and textOff not working in Lollipop has an application here as well. And the first statement
Text is not shown by default under Material theme since the switch widget assets don't work well with text.
also explains, why the result doesn't look good at all.
The SwitchPreferenceCompat class itself doesn't provide a possibility to set whether the on/off text should be displayed. So one way to make it work could be overriding the onBindViewHolder(PreferenceViewHolder)
method to set it programmatically.
Another and maybe better method is utilization of the theming mechanisms you're forced to use with the preference compat library anyway. You can't set any attribute to the views directly, but you can define the layout to be used with android:widgetLayout
. So just create your own preference theme overlay
<style name="MyPreferenceThemeOverlay" parent="PreferenceThemeOverlay">
<item name="switchPreferenceCompatStyle">@style/MySwitchPreferenceCompat</item>
</style>
with your own switch preference style
<style name="MySwitchPreferenceCompat" parent="Preference.SwitchPreferenceCompat">
<item name="android:widgetLayout">@layout/pref_stack</item>
</style>
using the slightly modified 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" />
Another thing you should keep in mind with your code is the compat functionality itself. With using android.support.v7.preference.SwitchPreferenceCompat
explicitly, you'll never get versions more suitable for newer devices the inflater knows about automatically like the currently only available alternative android.support.v14.preference.SwitchPreferenceCompat
. This might involve a little more work on your side though.
EDIT: This is the results of implementing the above suggestion by the poster, as he mentioned correctly, the results do not look good at all:
回答2:
Basing myself on tynn's option of overriding OnBindViewHolder method, I have implemented it and it is working. So I will post here the code with explanatory comments in case someone wants to use it.
NOTE: I'm developing my app on Xamarin.Android, so the code is in C#, but it should be really intuitive to translate it to Java (or 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;
}
}
}
}
}
Then you must declare the attributes in values/attrs.xml like this:
Resources/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>
And now you can use them in your layout (in my case I use it in a preference file): 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>
Note that you should declare the xml namespace you are using so it won't match with android's one. The URL doesn't need to exist, it only has to be any string that will be unique in the project.
And the result:
Feel free to ask any question.
EDIT: made the code generic so it can be used for any purpose.
来源:https://stackoverflow.com/questions/35415907/switchpreferencecompat-androidswitchtextoff-switchtexton-doesnt-work