How can I use a resource within a custom Xml resource file? - android

I have an XML resource file:
<resources>
<section>
<category value="1" resourceId="#xml/categoryData1" />
<category value="2" resourceId="#xml/categoryData2" />
<category value="3" resourceId="#xml/categoryData3" />
</section>
</resources>
Using XmlPullParser, on the START_TAG, I can use:
int value = parser.getAttributeIntValue(null, "value", 0);
to get values 1, 2, 3...however:
int resourceId = parser.getAttributeIntValue(null, "resourceId", 0);
doesn't work...it just yields the default value 0, or whatever I change the default value (3rd parameter) to be...
Does anyone know what I am doing wrong or if this is possible?

well, if you look in your resourceId attribute, it does not seem to contain an int, does it? Or am I misreading you?
you should instead use getAttributeValue and cast it in the right Type.

Related

Accepting a text style for a custom view

So I am writing my own custom view, that has both a TextView and an EditText within it. What I am trying to do is let the user set a text style for each one individually, like this:
<declare-styleable name="InputRow">
<attr name="descriptionTextStyle" format="string" />
<attr name="valueTextStyle" format="string" />
</declare-styleable>
I thought this would work, so that in XML I could say:
app:descriptionTextStyle="bold"
app:valueTextStyle="italic"
However, the problem comes when I am trying to read from the typed array. I can get the string:
if(typedArray.hasValue(R.styleable.InputRow_descriptionTextStyle)) {
setDescriptionTextStyle(typedArray.getString(R.styleable.InputRow_descriptionTextStyle));
}
but when I want to call descriptionTextView.setTypeface(Typeface tf, int style) I need an integer value for the second parameter, which I don't have.
I can't change the style to be an int format, because then ="bold" would be invalid, so I'm at a loss for how to get the text style.
Thanks to the suggestion from pskink, I took a look at how Android defines the style:
<!-- Default text typeface style. -->
<attr name="textStyle">
<flag name="normal" value="0" />
<flag name="bold" value="1" />
<flag name="italic" value="2" />
</attr>
Given that, I was able to update my custom attribute accordingly:
<attr name="descriptionTextStyle">
<flag name="normal" value="0" />
<flag name="bold" value="1" />
<flag name="italic" value="2" />
</attr>
Then I was able to setup my item by calling app:descriptionTextStyle="bold" and in the class I call typedArray.getInt() and call the setTypeface() method as I said. Thanks for the help!
You can use the
public void setTypeface(Typeface tf);
method, it only requires a typeface parameter.
Also for converting string to typeface you can do something like this:
switch (typefaceString) {
case SANS:
tf = Typeface.SANS_SERIF;
break;
case SERIF:
tf = Typeface.SERIF;
break;
case MONOSPACE:
tf = Typeface.MONOSPACE;
break;
}

Manifest fallbacks for screen orientation

In the documentation for the Android manifest, there are multiple different ways to specify screenOrientation:
landscape
sensorLandscape added in API 9
userLandscape added in API 18
How can I specify userLandscape, but on older versions of Android, have it fallback to sensorLandscape, and on even older versions fall back to landscape? I couldn't find how to do this in the documentation.
I don't think that there's a way to implement the fallback mechanism in the manifest itself.
I would suggest that you specify one of { userLandscape, sensorLandscape, landscape } in the manifest. Then, check for the version at runtime and improvise.
Say, you decide to go with android:screenOrientation="userLandscape" in the manifest.
In your activity's onCreate(Bundle), before setting the content:
int sdkInt = Build.VERSION.SDK_INT;
// if we're running on some API level within [9, 18), use `sensorLandscape`
if (sdkInt >= Build.VERSION_CODES.GINGERBREAD /* 9 */
&& sdkInt < Build.VERSION_CODES.JELLY_BEAN_MR2 /* 18 */) {
setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
} else if (sdkInt < Build.VERSION_CODES.GINGERBREAD /* 9 */) {
setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
// API 18 or above - handled in manifest
setContentView(R.layout.whatever);
Hope that someone comes up with a better solution than this. This seems brute-force.
Edit:
Tried a different approach - from what I know (and I could be wrong here), enums such as userLandscape, sensorLandscape etc. won't change values. As they currently stand:
<attr name="screenOrientation">
<enum name="unspecified" value="-1" />
<enum name="landscape" value="0" />
<enum name="portrait" value="1" />
<enum name="user" value="2" />
<enum name="behind" value="3" />
<enum name="sensor" value="4" />
<enum name="nosensor" value="5" />
<enum name="sensorLandscape" value="6" />
<enum name="sensorPortrait" value="7" />
<enum name="reverseLandscape" value="8" />
<enum name="reversePortrait" value="9" />
<enum name="fullSensor" value="10" />
<enum name="userLandscape" value="11" />
<enum name="userPortrait" value="12" />
<enum name="fullUser" value="13" />
<enum name="locked" value="14" />
</attr>
So, if you were to define an integer such as:
<!-- `0` for `landscape` -- defined in values/integers.xml -->
<integer name="customScreenOrientation">0</integer>
<!-- `6` for `sensorLandscape` -- defined in values-v9/integers.xml -->
<integer name="customScreenOrientation">6</integer>
<!-- `11` for `userLandscape` -- defined in values-v18/integers.xml -->
<integer name="customScreenOrientation">11</integer>
You could then use #integer/customScreenOrientation as the value for android:screenOrientation in your activity's tag.
Needless to say its a hack at best. If someone could confirm the steady status of enum values for screenOrientation, this could be a viable workaround - preferable to including code from my earlier suggestion in multiple activities.
Yet another edit:
The second approach I mentioned earlier can be improved upon:
Instead of multiple integers.xml files, create 3 styles.xml files. I guess you already have one - values/styles.xml. Create values-v9/styles.xml & values-v18/styles.xml.
<!-- values/styles.xml -->
<style name="AppTheme" parent="#style/BaseTheme">
<item name="android:screenOrientation">landscape</item>
</style>
<!-- values-v9/styles.xml -->
<style name="AppTheme" parent="#style/BaseTheme">
<item name="android:screenOrientation">sensorLandscape</item>
</style>
<!-- values-v18/styles.xml -->
<style name="AppTheme" parent="#style/BaseTheme">
<item name="android:screenOrientation">userLandscape</item>
</style>
Following this, create values/integers.xml(one file) and define an integer customScreenOrientation:
<integer name="customScreenOrientation">?android:attr/screenOrientation</integer>
You activity tag will look like:
<activity
....
android:theme="#style/AppTheme"
android:screenOrientation="#integer/customScreenOrientation"/>
The advantage of this approach over the second one is that we get to use the enums in place of hard-coded values. Again, these two approaches are equivalent if enum-values are set in stone. If they do change, the second approach will fail while the third one keeps on going.

Create Custom Compound View in Android with Attributes

I have created a custom compound view inside a library application and everything was OK. When I add custom attributes to view, I always get default values. I followed this steps with only one difference: my view is in a library project.
/res/values/attrs.xml
<resources>
<declare-styleable name="DatePickerView">
<attr name="showToday" format="boolean" />
<attr name="calendar" format="enum">
<enum name="jalali" value="0" />
<enum name="gregorian" value="1" />
</attr>
</declare-styleable>
</resources>
Layout file that contains view:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:farayan="http://schemas.android.com/apk/lib/net.farayan.android.view"
...
<net.farayan.android.view.datepicker.DatePickerView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
farayan:showToday="false"
farayan:calendar="gregorian"/>
...
Component's code:
int calendar;
boolean showToday;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DatePickerView, 0, 0);
try {
calendar = a.getInteger(R.styleable.DatePickerView_calendar, 0);
showToday = a.getBoolean(R.styleable.DatePickerView_showToday, true);
} finally {
a.recycle();
}
calendar and showToday are always 0 and true respectively. Any idea?
It looks like something is not right here:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:farayan="http://schemas.android.com/apk/lib/net.farayan.android.view"
What about change to:
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:farayan="http://schemas.android.com/apk/res/net.farayan.android.view"
net.farayan.android.view is your app root namespace.
update1
xmlns:farayan="http://schemas.android.com/apk/res/your_main_app_package"
Here is am example. It use a view defined in the library project.
If we add new compound view code and its attributesinside project, we should add this at the beginning of layout:
xmlns:custom="http://schemas.android.com/apk/res/your_main_app_package
and if new compound view is inside a library project linked to our peoject, we should add this:
xmlns:custom="http://schemas.android.com/apk/res-auto
Link: https://stackoverflow.com/a/10217752/1152549

Access resource defined in theme and attrs.xml android

I have a scenario in which I want to set a Drawable depending upon the theme defined.
To explain this further, Here is what I have in code:
\res\values\attrs.xml
<resources>
<declare-styleable name="AppTheme">
<attr name="homeIcon" format="reference" />
</declare-styleable>
</resources>
res\values\styles.xml
<resources>
<style name="AppTheme" parent="android:style/Theme">
<item name="attr/homeIcon">#drawable/ic_home</item>
</style>
</resources>
AndroidManifest.xml
<application android:label="#string/app_name"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
So as you have noticed I am defining a custom attr homeIcon and setting the attribute value in AppTheme.
When I define this attribute in a layout XML and try to access it it works smoothly
<ImageView android:id="#+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="?attr/homeIcon" />
and renders the Drawable ic_home in an ImageView.
But I am not able to figure out how to access the Drawable programmatically.
I tried to do this with a work around, by defining a holder LayerList Drawable, which results in resource not found exception:
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:drawable="?attr/homeIcon" />
</layer-list>
Summary
I want to access the Drawable defined in a custom defined Theme programmatically.
I think you can get the Drawable with this code:
TypedArray a = getTheme().obtainStyledAttributes(R.style.AppTheme, new int[] {R.attr.homeIcon});
int attributeResourceId = a.getResourceId(0, 0);
Drawable drawable = getResources().getDrawable(attributeResourceId);
a.recycle();
Another possible way to do it:
public static int getResIdFromAttribute(final Activity activity,final int attr) {
if(attr==0)
return 0;
final TypedValue typedvalueattr=new TypedValue();
activity.getTheme().resolveAttribute(attr,typedvalueattr,true);
return typedvalueattr.resourceId;
}
Or in Kotlin:
#JvmStatic
fun getResIdFromAttribute(activity: Activity, attr: Int): Int {
if (attr == 0)
return 0
val typedValue = TypedValue()
activity.theme.resolveAttribute(attr, typedValue, true)
return typedValue.resourceId
}
no need to recycle anything here...
usage:
int drawableResId=getResIdFromAttribute(this,R.attr.homeIcon);
Drawable drawable = getResources().getDrawable(drawableResId);
I used below method to get resource id form style attribute. Then it can be used for drawable, string, dimension so on.
TypedArray typedArray = context.getTheme().obtainStyledAttributes(new int[] { R.attr.attrName });
int resourceId = typedArray.getResourceId(0, defaultResourceId);
typedArray.recycle();
cheers :-)
If you are using support / design library easier way to get drawables now is -
Context.getDrawable(int)
or
ContextCompat.getDrawable(Context, int)
reference - https://plus.google.com/+BenjaminWeiss/posts/M1dYFaobyBM
Here are the results of my investigation, regarding this topic.
If we have declare-stylable then we can override those values in themes.
So far the best way that I found how to get them is the following.
TypedArray a = context.getTheme().obtainStyledAttributes(R.styleable.AppTheme);
a.getDrawable(R.styleable.AppTheme_homeIcon);
By using R.styleable.AppTheme_homeIcon we are referencing exactly that attribute that we want. For example if we would have few more attributes, then we can reference them as follows:
a.getColor(R.styleable.AppTheme_color,defaultValue);
a.getDimension(R.styleable.AppTheme_width,defaultWidth);
And if in current theme those attributes were not defined you will get default values and no Exceptions.

Read Newer Theme Attributes On Older Platform

I am trying to read attribute values from themes and styles which were designed for platforms that are newer than I am running my application on.
Please don't ask why. If you know anything about the libraries I write then you should already know that I like to push the capabilities of the platform :)
I am operating under the presumption that when Android styles are compiled the attribute constants are what is used for the keys and therefore should theoretically be able to be read on any platform somehow. This is what I have observed to be happening with layout XMLs in my other libraries with no trouble.
Here is a base test case which shows the problem. This should be compiled using Android 3.0+.
<resources>
<style name="Theme.BreakMe">
<item name="android:actionBarStyle">#style/Widget.BreakMe</item>
</style>
<style name="Widget.BreakMe" parent="android:Widget">
<item name="android:padding">20dp</item>
</style>
</resources>
The fact that this uses android:actionBarStyle specifically is irreleveant. All that should be understood is that its an attribute which was only available starting with Android 3.0.
Here are the way that I have tried to access these values thus far on platforms prior to Android 3.0.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Break Me"
style="?android:attr/actionBarStyle"
/>
and
<declare-styleable name="Whatever">
<item name="datStyle" format="reference" />
</declare-styleable>
<style name="Theme.BreakMe.Take2">
<item name="datStyle">?android:attr/actionBarSize</item>
</style>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Break Me"
style="?attr/datStyle"
/>
and
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.actionBarStyle, outValue, true);
and
int[] Theme = new int[] { android.R.attr.actionBarSize };
int Theme_actionBarSize = 0;
TypedArray a = context.obtainStyledAttributes(attrs, Theme);
int ref = a.getResourceId(Theme_actionBarSize, 0);
and
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ActionBar, android.R.attr.actionBarStyle, 0);
All of them result in this error in LogCat:
E/ResourceType(5618): Style contains key with bad entry: 0x010102ce
The 0x010102ce constant is the attribute value of android.R.attr.actionBarStyle which seems to indicate the platform is rejecting the attribute before I can even get a chance to access its value.
I am looking for any other way to read attributes like this from the Theme. I'm fairly sure that once I've obtained the style reference I won't have trouble reading its attributes.
Is there any possible way to do this?
I am operating under the presumption that when Android styles are compiled the attribute constants are what is used for the keys and therefore should theoretically be able to be read on any platform somehow.
Possibly, though that is not how I am interpreting the C++ source code that raises the error you are seeing. Check out ResTable::Theme::applyStyle() in frameworks/base/libs/utils/ResourceTypes.cpp.
My interpretation is that Android has what amounts to an in-memory table of packages->types->possible entries:
numEntries = curPI->types[t].numEntries;
Your entry index is higher than the highest known entry:
if (e >= numEntries) {
LOGE("Style contains key with bad entry: 0x%08x\n", attrRes);
bag++;
continue;
}
It is possible that they handle this different for android versus other packages -- android uses known values at firmware build time (and your generated entry index is higher, because it is from a newer platform), non-android ones assume anything's valid.
If my guesswork is correct, what you want to do will not work. That being said, my C++ days are seriously in my rear-view mirror, so I may be misinterpreting what I'm seeing.
Perhaps I'm missing the end goal here, but I put together the following example that was able to read out all the attributes without issue on any 2.x device. The example was compiled against a 3.0 targetSdk.
styles.xml (Declare the styles and themes)
<resources>
<style name="Theme.NewFeatures" parent="android:Theme">
<item name="android:actionBarStyle">#style/Widget.MyActionBar</item>
</style>
<style name="Widget.MyActionBar" parent="android:Widget">
<item name="android:padding">20dp</item>
</style>
</resources>
attrs.xml (Declare the attribute groups you wish to obtain at runtime)
<resources>
<declare-styleable name="ActionBarNewFeatures">
<attr name="android:actionBarStyle" />
</declare-styleable>
<declare-styleable name="MyWidgetNewFeatures">
<attr name="android:padding" />
</declare-styleable>
</resources>
AndroidManifest.xml (Apply the custom theme)
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.NewFeatures" >
<activity
android:name=".SomeActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
SomeActivity.java (Go digging for attributes)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TypedArray a = obtainStyledAttributes(R.styleable.ActionBarNewFeatures);
//Get the style ID for the widget
int resid = a.getResourceId(R.styleable.ActionBarNewFeatures_android_actionBarStyle, -1);
a.recycle();
a = obtainStyledAttributes(resid, R.styleable.MyWidgetNewFeatures);
int padding = a.getDimensionPixelSize(R.styleable.MyWidgetNewFeatures_android_padding, -1);
a.recycle();
TextView tv = new TextView(this);
tv.setText(String.format("Padding will be %d px", padding));
setContentView(tv);
}
As long as I compile the example against 3.0 so it can resolved all the attribute names; on every 2.X device/emulator I have this will correctly read into the theme and then into the widget style to get the scaled padding dimension I had set.
Hope I didn't miss something big.
Probably, You must define a few themes. For old devices use folder res/values-v11/themes.xml. See section "Using Holo while supporting Android 2.x" in the http://android-developers.blogspot.com/2012/01/holo-everywhere.html

Categories

Resources