アカベコマイリ

HEAR NOTHING SEE NOTHING SAY NOTHING

Android で SVG

Android アプリで SVG を描画する方法について調べてみた。

ライブラリのプロジェクト組み込み

Android 標準では SVG 描画をサポートしていないため代わりに svg-android というライブラリを入手する。これは以下のサイトで公開されている。ライセンスは Apache License 2.0

ライブラリは JAR ファイル形式。現時点の最新は svg-android-1.1.jar。今回は SvgViewer という Android プロジェクトを作成して組み込んでみる。手順は以下のようになる。

  1. Eclipse 上で SvgViewer という Android プロジェクトを作成
  2. プロジェクトの直下に lib というフォルダを作成
  3. lib に JAR ファイルをコピー
  4. パッケージ・エクスプローラー上でプロジェクトを選択
  5. コンテキストメニューから「リフレッシュ」を選択、これで lib/svg-android-1.1.jar が表示されるはず
  6. パッケージ・エクスプローラー上の lib 内にある svg-android-1.1.jar を選択
  7. コンテキストメニューから「ビルド・パス」→「ビルド・パスに追加」を選択

組み込みに成功すると svg-android-1.1.jar がプロジェクト内の参照ライブラリとなり com.larvalabs.svgandroid パッケージが使用できるようになる。

サンプル アプリ

いろいろな SVG ファイルを閲覧したいので簡単なファイラー機能を持ったアプリを作ってみる。仕様は以下。

  • 開いているフォルダ内のファイル ( SVG のみ ) とサブ フォルダをリスト形式で表示
  • 画面上部に開いているフォルダのパスを表示
  • パスの隣にあるボタンを押すと上の階層に戻れる
  • リスト上のファイルとサブ フォルダはアイコンを分ける
  • アイコンはリソース内の SVG ファイルを利用する
  • リスト上のサブ フォルダをタップすると中に移動
  • リスト上のファイルをタップすると SVG ファイル画面に遷移

リスト表示する SVG 形式のアイコンは以下を使用する。

そのまま使うとリストには大きすぎるため Inkscape を利用して 48x48 にサイズ調整した。これらをプロジェクトの res/drawable に icfile.svg、icfolder.svg というファイルとして格納して ListView に関連づける Adapter クラスで以下のように読み込む。

/**
 * インスタンスを初期化します。
 *
 * @param context            コンテキスト。
 * @param textViewResourceId レイアウト。
 * @param objects            管理対象とするアイテムのコレクション。
 */
public FileListItemAdapter( Context context, int textViewResourceId ) {
    super( context, textViewResourceId );
    this.mLayoutInflater = ( LayoutInflater )context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );

    Resources r = context.getResources();
    this.mFileIcon   = this.loadIcon( r, R.drawable.ic_file   );
    this.mFolderIcon = this.loadIcon( r, R.drawable.ic_folder );
}

/**
 * SVG 形式のアイコンを Drawable として読み込みます。
 *
 * @param r  リソース管理オブジェクトのインスタンス。
 * @param id アイコンのリソース識別子。
 *
 * @return 読み込まれた Drawable。
 */
private Drawable loadIcon( Resources r, int id ) {
    SVG svg = SVGParser.getSVGFromResource( r, id );
    return svg.createPictureDrawable();
} 

SVG の読み込みは com.larvalabs.svgandroid パッケージに定義された SVGParser クラスでおこなう。getSVGFrom~ 系メソッドが複数あるので読み込み方法にあわせて選択する。これらのメソッドはすべて SVG クラスのインスタンスを返す。

実際に描画するためのデータは SVG インスタンスから取得する。コントロールの描画要素としたいなら SVG.createPictureDrawable から得られる PictureDrawableCanvas で画像の一部として扱いたい場合は SVG.getPictureメソッドから Picture といった感じで使い分ける。ファイル一覧の構築はフォルダが選択される度に以下のメソッドを呼び出して実行する。

/**
 * ファイル一覧を更新します。
 *
 * @param dir ディレクトリ。
 */
private void updateFileList( File dir ) {
    this.mCurrentDirTextView.setText( dir.getPath() );

    // 親ディレクトリの有無で移動きりかえ
    this.mParentDir = dir.getParent();
    this.mUpDirButton.setEnabled( this.mParentDir != null );

    // ファイル一覧の更新
    {
        this.mFileListAdapter.clear();

        File[] files = dir.listFiles( mFilter );
        if( files != null ) {
            for( File file : files ) {
                this.mFileListAdapter.add( file );
            }
        }

        this.mFileListAdapter.notifyDataSetChanged();
    }
}

mFileListAdapter が前述の Adapter クラス。ファイル ビューア系のサンプルでフォルダを開く度に Adapter を作り直すものをよく見るが、今回のものは SVG から生成した画像をひとつのインスタンスで使い回したいため Adapter 自体を更新する方式にした。

ArrayAdapter.clear ですべての要素を消去。以降は add で追加されてゆく。要素の更新が完了したらArrayAdapter.notifyDataSetChanged を呼び出すことで ListView 側へ通知される。

もう一つ重要な機能としてファイルのフィルターがある。これは Java でお馴染みの FileFilter を仕様。以下のように FileFilter 派生クラスを定義して File.listFiles(FileFilter) に指定すればフィルターされる。

/**
 * ファイル情報のコレクションから、SVG ファイルとディレクトリを抽出します。
 */
public class SvgFileFilter implements FileFilter {
    /**
     * SVG ファイルの拡張子。
     */
    private static final String SVG_EXTENSION = ".svg";

    /**
     * フィルタリングを実行します。
     *
     * @param file ファイル情報。
     *
     * @return ファイル情報がフィルタリングの対象だった場合は true。それ以外は false。
     */
    public boolean accept( File file ) {
        return ( file.isDirectory() || file.getName().endsWith( SVG_EXTENSION ) );
    }
}

リストから SVG ファイルが選ばれた時は、SVG を表示するための Activity に遷移する。読み込み対象はストレージ上のファイルとなるので遷移元からファイルのパスを Intent.putExtra で渡し、それから構築した InputStream を指定して SVG を読む。

/**
 * SVG ファイルを読み込みます。
 *
 * @param path SVG ファイルへのパス情報。
 */
private void loadSvg( String path ) {
    try {
        this.mStream = new FileInputStream( new File( path ) );

        SVG svg = SVGParser.getSVGFromInputStream( this.mStream );

        if( svg != null ) {
            this.mSvgImageView.setImageDrawable( svg.createPictureDrawable() );
        }

    } catch( Exception e ) {
        this.showErrorMessage( e.getMessage() );
    }
}

アプリを実行すると以下のようになる。

サンプル アプリ

最後にサンプル プログラムのプロジェクトを公開しておく。

Android 2.1 update 1 (API Level 7) でビルド、エミュレータと初代 Xperia (SO-01B) にて動作確認した。

感想

現時点では読めない SVG ファイルが多い。例えば OpenClipArt から入手したものはほとんどエラーになる。単純な図形でもレンダリングが崩れることもあるのでパーサーの作り込みが足りないのだろう。

また読み込まれるサイズが SVG の定義に依存する点も残念。Picture/PictureDrawable になる前、つまりベクター状態でリサイズできるようになって欲しい。そうなれば GUI パーツとして利用しやすくなる。この辺りも将来に期待か。とはいえ SVG を Android の図形オブジェクトに変換するというアイディアは秀逸。将来は Android も WPF や Siliverlight のようにベクタ形式をサポートする可能性もあるが svg-android なら今すぐ対応できる。

今回のサンプルのように GUI リソースとして使えば画像を用いずに複雑な図形を描画できる。これは多くの解像度に対応しなければならない Android アプリにとって大きなメリットになりそうだ。

Comments from WordPress

  • 通りすがり 2012-03-19T09:46:18Z

    現時点では読めない SVG ファイルが多い。例えば OpenClipArt から入手したものは、 ほとんどエラーになる。単純な図形でもレンダリングが崩れることもあるので、 パーサーの作り込みが足りないのだろう。

    svg-android は SVG のモバイル向けのプロファイルである SVG Basic 1.1 をサポートするライブラリなのでフルセットの SVG で作られた画像には対応していませんし今後も対応しないでしょう。