Android は、外形やイメージを描画および動画化するカスタムの 2D グラフィックスライブラリを提供しています。 android.graphics.drawable と android.view.animation パッケージに2次元での描画と動画に使用する共通クラスがあるのが分かると思います。
このドキュメントは Android アプリケーションにおいてグラフィックスを描画するための入門書です。 Drawable オブジェクトを使用したグラフィックスを描画するための基本、Drawable クラスのふたつのサブクラスの使用方法、および単一のグラフィックスを tween する(動かす、伸ばす、回転させる) か、または ( フィルムのような ) 一連のグラフィックスを動画化するかのいずれかでアニメーションを作成する方法について説明していきます。
Drawable
Drawable は、"描画が可能なもの" を表す一般的な概念です。さまざまな特殊な種類の描画可能なグラフィックス用に定義されている、BitmapDrawable、ShapeDrawable、PictureDrawable、LayerDrawable、さらにいくつかの Drawable の拡張クラスが見受けられることでしょう。当然ですが、それらを拡張し、ユニークな振る舞いを行う独自のカスタム Drawable オブジェクトを定義することもできます。
Drawable を定義しインスタンス化するには、プロジェクトのリソースに保存されているイメージを使用する、Drawable プロパティを定義した XML を使用する、通常のクラスのコンストラクタを使用する、という3通りの方法があります。以下に最初の2つ ( コンストラクタを使用する方法は経験を積んだ開発者には今更ですので... ) のテクニックを説明したいと思います。
リソースイメージからの作成
アプリケーションにグラフィックスを加える簡単な方法のひとつに、プロジェクトのリソースのイメージファイルを参照するというのがあります。サポートされているファイルタイプは、PNG (推奨)、JPG (容認) および GIF (非推奨) です。これは特にアプリケーションのアイコン、ロゴ、ゲームなどで使用されるその他のグラフィックなどで推奨しているテクニックです。
イメージリソースを使用するには、プロジェクトの res/drawable/ ディレクトリにただファイルを追加するだけです。ここがコードまたは XML レイアウトからリソースを参照できる場所になります。いずれの方法でも、ファイルタイプの拡張子を除いたファイル名 (例: my_image.png はmy_image として参照 ) のレファレンス ID を使用して参照されます。
注意: res/drawable/ に置かれたイメージリソースは、 aapt ツールにより自動的に圧縮される ( イメージのロスなく ) 可能性があります。例えば、256 色以上必要としない True カラー PNG はカラーパレット上では 8 ビット PNG に変換される可能性があります。これは結果的には、品質としては同じイメージで、必要なメモリが少なく済むことになります。なので、イメージバイナリをこのディレクトリに置くと、ビルド時に変更されるということを知っておいてください。ビットマップに変換することを意図したイメージをビットストリームとして読み込むことをプランしている場合は、イメージを上記とは別の res/raw/ フォルダに配置してください。このフォルダにあるファイルは最適化の対象外となります。
サンプルコード
以下のコードは Drawable リソースからのイメージを使用した ImageView を作成し、それをレイアウトに追加する方法をデモしたコードスニペットです。
LinearLayout mLinearLayout;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a LinearLayout in which to add the ImageView
mLinearLayout = new LinearLayout(this);
// Instantiate an ImageView and define its properties
ImageView i = new ImageView(this);
i.setImageResource(R.drawable.my_image);
i.setAdjustViewBounds(true); // set the ImageView bounds to match the Drawable's dimensions
i.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
// Add the ImageView to the layout and set the layout as the content view
mLinearLayout.addView(i);
setContentView(mLinearLayout);
}
他のケースでは、イメージリソースをオブジェクトとしてハンドリングしたい場合があるかもしれません。そのようにするには、以下のようにして Drawable オブジェクトを作成します。
Resources res = mContext.getResources();
Drawable myImage = res.getDrawable(R.drawable.my_image);
注意: プロジェクト内のユニークなリソースそれぞれに唯一の状態しか保持できず、それを元に異なるオブジェクトとしてたくさんインスタンス化しても同じことです。例えば、同じイメージリソースから、ふたつの Drawable オブジェクトをインスタンス化し、その片方の Drawable のプロパティ ( アルファチャネルなど ) を変更すると、他のものにも影響を与えてしまいます。したがって、ひとつのイメージリソースから複数のインスタンスを生成しそれらを扱う場合は、直接 Drawable に変換するのではなく、Tween アニメーション を使用すべきでしょう。
サンプル XML
以下は、XML レイアウトの ImageView に Drawable リソースを追加する方法を示したXML スニペットです (お遊びで赤っぽくしてみます) 。
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="#55ff0000"
android:src="@drawable/my_image"/>
プロジェクトリソースの使用に関するさらに詳しい情報については、リソースとアセット を読んでください。
リソース XML からの作成
それでは今から、Android の ユーザインターフェイス 開発の原理について慣れ親しんでいきましょう。 今から、XML でのオブジェクト定義に備わっている能力と柔軟さについて理解することになります。 この根本理念は View から Drawable へと引き継がれています。始めのうちはその変数がアプリケーションやユーザの介入による影響がないような Drawable オブジェクトを作成したい場合は、Drawable を XML で定義しておくのが良い選択でしょう。ユーザがアプリケーションを使用している間に Drawable のプロパティが変更されそれを期待している場合でも、いちどインスタンス化されてしまえばいつでもプロパティの変更は可能ですので、オブジェクトは XML で定義しておくべきでしょう。
XML に Drawable を定義したら、プロジェクトの res/drawable/ ディレクトリにそのファイルを保存します。その後、XML ファイルのリソース ID を渡して Resources.getDrawable() 呼び出し、そのオブジェクトを取得およびインスタンス化します ( 下のサンプル 参照 ) 。
inflate() メソッドをサポートした Drawable のサブクラスであれば、XML での定義とアプリケーションでのインスタンス化が可能です。XML インフレーションをサポートした各 Drawable は、そのオブジェクトのプロパティ定義を支援する XML 特有の特定可能な属性が利用できます (何があるかについてはそのクラスリファレンスを参照してください) 。各 Drawable のサブクラスの XML での定義方法に関する情報は、クラスのドキュメントを参照してください。
サンプル
以下は TransitionDrawable を定義した XML です。
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/image_expand">
<item android:drawable="@drawable/image_collapse">
</transition>
この XML を res/drawable/expand_collapse.xml で保存し、以下のコードで TransitionDrawable のインスタンス化と ImageView のコンテントとしての設定を行います。
Resources res = mContext.getResources();
TransitionDrawable transition = (TransitionDrawable) res.getDrawable(R.drawable.expand_collapse);
ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);
その後、この遷移が先に進みます ( 1 秒間 ) 。
transition.startTransition(1000);
上記で列挙した Drawable クラスの XML でサポートされている属性に関するより詳しい情報はそのリファレンスを参照してください。
ShapeDrawable
動的な2次元グラフィックスを描画したい場合、おそらく ShapeDrawable オブジェクトがその要求に適合していると思います。ShapeDrawable を使って、プログラムから基本的な外形を描き、可能な限りのスタイルをさまざまな方法で形づくることができます。
ShapeDrawable は Drawable の拡張であることから、Drawable が配置できる場所であれば使用可能で、その場所が View の背景だとしたら 、setBackgroundDrawable() で設定します。当然、外形をレイアウトに期待した通りに、独自のカスタム View として描画することもできます。ShapeDrawable には自身の draw() メソッドを持っているので、 View.onDraw() の実行中に ShapeDrawable を描画する(そのdraw を呼び出す) VIew のサブクラスを作成することができます。以下では ShapeDrawable を View として描画するために、基本的な View クラスの拡張して、先ほど説明したことを行っています。
public class CustomDrawableView extends View {
private ShapeDrawable mDrawable;
public CustomDrawableView(Context context) {
super(context);
int x = 10;
int y = 10;
int width = 300;
int height = 50;
mDrawable = new ShapeDrawable(new OvalShape());
mDrawable.getPaint().setColor(0xff74AC23);
mDrawable.setBounds(x, y, x + width, y + height);
}
protected void onDraw(Canvas canvas) {
mDrawable.draw(canvas);
}
}
コンストラクタで ShapeDrawable は OvalShape として定義されています。その後、色が与えられ外形の境界が設定されています。境界を設定しない場合は外形は描画されませんが、色を設定しない場合はデフォルトの黒になります。
定義済みのカスタム View を使用すれば、いかようにでも描画できるようになります。上のサンプルのクラスを使用して、以下のように Activity 内でこの外形をプログラムから描画が可能となります。
CustomDrawableView mCustomDrawableView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCustomDrawableView = new CustomDrawableView(this);
setContentView(mCustomDrawableView);
}
Activity からではなく、XML レイアウトからこのカスタム Drawable を描画したい場合は、CustomDrawable は、XMLからのインフレートを介して View をインスタンス化するときに呼び出される View(Context, AttributeSet) コンストラクタをオーバーライドする必要があります。以下のようにして CustomDrawable 要素を XML に追加します。
<com.example.shapedrawable.CustomDrawableView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
ShapeDrawable クラス (その他多くの Drawable タイプ同様 android.graphics.drawable パッケージにあります) は、Drawable のさまざまなプロパティをpublic メソッドで定義することを許可しています。アルファチャネル、カラーフィルタ、ディザ、透明度、および色といったプロパティを調整したいに違いないからです。
NinePatchDrawable
NinePatchDrawable グラフィックスは、伸ばすことができるビットマップイメージで、Android は、背景として配置した View のコンテントを調整する目的で、自動的にリサイズします。NinePatch が使用されているひとつの例として、Android の標準ボタンの背景が挙げられます。 ボタンはさまざまな長さの文字列の違いによる調整のため伸ばすことが必要だからです。NinePatch Drawable は標準の PNG イメージに 1 ピクセル幅の境界線が付加されたものです。これは .9.png という拡張子で保存され、プロジェクトの res/drawable/ ディレクトリに格納されている必要があります。
この境界線はイメージの伸ばせる領域および静的領域を定義するために使用されています。左と上にある境界線にあるひとつ (またはそれ以上) の 1 ピクセル幅の黒色の線を描画することで伸ばせる区域を明示します (伸ばせる区域はいくつでも指定可能です) 。伸ばせる区域の相対的なサイズは同じままなので、最大の区域は常に最大のままです
右と下に線を引くことにより、オプションのイメージ ( 事実上パディングの線となる ) の区域を定義することも可能です。View オブジェクトがその背景として NinePatch を設定し、そこに View のテキストを特定した場合は、右と下の線で指定した ( パディングの線が含まれている場合 ) 領域内にすべてのテキストが収まるようその区域を伸長させます。 パディングの線が含まれていない場合、Android はこの描画可能領域として定義するために、左と上の線を使用します。
ここで、この異なる線の違いを明確にしておきますが、左と上の線は、イメージを伸ばすために反復が許されるイメージのピクセルを定義しています。下と右の線は、View のコンテンツが位置することが許されているイメージの範囲内での相対領域を定義しています。
これは、ボタンを定義した NinePatch のサンプルです。
NinePatch は伸ばせる領域を左と上の線で、描画可能な領域を下と右の線で定義します。上のイメージのグレーの点線はイメージを伸ばすために反復されるイメージの領域を識別します。下のイメージのピンクの長方形は View のコンテントが許容される領域を識別しています。 コンテンツがこの領域に合わない場合は、それなりにイメージの伸縮が起こります。
Draw 9-patch ツールは、 WYSIWYG グラフィックスエディタを採用しており、 NinePatch イメージを作成する上でとても重宝します。また、伸ばせる領域として定義した範囲が、ピクセルの反復の結果、作成した描画体にダメージを与えるリスクがある場合には警告を出してくれます。
サンプル XML
これは、ふたつのボタンに NinePatch を追加する方法をデモしたレイアウト XML のサンプルです ( NinePatch イメージは res/drawable/my_button_background.9.png として保存されています )。
<Button id="@+id/tiny"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:text="Tiny"
android:textSize="8sp"
android:background="@drawable/my_button_background"/>
<Button id="@+id/big"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerInParent="true"
android:text="Biiiiiiig text!"
android:textSize="30sp"
android:background="@drawable/my_button_background"/>
width と height の "wrap_content" は、ボタンをきれいにテキストの外側にぴったり合わせるという指定であるという点に注目してください。
下は、 このXMLと 上で示したNinePatch イメージからレンダリングされたふたつのボタンです。ボタンの幅や高さがテキストにより変化し、背景のイメージが反復され伸びていることがこれから見て取れます。
Tween アニメーション
Tween アニメーションは、View オブジェクトのコンテンツに対し、単純な変形に関わる動作 ( 位置、サイズ、回転、および透過 ) を実行できます。それにより、オブジェクトがある場合、そのテキストを動かしたり、伸ばしたり、縮めたりすることが可能です。そこに背景のイメージがある場合、その背景のイメージもそのテキストと一緒に変化します。animation パッケージ では、Tween アニメーションで使用するすべてのクラスを提供しています。
アニメーションの指示は、Tween アニメーションの定義、XML または Andoroid コード による定義の順で行います。レイアウトの定義と同様に、ハードコードのアニメーションに比べ、読みやすく、再利用しやすく、交換しやすいXML ファイルを推奨します。以下のサンプルでは、XML を使用します。 ( XML ではなく、アプリケーションのコードでアニメーション定義について学ぶには、 AnimationSet クラスとその他の Animation サブクラスを参照してください ) 。
このアニメーション指示は、発生させたい変形を定義しますが、それをいつ変形するのか、変形がどれくらいの間受け入れるのかということについても定義します。変形は、順次にも同時にも可能で、例えば、TextView のコンテンツを左から右に動かしてから180 度回転させたり、また動かすのと回転させるのを同時に行うこともできます。各変形動作に対しては、その変形に対する特定のパラメータのセット ( サイズの変更に対する開始のサイズと終了のサイズ、および回転に対する開始の角度と終了のサイズ、など ) およびその共通のパラメータのセット( 例えば、開始時間と期間 ) を受け付けます。いくつかの変形を同時に発生させるには、それらのパラメータに同じ開始時間を指定し、順次に発生させるには変更を見越した開始時間とその期間を計算して指定します。
アニメーション XML ファイルは、Andorid プロジェクトの res/anim/ ディレクトリに属しています。このファイルにはひとつのルート要素が必要で、 <alpha>、 <scale>、 <translate>、 <rotate> の Interpolator ( 補間 ) 要素のうちのひとつ、またはそれらの要素のグループを保持する <set> 要素 ( その中にまた別の <set> が含まれることもあります ) がルート要素になります。すべてのアニメーション指示は、デフォルトで同時に適用されます。順次に発生させるには、以下のサンプルに示すように startOffset 属性を特定する必要があります。
以下は、ApiDemos のひとつからの抜粋した XML で、伸長が使われていて、 View オブジェクトがスピンと回転を同時に行います。
<set android:shareInterpolator="false">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="700" />
<set android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:fromXScale="1.4"
android:toXScale="0.0"
android:fromYScale="0.6"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400"
android:fillBefore="false" />
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400" />
</set>
</set>
スクリーン座標 ( このサンプルでは未使用 ) は、左上の角が ( 0 , 0 ) で、下方向と右方向に行くに従い増えていきます。
pivotX のように、値には自身のオブジェクトに対して相対値、または、親に対して相対値を指定可能なものもあります。希望通りに設定できるよう、適切フォーマットを確実に使用してください ( "50" は親に対して 50% の相対値、"50%" は自身に対して 50% の相対値 ) 。
時間による変形の方式を、 Interpolator で割り当てられた値で決めることができます。 Android には、いくつかの Interpolator のサブクラスがあり、それらにはさまざまなスピード曲線が指定されています。例えば、 AccelerateInterpolator により始めは遅く徐々に速くなる変形方法が指定できます。XML において、各要素で指定可能なひとつの属性です。
プロジェクトの res/anim/ ディレクトリに hyperspace_jump.xml で保存されたこの XML を使って、この Java コードでそれを参照し、レイアウトからの ImageView にそれを適用します。
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);
startAnimation() に対する別の方法としては、 Animation.setStartTime() で、このアニメーションに開始する時間を定義でき、その後 View.setAnimation() で View にアニメーションを割り当てます。
XML での構文、利用可能なタグおよび属性に関する詳しい情報は、 利用可能なリソース でのアニメーションに関する説明を参照してください。
注意: アニメーションを動かしたり、リサイズしたりする方法とは無関係に、アニメーションとして保持している View の境界は自動的に調整はされません。それでもなお、そのView の境界を越えた部分のアニメーションも描画され、クリッピングされることはありません。しかしながら、親のビューの境界を越えた場合は、アニメーションのクリッピングが 発生します 。
Frame アニメーション
これは、異なるイメージを順番につなげるという考えで作られた従来のアニメーションで、フィルムのように順番通りに再生するものです。AnimationDrawable クラスが Frame アニメーションの基本クラスとなります。
コード内で AnimationDrawable クラスの API を使用して、アニメーションのフレームを定義できますが、アニメーションを構成したフレームをひとつのXML ファイルでリストする方がより簡単にそれを実現できます。上の Tween アニメーションと同様に、この種のアニメーションに対する XML ファイルは、Android プロジェクトの res/anim/ ディレクトリに配置させます。このケースでは、アニメーションのフレームの順番と期間の指示は各フレームにより行われます。
XML ファイルの構成としては、 <animation-list> 要素がルートノードとしてあり、フレームに対する Drawable リソースとそのフレームの期間を指定した <item> の子ノードが複数のフレームとして定義されています。 以下がフレームが並んだアニメーションの XML ファイルのサンプルです。
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
このアニメーションは単純に3つのフレームを実行します。このリストの android:oneshot 属性を true に設定することにより、このサイクルは1回のみとなり、最後のフレームで止まった状態になります。これが false に設定された場合はこのアニメーションはループします。プロジェクトの res/anim/ ディレクトリに rocket_thrust.xml でこの XML ファイルを保存し、それを使ってView の背景イメージに追加し、それを再生することができます。以下のアクティビティは、 ImageView にアニメーションを追加し、スクリーンがタッチされたときにそのアニメーションを再生するサンプルです。
AnimationDrawable rocketAnimation;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.anim.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
AnimationDrawable の start() メソッド呼び出しは、AnimationDrawable が完全にウィンドウにアタッチされていないことから、アクティビティの onCreate() メソッドの間では呼び出せないという点に十分注意してください。 相互動作による要求なしで、アニメーションを即時に再生したい場合は、アクティビティのAndroid がウィンドウにフォーカスしたときに呼び出されるメソッドである onWindowFocusChanged() で呼び出すことにより望んでいた動作が実現できるかもしれません。