minimize

事業拡大のため、新しい仲間を募集しています。
→詳しくはこちら

Android は、図形やイメージの描画およびアニメーションをするための
カスタム2Dグラフィックライブラリを提供しています。
それらは android.graphics.drawable と android.view.animation パッケージにあります。

ここでは、Drawable オブジェクトを使った描画の基本を説明します。
移動・伸縮・回転を使ったアニメーション、および
「パラパラマンガ」の要領で作るアニメーションについても紹介します。

Drawables

Drawable は「描くことができるもの」を表す抽象クラスです。
BitmapDrawable, ShapeDrawable, PictureDrawable, LayerDrawable など様々なものが使えます。
もちろん、あなたはこれらをさらにカスタマイズ(サブクラス化)して使うことも可能です。

Drawable インスタンスを生成するには3つの方法があります。

上の二つの方法について、紹介します。
(コンストラクタを使う方法は、特に紹介すべき新しい点はありません)

イメージファイルによる生成

グラフィックをアプリケーションに組み込む簡単な方法は、
イメージファイルにプロジェクトリソースを使うことです。
PNG(推奨)、JPG(対応可能)、GIF(非推奨)の3種類をサポートしています。

アプリケーションのアイコンやロゴ、その他ゲームで使うような各種グラフィックには
この手法が適しているでしょう。

res/drawable/ ディレクトリの下にイメージのファイルを置くだけです。
これで R クラスには対応するIDが割り振られ、それを使うことができます。
例えば my_image.png ファイルを置いたら

ImageView i = new ImageView(this);
i.setImageResource(R.drawable.my_image);

によってそのイメージを持ったViewを作成することができます。
Drawable インスタンスを得たい場合は、

Drawable myImage = mContext.getResources().getDrawable(R.drawable.my_image);

とします。

ワンポイント

res/drawable/ ディレクトリの下に配置したイメージファイルは、
aapt ツールによって自動的に(ロスの少ない形式で)最適化され圧縮されます。
例えばフルカラーのPNGは8bitのカラーパレットに変換されるので、256色以上の情報は必要ありません。
これによって、元のファイルと全く同じ品質を保ったままファイルサイズを小さくすることができます。

ですから、このディレクトリに配置したファイルはビルドと同時に変化する可能性があることを
意識しておいて下さい。

もしこれを望まない場合、代わりに res/raw/ フォルダにファイルを置いて下さい。
こうすれば、そこに置かれたファイルは最適化されません。

XMLファイルによる生成

今まで、View をXMLファイルで生成する方法については紹介してきました。
Drawable も同じように、XMLファイルによって生成することができます。

もし生成しようとしている Drawable がスタティックな性質を持つならば
XMLによる生成をお勧めします。

さらに、あるプロパティの値を動的に変えたい場合でさえ、XMLによる生成を使うことができます。
なぜなら、生成時に渡すプロパティの値はいつでも(プログラムコード上で)変更することができるからです。
スタティックな部分をXMLで作っておき、ダイナミックな部分だけを
コードによって変更すればよいでしょう。

TransitionDrawable による例を以下に挙げます。

res/drawable/expand_collapse.xml

<transition xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:drawable="@drawable/image_expand">
  <item android:drawable="@drawable/image_collapse">
</transition>

これを使ってViewを生成するには、以下のようにします。

Resources res = mContext.getResources();
TransitionDrawable transition = (TransitionDrawable)res.getDrawable(R.drawable.expand_collapse);
ImageView image = (ImageView)findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

この例では、ImageView と TransitionDrawable を組み合わせて使っています。

ShapeDrawable

直接二次元のキャンバスに描きたいときは、ShapeDrawable が適しているでしょう。
基本的な形状の描画、そしてそれをスタイル付けすることができます。
setBackgroundDrawable() によって背景をセットし、その上に描画するというのが一般的です。

これを任意の View に適用することができます。

public class CustomDrawableView extends View {
    private ShapeDrawable mDrawable;
    public CustomDrawableView(Context context) {
        super(context);
        mDrawable = new ShapeDrawable(new OvalShape());
        ...
    }
    protected void onDraw(Canvas canvas) {
        mDrawable.draw(canvas);
    }
}

このように、onDraw メソッドを実装してその中で canvas に対して draw します。
さらにこの View を Activity に表示させたかったら、以下のようにします。

public class SampleActivity extends Activity {
    private CustomDrawableView mCustomDrawableView;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCustomDrawableView = new CustomDrawableView(this);
        setContentView(mCustomDrawableView);
    }
}

これはXMLで実現することもできます。

layout.xml

<com.example.shapedrawable.CustomDrawableView
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    />

NinePatchDrawable

これは、伸縮可能なビットマップ形式です。
これを使うと、Android はサイズに応じて自動的に画像を拡張・縮小します。
NinePatch 形式のビットマップは、.9.png の拡張子で作成されます。
これを res/drawable/ の下に置きます。
付属する draw9patch ツールを使って、NinePatch 形式のビットマップを作成することができます。

ボタンに NinePatch のイメージを適用する例を挙げます。
res/drawable/my_button_background.9.png を配置して、以下のXMLを用意します。

layout.xml

<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"/>

このように、通常のイメージと同じように使用することができます。

Tween Animation

View が持つコンテンツに対して、位置移動・サイズ変更・回転・そして透明度の変更という
シンプルな変換処理を実施するアニメーション方法です。

XMLやプログラムコードによって、アニメーションのセットを定義することができます。
レイアウト等を定義するときと同様、XMLファイルを使った方法が推奨されます。
そちらの方がハードコードよりも読みやすく、再利用しやすく、交換しやすいからです。

アニメーション用のXMLファイルは、res/anim/ に配置します。
このファイルは、<alpha>, <scale>, <translate>, <rotate> または <set> をルート要素に持ちます。
<set> の中には、これら5つの要素を子要素として入れることができます。

res/anim/hyperspace_jump.xml

<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>

ルート要素は set です。ここに、scale と set 要素が入っています。
内側の set の中に、scale と rotate が入っています。

先頭の scale 要素では、スケール(サイズ)の変換をしています。
1.0 x 1.0 → 1.4 x 0.6 という風に、元の大きさからX方向に拡大させY方法に縮小させています。
pivot というのは、拡大・縮小するときの中心点を表します。
50%ということは、キャンバスの中央を中心にするということです。
startOffset はアニメーション開始時間、duration はアニメーション実行時間です。(単位は ms)

interpolator を使うと、アニメーションのスピード曲線を制御することができます。
例えば decelerate_interpolator は、通常の速度から徐々に遅くなるようにアニメーションします。

このアニメーションを使うには、以下のようにします。

ImageView spaceshipImage = (ImageView)findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);

このように、AnimationUtils によってXMLリソースから Animation インスタンスを生成し
View.startAnimation でビューに対してアニメーションを適用します。

その場ではなく決まった時間にアニメーションさせたい場合は
Animation.setStartTime() で開始時間を設定し、View.setAnimation() によってセットしておきます。
これで、指定した時間にアニメーションが始まります。

Frame Animation

いわゆる「パラパラマンガ」形式のアニメーションです。

res/anim/rocket_thrust.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>

このように、複数の Drawable を用意してそれを duration 時間単位で切り替えます。
oneshot = "false" にすると、この処理を繰り返し行います。

public class SampleActivity extends Activity {
  private 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);
  }
}

Activity.onCreate() で、ImageView の背景にアニメーションをセットしています。
このように、アニメーションも Drawable の一種です。
この時点ではまだアニメーションはしません。
スクリーンタッチイベントに反応すると、AnimationDrawable.start() によってアニメーションが開始されます。

ワンポイント

一つ注意点があります。onCreate() の時点で start() してはいけません。
Activity のライフサイクルを見るとわかりますが、この時点ではまだ Activity は画面に表示されていません。
よって、ここでアニメーションを開始することはできないのです。

Activity を起動してすぐにアニメーションを開始させたい場合は、
Activity.onWindowFocusChanged() を実装してその中で start() させましょう。