Android アプリで SVG を描画する方法について調べてみた。
ライブラリのプロジェクト組み込み
Android 標準では SVG 描画をサポートしていないので、代わりに svg-android というライブラリを入手する。これは以下のサイトで公開されている。ライセンスは Apache License 2.0 となる。
svg-android – SVG parsing and rendering for Android
ライブラリは JAR ファイルとして配布されている。現時点の最新は svg-android-1.1.jar。
今回は SvgViewer という Android プロジェクトを作成し、そこへ組み込んでみる。手順は以下のようになる。
- Eclipse 上で SvgViewer という Android プロジェクトを作成
- プロジェクトの直下に lib というフォルダを作成
- lib に JAR ファイルをコピー
- パッケージ・エクスプローラー上でプロジェクトを選択
- コンテキストメニューから「リフレッシュ」を選択、これで lib/svg-android-1.1.jar が表示されるはず
- パッケージ・エクスプローラー上の lib 内にある svg-android-1.1.jar を選択
- コンテキストメニューから「ビルド・パス」→「ビルド・パスに追加」を選択
組み込みに成功すると svg-android-1.1.jar がプロジェクト内の参照ライブラリとなり、com.larvalabs.svgandroid パッケージが使用できるようになる。
サンプル アプリ
いろいろな SVG ファイルを閲覧したいので、簡単なファイラー機能を持ったアプリを作ってみる。仕様は以下のようになる。
- 開いているフォルダ内のファイル ( SVG のみ ) とサブ フォルダをリスト形式で表示
- 画面上部に開いているフォルダのパスを表示
- パスの隣にあるボタンを押すと上の階層に戻れる
- リスト上のファイルとサブ フォルダはアイコンを分ける
- アイコンはリソース内の SVG ファイルを利用する
- リスト上のサブ フォルダをタップすると中に移動
- リスト上のファイルをタップすると SVG ファイル画面に遷移
リストに表示する SVG 形式のアイコンは、以下を使用する。
そのまま使うとリストには大きすぎるため、Inkscape を利用して 48×48 にサイズ調整した。
これらをプロジェクトの res/drawable に ic_file.svg、ic_folder.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 メソッドから得られる PictureDrawable を、Canvas で画像の一部として扱いたい場合は 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() ); } }
アプリを実行すると、以下のようになる。
最後に、サンプル プログラムのプロジェクトを公開しておく。
SvgViewer.zip 36.8KB
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 アプリにとって、大きなメリットになりそうだ。