2012年12月28日金曜日

drawable.xmlで遊んでみる #androidadvent2012

これはAndroid Advent Calendar 2012の12月28日<表>のエントリです。

@kinnekoさんの<裏>記事もどうぞ。
Supersonicで自前のGoogleMusicを実現する http://d.hatena.ne.jp/kinneko/20121228/p1



こんにちは。最近、公私共に全然Androidの開発やってない@mstsskです。

昨年のAndroid Advent Calendar 2011では、先にJPPの太鼓持ちjsonというテーマを決めていたので、「Androidアプリで使えるJSONライブラリ比較」という記事を書きました。

ですが、今回は記事に出来るような事がなーんにもない状態で、勢いでAdvent Calendarに参加を決めちゃいました。

ううむ。どうしよう…

そうだ! Androidと言えば、drawableがある。Shape Drawableを使えば、画像ファイルを用意せずともxmlの記述だけで画面解像度に依存しない図形を描画できる。 #小芝居
でも、描けるのはあくまでも簡単な図形だけらしい… そこで、drawable.xmlで絵が描けるか試してみました。


ドロイド君を drawable.xml だけで描いてみた

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- for Android 4.0 or above -->
<!-- left antenna's stroke -->
<item
android:bottom="350dp"
android:left="85dp"
android:right="248dp"
android:top="0dp">
<rotate
android:fromDegrees="-30"
android:pivotX="80%"
android:pivotY="0%"
android:toDegrees="00" >
<shape android:shape="rectangle" >
<solid android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</rotate>
</item>
<!-- right antenna's stroke -->
<item
android:bottom="350dp"
android:left="248dp"
android:right="85dp"
android:top="0dp">
<rotate
android:fromDegrees="30"
android:pivotX="20%"
android:pivotY="0%"
android:toDegrees="00" >
<shape android:shape="rectangle" >
<solid android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</rotate>
</item>
<!-- head -->
<item
android:bottom="160dp"
android:left="60dp"
android:right="60dp"
android:top="25dp">
<shape android:shape="oval" >
<solid android:color="@color/android" />
<stroke
android:width="10dp"
android:color="@android:color/white" />
</shape>
</item>
<!-- left antenna -->
<item
android:bottom="360dp"
android:left="100dp"
android:right="253dp"
android:top="10dp">
<rotate
android:fromDegrees="-30"
android:pivotX="80%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<corners android:radius="50dp" />
</shape>
</rotate>
</item>
<!-- right antenna -->
<item
android:bottom="360dp"
android:left="253dp"
android:right="100dp"
android:top="10dp">
<rotate
android:fromDegrees="30"
android:pivotX="20%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<corners android:radius="50dp" />
</shape>
</rotate>
</item>
<!-- left eye -->
<item
android:bottom="327dp"
android:left="120dp"
android:right="222dp"
android:top="76dp">
<shape android:shape="oval" >
<solid android:color="@android:color/white" />
</shape>
</item>
<!-- right eye -->
<item
android:bottom="327dp"
android:left="222dp"
android:right="120dp"
android:top="76dp">
<shape android:shape="oval" >
<solid android:color="@android:color/white" />
</shape>
</item>
<!-- left leg's stroke -->
<item
android:bottom="0dp"
android:left="104dp"
android:right="188dp"
android:top="300dp">
<shape android:shape="rectangle" >
<solid android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</item>
<!-- right leg's stroke -->
<item
android:bottom="0dp"
android:left="188dp"
android:right="104dp"
android:top="300dp">
<shape android:shape="rectangle" >
<solid android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</item>
<!-- body -->
<item
android:bottom="82dp"
android:left="60dp"
android:right="60dp"
android:top="136dp">
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<stroke
android:width="10dp"
android:color="@android:color/white" />
<corners
android:bottomLeftRadius="22dp"
android:bottomRightRadius="22dp" />
<!-- android:radius="50dp" -->
</shape>
</item>
<!-- left arm -->
<item
android:bottom="119dp"
android:left="0dp"
android:right="291dp"
android:top="130dp">
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<stroke
android:width="10dp"
android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</item>
<!-- right arm -->
<item
android:bottom="119dp"
android:left="291dp"
android:right="0dp"
android:top="130dp">
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<stroke
android:width="10dp"
android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</item>
<!-- left leg -->
<item
android:bottom="10dp"
android:left="114dp"
android:right="198dp"
android:top="300dp">
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<corners android:radius="50dp" />
</shape>
</item>
<!-- right leg -->
<item
android:bottom="10dp"
android:left="198dp"
android:right="114dp"
android:top="300dp">
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<corners android:radius="50dp" />
</shape>
</item>
<!-- base -->
<item>
<shape android:shape="rectangle" >
<solid android:color="@android:color/transparent" />
<!--
<stroke
android:width="1dp"
android:color="@android:color/background_light" />
-->
<size
android:height="422dp"
android:width="360dp" />
</shape>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="android">#ffa4c639</color>
</resources>
view raw colors.xml hosted with ❤ by GitHub


はい。読み飛ばした方、こんにちは。

いかかでしょうか。ドロイド君くらいならxmlの記述だけで描けてしまいます。
colors.xmlだけは作りましたが、1ソースで済ませるという縛りでやってみたので大分冗長になっちゃってるのはご愛嬌。
まぁ、ちゃんと分けたとしてもあまり綺麗にはならなそうだったというのもありますけど。




ふー。終わった終わった。これで肩の荷が降りたわー。あー良かった。

ん。そういえば、他にも同じ事やってる人いたりしないよな… まさかな…


………………

[drawable ドロイド君][検索]

…………

検索結果に…

……

XML Drawable onlyでドロイド君を描いてみた - ReDo



だだ被りじゃねぇか!!!!! ( Д ) ゜ ゜

XML Drawable onlyでドロイド君を描いてみた http://greety.sakura.ne.jp/redo/2011/12/xml-drawable-only.html via @youten_redo

しかも、Advent Calender主催者の、Advent Calender関係ない、1年も前のエントリと。


このままじゃヤバい。ヤクい。これは、何かしら差別化しないと…

そうだ! 以前にやったこれをdrawable.xmlでやってみたらどうだろう!


というわけで↓

わかめねこを drawable.xml だけで描いてみた

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- for Android 4.0 or above -->
<!-- left leg -->
<item
android:bottom="0dp"
android:left="100dp"
android:right="160dp"
android:top="313dp">
<shape android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="@color/vvakame_yellow"
android:startColor="@color/vvakame_orange" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
<corners
android:bottomLeftRadius="20dp"
android:bottomRightRadius="20dp"
android:topLeftRadius="150dp"
android:topRightRadius="150dp" />
</shape>
</item>
<!-- right leg -->
<item
android:bottom="0dp"
android:left="150dp"
android:right="110dp"
android:top="313dp">
<shape android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="@color/vvakame_yellow"
android:startColor="@color/vvakame_orange" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
<corners
android:bottomLeftRadius="20dp"
android:bottomRightRadius="20dp"
android:topLeftRadius="150dp"
android:topRightRadius="150dp" />
</shape>
</item>
<!-- tail, end -->
<item
android:bottom="240dp"
android:left="200dp"
android:right="25dp"
android:top="130dp">
<rotate android:fromDegrees="-35" >
<shape android:shape="rectangle" >
<stroke
android:width="10dp"
android:color="@android:color/black" />
<solid android:color="@color/vvakame_yellow" />
<corners android:radius="30dp" />
</shape>
</rotate>
</item>
<!-- tail, inner -->
<item
android:bottom="120dp"
android:left="148dp"
android:right="65dp"
android:top="160dp">
<rotate android:fromDegrees="-15" >
<shape android:shape="rectangle" >
<stroke
android:width="15dp"
android:color="@color/vvakame_yellow" />
<solid android:color="@android:color/transparent" />
<corners android:bottomRightRadius="30dp" />
</shape>
</rotate>
</item>
<!-- tail, stroke, inside -->
<item
android:bottom="130dp"
android:left="135dp"
android:right="78dp"
android:top="150dp">
<rotate android:fromDegrees="-15" >
<shape android:shape="rectangle" >
<stroke
android:width="10dp"
android:color="@android:color/black" />
<solid android:color="@android:color/transparent" />
<corners android:bottomRightRadius="30dp" />
</shape>
</rotate>
</item>
<!-- tail, stroke, outside -->
<item
android:bottom="110dp"
android:left="135dp"
android:right="55dp"
android:top="150dp">
<rotate android:fromDegrees="-15" >
<shape android:shape="rectangle" >
<stroke
android:width="10dp"
android:color="@android:color/black" />
<solid android:color="@android:color/transparent" />
<corners android:bottomRightRadius="50dp" />
</shape>
</rotate>
</item>
<!-- body stroke -->
<item
android:bottom="70dp"
android:left="100dp"
android:right="110dp"
android:top="180dp">
<shape android:shape="rectangle" >
<gradient
android:angle="90"
android:centerColor="@color/vvakame_yellow"
android:endColor="@color/vvakame_yellow"
android:startColor="@color/vvakame_orange" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
<corners
android:bottomLeftRadius="35dp"
android:bottomRightRadius="35dp"
android:topLeftRadius="150dp"
android:topRightRadius="150dp" />
</shape>
</item>
<!-- left ear -->
<item
android:bottom="190dp"
android:left="45dp"
android:right="150dp"
android:top="0dp">
<rotate android:fromDegrees="-10" >
<shape android:shape="oval" >
<gradient
android:endColor="@color/vvakame_orange"
android:gradientRadius="250"
android:startColor="@color/vvakame_yellow"
android:type="radial" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
</shape>
</rotate>
</item>
<!-- right ear -->
<item
android:bottom="187dp"
android:left="135dp"
android:right="60dp"
android:top="3dp">
<rotate android:fromDegrees="20" >
<shape android:shape="oval" >
<gradient
android:endColor="@color/vvakame_orange"
android:gradientRadius="250"
android:startColor="@color/vvakame_yellow"
android:type="radial" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
</shape>
</rotate>
</item>
<!-- head inner -->
<item
android:bottom="185dp"
android:left="38dp"
android:right="53dp"
android:top="40dp">
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_yellow" />
<stroke
android:width="15dp"
android:color="@android:color/transparent" />
<corners
android:bottomLeftRadius="135dp"
android:bottomRightRadius="135dp"
android:topLeftRadius="90dp"
android:topRightRadius="100dp" />
</shape>
</item>
<!-- head striped pattern, top left -->
<item
android:bottom="317dp"
android:left="118dp"
android:right="168dp"
android:top="47dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, top center -->
<item
android:bottom="316dp"
android:left="141dp"
android:right="145dp"
android:top="48dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, top right -->
<item
android:bottom="315dp"
android:left="163dp"
android:right="124dp"
android:top="49dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, left top -->
<item
android:bottom="290dp"
android:left="50dp"
android:right="222dp"
android:top="97dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="0dp"
android:topRightRadius="7dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, left middle -->
<item
android:bottom="267dp"
android:left="45dp"
android:right="223dp"
android:top="120dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="0dp"
android:topRightRadius="7dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, left bottom -->
<item
android:bottom="245dp"
android:left="45dp"
android:right="224dp"
android:top="142dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="0dp"
android:topRightRadius="7dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, right top -->
<item
android:bottom="285dp"
android:left="209dp"
android:right="58dp"
android:top="102dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="7dp"
android:topRightRadius="0dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, right middle -->
<item
android:bottom="262dp"
android:left="208dp"
android:right="59dp"
android:top="125dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="7dp"
android:topRightRadius="0dp" />
</shape>
</rotate>
</item>
<!-- head striped pattern, right bottom -->
<item
android:bottom="240dp"
android:left="207dp"
android:right="60dp"
android:top="147dp">
<rotate
android:fromDegrees="2"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@color/vvakame_orange" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="0dp"
android:topLeftRadius="7dp"
android:topRightRadius="0dp" />
</shape>
</rotate>
</item>
<!-- head stroke -->
<item
android:bottom="185dp"
android:left="38dp"
android:right="53dp"
android:top="40dp">
<shape android:shape="rectangle" >
<solid android:color="@android:color/transparent" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
<corners
android:bottomLeftRadius="135dp"
android:bottomRightRadius="135dp"
android:topLeftRadius="90dp"
android:topRightRadius="100dp" />
</shape>
</item>
<!-- body inner -->
<item
android:bottom="70dp"
android:left="100dp"
android:right="110dp"
android:top="180dp">
<shape android:shape="rectangle" >
<gradient
android:angle="90"
android:centerColor="@color/vvakame_yellow"
android:endColor="@color/vvakame_yellow"
android:startColor="@color/vvakame_orange" />
<stroke
android:width="20dp"
android:color="@android:color/transparent" />
<corners
android:bottomLeftRadius="35dp"
android:bottomRightRadius="35dp"
android:topLeftRadius="150dp"
android:topRightRadius="150dp" />
</shape>
</item>
<!-- left eye -->
<item
android:bottom="275dp"
android:left="103dp"
android:right="187dp"
android:top="81dp">
<rotate
android:fromDegrees="6"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@android:color/black" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="7dp"
android:topRightRadius="7dp" />
</shape>
</rotate>
</item>
<!-- right eye -->
<item
android:bottom="271dp"
android:left="181dp"
android:right="109dp"
android:top="85dp">
<rotate
android:fromDegrees="-3"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<solid android:color="@android:color/black" />
<corners
android:bottomLeftRadius="7dp"
android:bottomRightRadius="7dp"
android:topLeftRadius="7dp"
android:topRightRadius="7dp" />
</shape>
</rotate>
</item>
<!-- nose, left -->
<item
android:bottom="247dp"
android:left="113dp"
android:right="155dp"
android:top="121dp">
<shape android:shape="oval" >
<stroke
android:dashGap="25dp"
android:dashWidth="60dp"
android:width="5dp"
android:color="@android:color/black" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
<!-- nose, right -->
<item
android:bottom="247dp"
android:left="140dp"
android:right="128dp"
android:top="121dp">
<shape android:shape="oval" >
<stroke
android:dashGap="25dp"
android:dashWidth="45dp"
android:width="5dp"
android:color="@android:color/black" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
<!-- left arm -->
<item
android:bottom="116dp"
android:left="31dp"
android:right="229dp"
android:top="194dp">
<rotate
android:fromDegrees="-70"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="@color/vvakame_orange"
android:startColor="@color/vvakame_yellow" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
<corners
android:bottomLeftRadius="150dp"
android:bottomRightRadius="150dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
</shape>
</rotate>
</item>
<!-- right arm -->
<item
android:bottom="150dp"
android:left="249dp"
android:right="11dp"
android:top="157dp">
<rotate
android:fromDegrees="72"
android:pivotX="0%"
android:pivotY="0%"
android:toDegrees="0" >
<shape android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="@color/vvakame_orange"
android:startColor="@color/vvakame_yellow" />
<stroke
android:width="10dp"
android:color="@android:color/black" />
<corners
android:bottomLeftRadius="150dp"
android:bottomRightRadius="150dp"
android:topLeftRadius="10dp"
android:topRightRadius="10dp" />
</shape>
</rotate>
</item>
<!-- base -->
<item>
<shape android:shape="rectangle" >
<solid android:color="@android:color/transparent" />
<!--
<stroke
android:width="1dp"
android:color="@android:color/background_light" />
-->
<size
android:height="400dp"
android:width="300dp" />
</shape>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="vvakame_yellow">#fffff568</color>
<color name="vvakame_orange">#fff7941e</color>
</resources>
view raw colors.xml hosted with ❤ by GitHub

はい、結局いつものネタに走りました。
drawable.xmlになってもわかめねこのプリチーさは変わりませんね。



解説のようなもの

めんどくさい満足したので、これで終わりにしてもいいんですが、一応解説。

viewを内包せず、全部1つのdrawable.xmlの中でshapeで済ませるにあたって、ミソになったのが以下
  1. layer-listのitemの子要素として直接shapeを書ける
  2. 最も外側のitemを基準にしたpaddingによる相対的なサイズ・位置の指定
  3. itemとshapeの間にrotate要素を入れて回転を設定
  4. ovalのshapeはstrokeのdashGap,dashWidthを調整すれば円弧が描ける


layer-listのitemの子要素として直接shapeを書ける


item要素のandroid:drawable属性で別ファイルのdrawableを指定しなくても、子要素としてshapeを記述できます。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/black">
<size android:height="480dp" android:width="360dp">
</size></solid></shape>
</item>
</layer-list>
view raw gistfile1.xml hosted with ❤ by GitHub


最も外側のitem要素を基準にしたpaddingによる相対的なサイズ・位置の指定


item要素のandroid:top,bottom,left,rightの各属性は、各辺ごとに一番外側の部分からの距離(padding)を指定します。
そこで、適当なsize指定をしたitem要素を外枠として、その内側に収まるように他のitem要素のpaddingを記述して位置や大きさを指定しました。
各itemのpaddingを、外枠にしたitem要素のsizeより大きくなるようにしてしまったり、item要素の大きさが負になるようなpadding指定をしてしまうと、全部のitemが思っきり崩れます。

これは、ソースからドロイド君の胴体部分と外枠部分の記述だけを抜き出したものです。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
... 胴体の下に置きたい要素 ...
<!-- body(ドロイド君の胴体): 外枠からのtop,bottom,left,rightを記述して位置・大きさを指定 -->
<item
android:bottom="82dp"
android:left="60dp"
android:right="60dp"
android:top="136dp">
<shape android:shape="rectangle" >
<solid android:color="@color/android" />
<stroke
android:width="10dp"
android:color="@android:color/white" />
<corners
android:bottomLeftRadius="22dp"
android:bottomRightRadius="22dp" />
<!-- top,bottom,left,rightが優先されるらしく、ここではsizeは意味がない -->
</shape>
</item>
... 胴体の上に置きたい要素 ...
<!-- base(外枠のための要素): sizeを指定して外枠を定義 -->
<item>
<shape android:shape="rectangle" >
<solid android:color="@android:color/transparent" />
<size
android:height="422dp"
android:width="360dp" />
</shape>
</item>
</layer-list>
view raw gistfile1.xml hosted with ❤ by GitHub


itemとshapeの間にrotate要素を入れて回転を設定


shapeを回転させるには、item要素の直下にshape要素を置かずに、 item → rotate → shape という具合にrotate要素を挟み、その属性で回転角度・回転軸(pivot)を指定します。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- left antenna's stroke -->
<item
android:bottom="350dp"
android:left="85dp"
android:right="248dp"
android:top="0dp">
<rotate
android:fromDegrees="-30"
android:pivotX="80%"
android:pivotY="0%"
android:toDegrees="00" > <!-- toDegreesはここでは意味がない -->
<shape android:shape="rectangle" >
<solid android:color="@android:color/white" />
<corners android:radius="50dp" />
</shape>
</rotate>
</item>
... その他のitem要素 ...
</layer-list>
view raw gistfile1.xml hosted with ❤ by GitHub


ovalのshapeはstrokeのdashGap,dashWidthを調整すれば円弧が描ける


shapeは本来は、矩形・丸・線・円の4つの簡単な図形しか描けませんが、無理やり円弧を描くこともできます。
stroke要素のdashGap属性,dashWidth属性の記述によって、ある程度は円の切れ方を指定できるので、それを利用します。

これは、わかめねこの鼻の部分のソースです。
2つの円弧をずらして配置し「ω」という形にしています。
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
... その他のitem要素 ...
<!-- nose, left -->
<item
android:bottom="247dp"
android:left="113dp"
android:right="155dp"
android:top="121dp">
<shape android:shape="oval" >
<stroke
android:dashGap="25dp"
android:dashWidth="60dp"
android:width="5dp"
android:color="@android:color/black" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
<!-- nose, right -->
<item
android:bottom="247dp"
android:left="140dp"
android:right="128dp"
android:top="121dp">
<shape android:shape="oval" >
<stroke
android:dashGap="25dp"
android:dashWidth="45dp"
android:width="5dp"
android:color="@android:color/black" />
<solid android:color="@android:color/transparent" />
</shape>
</item>
... その他のitem要素 ...
</layer-list>
view raw gistfile1.xml hosted with ❤ by GitHub

これらの手段を組み合わせれば、わかめねこの様なイラストもdrawable.xmlで描けてしまう、という訳です。


Gingerbread以前では?

ちなみに、これらのコードはICS以降向けです。
Gingerbread以前のバージョンでも動きますが、rotate指定の回転の基準点が全く違う箇所になる様で、rotateを使っているshapeがずれてしまいます。※ Honeycombで試してない(てへぺろ


上記はレイアウトエディタのスクリーンショットですが実機でも同じ様な表示となります。
左アンテナは上にずれ、右アンテナは下にずれて胴体の下に隠れてしまっています(マウスカーソルのある辺りにあります)。

ちゃんと確認してはいないんですが、どうやら以下のような違いがあるのでは、と考えています。
ICS以降
paddingの内側の描画されている範囲の左上が回転軸の基点
Gingerbread以前
paddingの外側の一番大きい要素の左上が回転軸の基点



まとめ

勢いだけで、大分役に立たない記事と相成りました。

ただ、png画像など用意せずにxmlの記述だけでもここまで描けるというのがわかっていただけと思うので、今後アプリを作ったりデザインしたりする方々には是非覚えておいていただきたいです。上手く使えば様々な画面解像度への対応が捗るんではないでしょうか。今後もハードウェアの技術向上は続きますから、HTC J butterflyのxxhdpiみたいなショックが来るかも知れませんからね。

「Androidもうええねん」なんて声も聞こえますが、まだまだAndroidは続きますよ!
オレはようやくのぼりはじめたばかりだからな。このはてしなく遠いAndroid坂をよ…

さて、明日は @awwa500 さんと @takke さんの担当です。
何故か新年までカウントしていくAndroid Advent Calender 2012。どうぞ大晦日の大トリ記事までよろしくお願いします。



今回作ったもの