I've implemented a custom seekbar preference, using this site - seekbar preference and it works just fine. Now I want to add values to the seekbar's customized properties from the string.xml
file instead of hard-coding them:
Instead of writing customseekbar:unitsRight="Seconds"
I want to have a string resource like <string name="units">Seconds</string>
and use it like this: customseekbar:unitsRight="@string/units"
. I've tried to implement this guide. My relevant code is:
attrs.xml
<resources>
<declare-styleable name="CustomSeekBarPreference">
<attr name="unitsRight" format="reference|string"/>
</declare-styleable>
And the constructor -
CustomSeekBarPreference.java
public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {
private static final String APPLICATIONNS="http://CustomSeekBarPreference.com";
public CustomSeekBarPreference(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.CustomSeekBarPreference, 0 ,0);
mUnitsRight = a.getString(R.styleable.CustomSeekBarPreference_unitsRight);
a.recycle();
}
and the layout -
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:customseekbar="http://CustomSeekBarPreference.com" >
<com.x.sharedpreferencestestapp.CustomSeekBarPreference
android:key="1"
android:defaultValue="30"
android:max="100"
customseekbar:min="0"
android:title="Default step"
customseekbar:unitsRight="@string/units"/>
</PreferenceScreen>
But as you can see, I get 'null' instead of the right value:
Even if I change the value to a fixed string instead of string resource, like
customseekbar:unitsRight="Seconds"
I still get the same result. And just to make it clear - if I stick to the original code of the seekbar preference: mUnitsRight = getAttributeStringValue(attrs, APPLICATIONNS, "unitsRight", "defaultValue")
it works, but not with string resource.
You're getting null for that attribute value because of the namespace you've declared for it in the layout XML - http://CustomSeekBarPreference.com
.
As far as I'm aware, the obtainStyledAttributes()
method can only pull attributes that are in the standard Android resource namespace – http://schemas.android.com/apk/res/android
– or your app's resource namespace, which is http://schemas.android.com/apk/res/
plus the app's package name. Attributes in any other namespace will be ignored, and will not be in the returned TypedArray
, which is why you get null no matter if the value is a hardcoded string, or a resource reference.
In the example you're following, they've used a similar non-standard namespace, but they're pulling the value directly from the AttributeSet
, specifying that namespace in the getAttributeValue()
call thereon. I can't say that I've seen this particular method often used in this manner, and the only benefit I can see to it is that it saves you from having to define your own custom attributes, which is a rather trivial task.
There are a couple of ways to fix this.
Move those layout attributes into your app's namespace, and define an attr
resource for each.
This is the method demonstrated in the developer page you've linked, and is probably the most common and familiar way to implement custom View
attributes.
First change the namespace declaration in the layout to:
xmlns:customseekbar="http://schemas.android.com/apk/res-auto"
(The res-auto
segment is a convenience that will cause the actual namespace name to be appropriately constructed with the current package name, as previously described.)
With your posted setup, this will then cause an error with customseekbar:min
, since you've not defined min
as an attribute resource (attr
) in your app. You can simply define that attr
, and then handle it the same way you're handling unitsRight
; i.e., retrieve its value from the TypedArray
returned from obtainStyledAttributes()
. You would do the same for any additional custom attributes you might need.
Modify the getAttributeStringValue()
method in the CustomSeekBarPreference
example to handle resource references as well.
This may be the simpler option, as far as modifying the given example, but directly accessing the AttributeSet
values prevents those values from being adjusted for any theme or style that you might wish to apply. If that's not a concern, then the necessary changes are rather simple.
In the modified method, we just need to first check if the attribute value is a resource value, using AttributeSet#getAttributeResourceValue()
. If that method returns a valid identifier, we retrieve the actual value with Resources#getString()
. If not, we treat the attribute value as a plain string.
private String getAttributeStringValue(AttributeSet attrs, String namespace,
String name, String defaultValue) {
String value = null;
int resId = attrs.getAttributeResourceValue(namespace, name, 0);
if (resId == 0) {
value = attrs.getAttributeValue(namespace, name);
if (value == null)
value = defaultValue;
}
else {
value = getContext().getResources().getString(resId);
}
return value;
}
Using this method, you would not need to define your custom attributes, and the layout namespace can remain as you have it in the posted snippet.