I am working on a simple project and I want to avoid the PNG images as much as possible. I need a '+' (Add) button and I created it using the ShapeDrawable with the given code.
res/drawable/plus_icon_drawable.xml
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="line">
<stroke android:color="#000"
android:width="3dp"/>
</shape>
</item>
<item>
<rotate
android:fromDegrees="90"
android:toDegrees="90">
<shape android:shape="line">
<stroke android:color="#000"
android:width="3dp"/>
</shape>
</rotate>
</item>
</layer-list>
This code works fine when I add it as a background of Button or an ImageButton but when I add this as an icon of an ActionBar item, the item is not getting displayed.
When I set a PNG image as the icon, it works fine. Is there any restriction for the ShapeDrawables which blocks it from getting rendered on ActionBar?
This is my ActiobBar code
res/menu/action_my_actions.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_some_id"
android:icon="@drawable/plus_icon_drawable"
android:title="My Action"
android:showAsAction="always"/>
</menu>
Update (08 July 2014): Tested with both Native and AppCompat. Actually the item is present there but the ShapeDrawable image is not getting rendered. The action item responds to clicks.
What am I missing here?
Is there any restriction for the ShapeDrawables which blocks it from getting rendered on ActionBar?
Nothing like that. Its just how things are. Here's why you're seeing this:
Layer/ShapeDrawables
don't have a default size. In other words, its perfectly fine for a ShapeDrawable
to be of zero width and height. The stroke-width parameter does not dictate anything either.
This code works fine when I add it as a background of Button or an ImageButton but when I add this as an icon of an ActionBar item, the item is not getting displayed.
Yes, of course. ImageButtons/Buttons
have a concept of padding/ min-width/min-height. These allow the ShapeDrawable
to have some room to breathe. For example, a Button styled with Widget.Holo.Button
will have the minWidth
and minHeight
set:
<item name="android:minHeight">48dip</item>
<item name="android:minWidth">64dip</item>
A valid test here would be:
define an ImageView
as follows:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add" />
</RelativeLayout>
The ImageView
has no sizing guidelines in the definition above - we're using wrap_content
. Nothing will be displayed. On the other hand, if you use a png drawable, it itself defines the size. In the example above, using a explicit size (say 50dp) will make the ShapeDrawable
visible
.
This is how ActionMenuItemView
sets the drawable:
public void setIcon(Drawable icon) {
mIcon = icon;
if (icon != null) {
// Zero in case you don't define the <size />
int width = icon.getIntrinsicWidth();
int height = icon.getIntrinsicHeight();
if (width > mMaxIconSize) {
final float scale = (float) mMaxIconSize / width;
width = mMaxIconSize;
height *= scale;
}
if (height > mMaxIconSize) {
final float scale = (float) mMaxIconSize / height;
height = mMaxIconSize;
width *= scale;
}
icon.setBounds(0, 0, width, height);
}
setCompoundDrawables(icon, null, null, null);
updateTextButtonVisibility();
}
Snippet from ShapeDrawable#inflate(...)
method:
setIntrinsicWidth((int)
a.getDimension(com.android.internal.R.styleable.ShapeDrawable_width, 0f));
setIntrinsicHeight((int)
a.getDimension(com.android.internal.R.styleable.ShapeDrawable_height, 0f));
Without ShapeDrawable_width
and ShapeDrawable_height
defined, the intrinsic width and height will be set to zero. This means, the statement icon.setBounds(0, 0, width, height);
(from setIcon(Drawable) method) is in fact => icon.setBounds(0, 0, 0, 0);
.
So, it seems that we're indeed dealing with ShapeDrawables
that don't have size info.
A possible fix:
<shape android:shape="line">
<size android:width="?android:attr/actionBarSize"
android:height="?android:attr/actionBarSize"/>
<stroke android:color="#000"
android:width="3dp"/>
</shape>
But this won't work - for some reason ?android:attr/actionBarSize
is not considered a dimension. This is odd because ?android:attr/actionBarSize
works fine when supplied to android:layout_width
in case of layouts.
Workaround would be to define a new dimension, say actionBarSizeDp
and set it up:
In res/values/dimens.xml
:
<dimen name="actionBarSizeDp">48dp</dimen>
In res/values-land/dimens.xml
:
<dimen name="actionBarSizeDp">40dp</dimen>
These values are the default action bar size values as defined in android's own res/values-XX/dimens.xml.
Finally, use actionBarSizeDp
in plus_icon_drawable.xml
:
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="line">
<size android:width="@dimen/actionBarSizeDp"
android:height="@dimen/actionBarSizeDp"/>
<stroke android:color="#000"
android:width="3dp"/>
</shape>
</item>
<item>
<rotate
android:fromDegrees="90"
android:toDegrees="90">
<size android:width="@dimen/actionBarSizeDp"
android:height="@dimen/actionBarSizeDp"/>
<shape android:shape="line">
<stroke android:color="#000"
android:width="3dp"/>
</shape>
</rotate>
</item>
</layer-list>
In fact, defining the size
on one of the LayerDrawables
should suffice. The other(s) will be drawn accordingly.