Android アプリの WebView 連携

2014年1月27日 0 開発 ,

この前 iOS アプリの UIWebView 連携という記事を書いたが、その対となるものとして Android アプリにおける WebView 連携についてまとめてみる。

もくじ

  1. はじめに
  2. ローカル HTML と assets
  3. アプリから WebView 上の JavaScript 関数を実行する
  4. WebView 上の JavaScript からアプリへコールバックする
  5. Google Maps API と連携してみる
  6. サンプル プログラム

はじめに

Android SDK は WebView という Web ブラウザ コントロールを提供している。これは iOS における UIWebView に相当し、ページ表示とアプリ連携をサポートしている。

今回の記事では、iOS の UIWebView 連携で実装したサンプルを Android に移植しながら、WebView 連携の実装手順や考察をまとめる。

ローカル HTML と assets

Android アプリ内にローカル HTML を組み込む場合、assets を利用することになる。Eclipse + ADT を利用した Android プロジェクトの場合、その直下に assets というディレクトリが用意されている。ここに配置したものはフォルダ階層を維持した状態でアプリから参照できる。

assets

assets 内の HTML を WebView へ表示する場合は WebView.loadUrlfile:///android_asset/ から始まるパスを指定すればよい。assets 直下の html という名前のフォルダ内にある basic.html というファイルが対象なら、以下のようになる。

// WebView mWebView が定義されているものとする
this.mWebView.loadUrl( "file:///android_asset/html/basic.html" );

assets にはどのようなファイルも配置可能だが、未圧縮ファイルの場合は 1 〜 2MB ほどの容量制限 ( 上限は端末依存 ) がある。ここは比較的、小さなファイルを置く場所であり大きいものは res/raw を利用したほうがよいだろう。

もしローカル HTML の構成物に大きな画像や音声ファイル、Web フォントなどが含まれる場合は、リンク指定を Web にして動的にダウンロードされるようにするなどして容量制限を回避する必要あり。ローカル HTML 全体を ZIP 圧縮して組み込み、動的に展開してもよいだろう。

assets は配置されたファイルをそのまま組み込むため、意図せぬファイルが含まれぬよう注意する。例えば Mac の .DS_Store や Windows の thumb.db、Git リポジトリの設定ファイル ( ローカル HTML だけ Git submodule で組み込んでいるとやりがち ) など。これらが組み込まれるとアプリのサイズ増大やセキュリティ的な問題 ( 開発環境の情報などが漏れる ) を引き起こす可能性がある。

組み込み対象をフィルタリングできればよいのだが Eclipse + ADT にはそういう機能が用意されていないらしく、自衛するしかないようだ。

アプリから WebView 上の JavaScript 関数を実行する

アプリから WebView の JavaScript 関数を実行してみる。まず HTML に以下の div を定義する。アプリから指定されたテキストを受け取り、この div 内に表示したい。

<div class="from-app-text"></div>

次に HTML から読み込まれた JavaScript 上で以下の関数を定義。関数がグローバルに定義されていることが重要で、名前については重複しなければ何でもよい。たとえばプロジェクト名を接頭語として付与してもよいだろう。

この関数を実行すると、引数に指定されたテキストがさきほどの div へ表示されるようにしておく。

window.webViewCallback = function( text ) {
    $( ".from-app-text" ).text( text );
};

アプリ側からこの関数を実行する処理は以下のようになる。

/**
 * WebView 上に読み込まれた JavaScript の関数を実行します。
 *
 * @param param JavaScript の関数へ指定するパラメータ。
 */
private void executeJavaScriptFunction( String param ) {
    final String script = "javascript:window.webViewCallback('%s');";
    this.mWebView.loadUrl( String.format( script, param ) );
}

Android の WebView には iOS の UIWebView - stringByEvaluatingJavaScriptFromString 相当のメソッドはない。かわりに javascript: から始まる JavaScript を記述してそれを WebView.loadUrl へ指定することでスクリプトが実行される。

WebView 上で JavaScript を有効にする場合は WebView.getSettings で取得した WebViewSettings に対し setJavaScriptEnabled を呼び出す必要があるのだけど、それを記述すると以下のコンパイラ警告がでる。

Using setJavaScriptEnabled can introduce XSS vulnerabilities into you application, review carefully.

これは JavaScript を有効にすることでアプリに XSS の脆弱性を含んでしまう可能性がありますよ、という内容である。リスクを承知したうえで警告を抑止する場合は WebViewSettings.setJavaScriptEnabled を呼び出すメソッドか、それを含むクラスに以下のアノテーションを追加する。

@SuppressLint( "SetJavaScriptEnabled" )

たとえローカル HTML であっても Web 側のリソースを参照する場合は危険と隣合わせであることは意識しておきたい。後述する Google Maps API 連携のように Web サービスを参照するならサイトが乗っ取られて API の中身がそっくり入れ替わり、アプリ側へのコールバックが悪用される危険性もある。そのためアプリ側のインターフェースは非公開としておくとか、コールバックのパラメータ妥当性を検証するといった対策をしておくのがよいだろう。

WebView 上の JavaScript からアプリへコールバックする

WebView 上の JavaScript からアプリ側へコールバックする処理を実装してみる。二通りの方法があるので両方紹介しておくが、サンプルでは location.href の方だけ採用している。

JavaScriptInterface

WebView 上からアプリ側へコールバックする場合、一般的な方法は JavascriptInterface になるだろう。これはアプリ側で実装したクラスを WebView 上の JavaScript Object として公開する機能である。例えば以下のようなクラスを定義する。

/**
 * WebView 上の JavaScript に公開するインターフェースです。
 */
class MyJavaScriptInterface {
    /**
     * アラートを表示します。
     *
     * @param message メッセージ。
     */
    @JavascriptInterface
    public void showAlert( String message ) {
        // アラート表示処理 ...
    }
}

このクラスのインスタンスを生成し、WebView.addJavascriptInterface の第一引数へ指定することで第二引数の名称をもったオブジェクトとして WebView 上の JavaScript へ公開される。

MyJavaScriptInterface obj = new MyJavaScriptInterface();
this.mWebView.addJavascriptInterface( obj, "appJsInterface" );

JavaScript 側からの操作は以下のようになる。

appJsInterface.showAlert( "message" );

パラメータの型は自動的にマーシャリングされる。String や Boolean はそのまま、Number なら Integer、Double、Float などへ対応づけられる。

実際に利用する場合、インターフェースとして公開するクラスだけで処理が完結することは考えにくい。そのため Activity を Context として参照させたり Listener 経由でコールバック機構を設けたりすることになるだろう。

型の自動解決や JasScript のオブジェクトとして操作可能なことから非常に便利な機能ではあるが、想定外のメソッド呼び出しやフィールド参照が実行される脆弱性がある。

Android 4.2 以降では、この問題への対策として JavascriptInterface アノテーションが指定された public メソッドのみ許可するための制限が追加された。とはいえ Android 4.2 未満では利用できないし WebView 連携するページを iOS と共通にする場合、コールバック部分を OS ごとに分岐せねばならないのもイマイチ。というわけで次項では JavaScriptInterface 以外の方法で連携する方法を紹介する。

location.href によるコールバック

iOS アプリの UIWebView 連携で紹介した方法を Android に移植してみる。JavaScript 側の処理は iOS と共通である。

location.href = "app-callback://test";

location.href にリクエストを指定するとその内容にしたがってブラウザはページ遷移する。今回はコールバックが目的なので、リクエストとなる URL のスキーム部分に独自の文字列を指定する。スキームの名称は RFC 3986 – Uniform Resource Identifier (URI): Generic Syntax に定義された文字の範囲であればなんでもよい。今回は app-callback としているが、アプリのプロジェクト名を含めてユニークなものとするのがよいと思う。

アプリ側でページ遷移をハンドリングするには WebViewClient.shouldOverrideUrlLoading を実装して対象となる WebView へ指定する。

/**
 * 指定された URL が WebView からのコールバックであることを調べ、対応する処理を実行します。
 *
 * @param url URL。
 *
 * @return コールバックだった場合は true。
 */
private boolean checkCallbackUrl( String url ) {
    final String CallbacScheme = "app-callback://";
    if( !url.startsWith( CallbacScheme ) ) { return false; }

    String message = url.substring( CallbacScheme.length() );
    new AlertDialog.Builder( this )
        .setTitle( R.string.text_from_webview )
        .setMessage( message )
        .setPositiveButton( "OK", null )
        .show();

    return true;
}

/**
 * WebView を初期化します。
 */
@SuppressLint( "SetJavaScriptEnabled" )
private void initWebView() {
    this.mWebView = ( WebView )this.findViewById( R.id.webViewBasicCoordination );
    this.mWebView.getSettings().setJavaScriptEnabled( true );
    this.mWebView.setWebViewClient( new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading( WebView view, String url ) {
            return checkCallbackUrl( url );
        }
    } );

    this.mWebView.loadUrl( "file:///android_asset/html/basic.html" );
}

WebViewClient.shouldOverrideUrlLoading には遷移先となる URL 文字列が指定されるため、コールバック用スキームであることを判定する。コールバック用なら独自処理する。shouldOverrideUrlLoading の戻り値は独自処理を実行してページ遷移をキャンセルするなら true、WebView 既定の処理にまかせる場合は false を返す。コールバック処理ならページ遷移は不要なので true にしておく。

ここまでの処理を利用して簡単なサンプルを実装してみた。表示しているローカル HTML は iOS のサンプルへ組み込んでいたものから一切、手を加えていない。

基本的な連携

画面上部の EditText に文字を入力してキーボードを Done で閉じると JavaScript が実行され、HTML 側のテキストが更新される。HTML 上の青い領域をタップするとアプリ側へのコールバックが実行され、JavaScript 側で定義されたテキストが AlertDialog で表示される。

Google Maps API と連携してみる

iOS 版のサンプルで Google Maps API 連携してみたが、ローカル HTML 部分はそのままにアプリ側の処理を Android に移植してみる。まず JavaScript への住所指定については iOS とほぼ変わらない。単に WebView.loadUrl でスクリプト実行するだけである。

/**
 * マップの中央位置を指定された住所の位置へ移動させます。
 *
 * @param address 住所。ジオコーディングによって検索されます。
 */
private void moveToMapCenter( String address ) {
    final String region = this.getString( R.string.google_map_rgion );
    final String script = "javascript:window.webViewCallbackSearchAddress('%s','%s');";
    this.mWebView.loadUrl( String.format( script, address, region ) );
}

次に WebView 上の JavaScript からのコールバックだが、これも大まかな処理の流れは一緒である。ページ遷移をアプリ側で検出して URL がコールバックであることを判定した後にパラメータを解析する。以下の checkCallbackUrl は WebViewClient.shouldOverrideUrlLoading から呼び出される。

/**
 * 指定された URL が WebView からのコールバックであることを調べ、対応する処理を実行します。
 *
 * @param url URL。
 *
 * @return コールバックだった場合は true。
 */
private boolean checkCallbackUrl( String url ) {
    final String CallbacScheme = "app-callback://map";
    if( !url.startsWith( CallbacScheme ) ) { return false; }

    Map< String, String > params = this.parseUrlParameters( url );
    String address   = params.get( "address" );
    String latitude  = params.get( "lat" );
    String longitude = params.get( "lng" );
    Log.d( "HostWebView", String.format( "address = %s, latitude = %s, longitude = %s", address, latitude, longitude ) );

    if( address != null ) {
        this.mAddressTextView.setText( address );
    }

    return true;
}

/**
 * URL におけるパラメータ部分を解析してディクショナリ化します。
 *
 * @param url URL。
 *
 * @return 解析結果。
 */
private Map< String, String > parseUrlParameters( String url ) {
    Map< String, String > result = new HashMap< String, String >();
    int                   index = url.indexOf( "?" );
    if( index == -1 ) { return result; }

    String[] params = url.substring( index + 1 ).split( "&" );
    for( String param : params ) {
        String[] keyValuePair = param.split( "=" );
        if( keyValuePair.length >= 2 ) {
            try {
                String value = URLDecoder.decode( keyValuePair[ 1 ], "utf-8" );
                result.put( keyValuePair[ 0 ], value );

            } catch( UnsupportedEncodingException e ) {
                e.printStackTrace();
            }
        }
    }

    return result;
}

Android 上でもバッチリ動作している。このスクリーンショットではハチ公で検索して渋谷駅近辺が表示されている。ところでハチ公は Google Maps API 的にも渋谷なのだろうか?

Google Maps API 連携

サンプル プログラム

今回の内容を実装したサンプル プログラムのプロジェクト一式を GitHub にて公開した。開発は Eclipse Kepler Service Release 1 + ADT 22.3、動作確認には Xperia NX ( SO-02D ) を使用。ライセンスは The MIT License (MIT)。fork、改変などはご自由にどうぞ

Android アプリで Font Awesome を利用する

2013年8月4日 1 開発 , ,

すこし前に iOS アプリ上で Font Awesome を利用する方法について調査してみたので、今回は Android アプリで試してみる。

Android アプリにおける組み込みフォント

Android の TextView などは Typeface を指定するとフォントを変更できる。Typeface インスタンスはフォントの参照方法ごとに用意された create 系メソッドから生成する。

組み込みフォントの場合、createFromAssetcreateFromFile を利用する。前者が Android プロジェクトの assets、後者はファイルからフォントを参照する。以降にこれらの詳細をまとめる。

assets

Android プロジェクトには assets という仕組みがある。開発に Eclipse & ADT を使用しているならプロジェクト直下、Android Studio の場合は src/main に assets というフォルダを作成することで、その配下を特殊なリソースとして参照できるようになる。

というわけで、実際にプロジェクトへ際に読み込んでみよう。手順は以下のようになる。

  1. Android プロジェクト内の assets フォルダを開く
  2. assets フォルダ内へ Font Awesome に含まれる fontawesome-webfont.ttf をコピー

次に、assets からフォントを読み込むための処理を実装する。汎用なので、ユーティリティ的なクラスに static メソッドとして定義するとよいだろう。

/**
 * フォント関連のユーティリティです。
 */
public class FontUtility {
    /**
     * フォントを assets から読み込みます。
     *
     * @param context コンテキスト。
     * @param path    フォント ファイルを示す assets フォルダからの相対パス。
     *
     * @return 成功時は Typeface インスタンス。それ以外は null。
     */
    public static Typeface getTypefaceFromAssets( Context context, String path ) {
        return Typeface.createFromAsset( context.getAssets(), path );
    }
}

Activity クラスのメソッド上で、さきほど組み込んだ assets/fonts から読み込む場合の処理は以下のようになる。

// フォント読み込み
Typeface typeface = FontUtility.getTypefaceFromAssets( this, "fontawesome-webfont.ttf" );

// コントロールの表示フォント指定
TextView textView = ( TextView )this.findViewById( R.id.textView );
textView.setTypeface( typeface );

なお assets におけるサイズには上限がある。諸説あるようだがおおむね UNCOMPRESS_DATA_MAX という値が 1MB ~ 2MB ほどで定義されており、上限を超えると assets からの読み込みがエラーになる。

Font Awesome の場合、どのタイプのフォントも 1MB 未満なので制限はクリアしている。たとえばバージョン 3.2.1 における TTF ファイルは 78KB である。とはいえ assets の上限サイズを考えるとかなり大きい。

また assets には他のファイルを置くことも多い。特に階層を維持するという特性からローカル HTML に利用する事も考えられる。その場合、サイズだけでなくファイル構成の面でも管理しにくくなるかもしれない。

よって、なるべくなら別の場所へ組み込みたい。次項では、そのための方法を試す。

res/raw を利用したフォントの読み込み

Android のリソースには raw というカテゴリがある。これは標準でサポートされていないファイルをリソースとして扱いたい場合に利用する。まずは res/raw にフォントを組み込む。手順は以下のようになる。

  1. Android プロジェクト内の res 直下に raw というサブフォルダを作成
  2. raw 内へ Font Awesome に含まれる fontawesome-webfont.ttf をコピー
  3. fontawesome-webfont.ttf のファイル名を fontawesome.ttf に変更する

res/raw に配置されたファイルは id が割り当てられるのだが、名前にハイフンを利用できないので手順 3 が必要となる。リソースとして組み込まれたら Resources.openRawResource に id を指定することで、ファイルを InputStream として取得できる。

Typeface の create 系メソッドにはストリームを受け取るものはないけれど、ファイル化されたものなら読み込める。この仕組みを利用して res/raw から Typeface を得る方法が Stack Overflow で紹介されている。

この処理を前項で作成したクラスに追加してみる。

/**
 * フォントを res/raw から読み込みます。
 * 実装は Stack Overflow に投稿された以下の記事を参考にしています。
 *
 * Font in Android Library - Stack Overflow
 * http://stackoverflow.com/questions/7610355/font-in-android-library
 *
 * @param context  コンテキスト。
 * @param fileName リソース識別子。R.raw 以下に定義されたものを指定します。
 *
 * @return 成功時は Typeface インスタンス。それ以外は null。
 */
public static Typeface getTypefaceFromRaw( Context context, int resourceId ) {
    InputStream          inputStream  = null;
    BufferedOutputStream outputStream = null;
    Typeface             typeface     = null;
    try {
        // res/raw のフォントをメモリに読み込む
        inputStream = context.getResources().openRawResource( resourceId );

        // フォントを一時ファイルとして出力
        String fontFilePath = context.getCacheDir() + "/tmp" + System.currentTimeMillis() + ".raw";
        outputStream = new BufferedOutputStream( new FileOutputStream( fontFilePath ) );

        byte[] buffer = new byte[ inputStream.available() ];
        int    length = 0;
        while( ( length = inputStream.read( buffer ) ) > 0 ) {
            outputStream.write( buffer, 0, length );
        }

        // フォントとして読み込んだら、一時ファイルを消す
        typeface = Typeface.createFromFile( fontFilePath );
        new File( fontFilePath ).delete();

    } catch( NotFoundException e ) {
        Log.e( "UseFontAwesome", "Could not find font in resources!", e );

    } catch( IOException e ) {
        Log.e( "UseFontAwesome", "Error reading in font!" );

    } finally {
        tryClose( inputStream  );
        tryClose( outputStream );
    }

    return typeface;
}

/**
 * 破棄すべきリソースを持つオブジェクトを開放します。
 *
 * @param obj オブジェクト。
 */
private static void tryClose( Closeable obj ) {
    if( obj != null ) {
        try {
            obj.close();

        } catch( IOException e ) {
            e.printStackTrace();
        }
    }
}

res/raw から読み込んだリソースを一時ファイルとして出力し Typeface.createFromFile で参照している。Activity クラスのメソッド上でさきほど res/raw に組み込んだ fontawesome.ttf を読む処理は以下。

// フォント読み込み
Typeface typeface = FontUtility.getTypefaceFromRaw( this, R.raw.fontawesome );

// コントロールの表示フォント指定
TextView textView = ( TextView )this.findViewById( R.id.textView );
textView.setTypeface( typeface );

assets、res/raw のどちらにも共通することだがアプリで組み込みフォントを利用するときなんども読み込むのは非効率なので Typeface はキャッシュしたほうがよさそう。例えば Application 派生クラスで一度だけ読み込み以降は Context.getApplication 経由で参照するようにする、など。

アイコン一覧の表示

Font Awesome のフォントを読み込めるようになったので表示を試す。以前 iOS で作成したサンプルのように Font Awesome の持つアイコンと対応する UNICODE を一覧にしてみよう。

アイコンを TextView に表示する場合、フォントと文字の指定が必要となる。前者は前項などで解説済み。後者については UNICODE の値から String を生成することになる。

// アイコンとして表示する文字の生成
char   unicode  = 0xF016;
String iconText = String.valueOf( unicode );

// アイコン表示
TextView textView = ( TextView )this.findViewById( R.id.iconTextView );
textView.setText( iconText );

// UNICODE 表示
textView = ( TextView )this.findViewById( R.id.unicodeTextView );
textView.setText( String.format( "%04X", ( int )unicode ) );

Font Awesome の持つ UNICODE の範囲は font-awesome.css を参照のこと。欠番もあるが、現時点では F000 ~ F18B となる。実際に ListView で表示すると以下のようになる。

アイコン一覧

ばっちり。サンプルではフォントの読み込みについて assets と res/raw の両方を試せるようにしている。

さまざまなコントロール上の表示

Android のコントロールでテキスト部分を持つものは大抵 TextView から派生している。たとえば Button や CheckBox、RadioButton など。これらに Font Awesome のアイコンを適用する場合は TextView に対する処理を転用できて便利だ。

もし私が設計するなら、せいぜい View あたりからの派生に留めるだろう。とはいえテキスト表示機能の汎用性を考慮するなら、このような継承もありなのかもしれない。レイアウト系や ImageView などの例外を除き代表的なコントロールの大半がそう設計されているのも面白い。

というわけで TextView 派生の代表的なコントロールを対象にアイコンを指定してみた。対象は以下。

実際に表示してみると、以下のようになる。

アイコンと各種コントロール

TextView 派生のコントロールに Font Awesome のアイコンを指定する場合はテキスト扱いとなるため、レイアウト XML 上からサイズ指定できる。今回のサンプルでは android:textSize="20sp" を設定してみた。

サンプル プロジェクト

今回のサンプル プロジェクト一式は GitHub で公開している。

開発は Eclipse 4.3 + ADT、動作確認には Xperia NX ( Android 4.0.4 ) と Android 4.3 シミュレーターを使用。ライセンスは The MIT License (MIT)。fork、改変などはご自由にどうぞ。

スクリーンショットの撮り方いろいろ

2012年2月19日 0 ソフト紹介 , , , ,

よく使うプラットフォームごとのスクリーンショット撮影や画像の加工方法について。

もくじ

Windows 編

キーボード ショートカット

Windows でスクリーンショットを撮影するための基本的な方法。

一般的な Windows 用のキーボードならスクリーンショット撮影用に Print Screen ( PrtScr や PrtSc などと表記されたりもする ) キーが用意されている。操作するキーは以下のようになる。

Key 処理結果
Print Screen 画面全体のスクリーンショットを撮影し、クリップボードに画像データとしてコピーする。
Alt + Print Screen アクティブなウィンドウ ( 枠も含む ) のスクリーンショットを撮影し、クリップボードに画像データとしてコピーする。

ノート PC だとキーボード面積を節約するため Print Screen が他のキーとの組み合わせになっているたり、FnShift キーも一緒に押す必要があったりする。例えば Fn + PrtSc といった感じ。

この方法で撮影した場合、Windows Vista 以降で Aero を有効にしているとウィンドウ枠の背面も映り込まれるため注意する。

PriScVista

オススメのスクリーンショット撮影ツール。

起動するとタスクバーの通知領域にアイコンが表示される。撮影はアイコンを右クリックすると表示されるメニューかキーボード操作で実行。

PriScVista

Aero が有効なときにウィンドウ単位で撮影すると、透過情報や影を含んだ状態の画像を保存できる。つまり背面の映り込みを気にしなくてよい。透過が不要なら任意の色を背面に指定して撮影することも可能。

私はウィンドウ単位で撮影して透過 + 影つきの PNG ファイルにすることが多い。ファイル名へ任意の書式で連番もつけられるのも地味に便利。

たまに撮影できないウィンドウがある。一部のインストーラ系やウィンドウ枠に特殊な効果をもたせているもの、おそらく非クライアント領域をクリップしているウィンドウなどは撮影しても画像を保存できないようだ。

機能的に申し分なく使い方も簡単。インストーラ不要で環境を汚さない。アプリが表示される場所は通知領域なのでスクリーンショットを撮るとき意識せずにすむ、といったあたりが気に入っている。あとアイコンも好み。

Paint.NET

愛用しているペイント系グラフィック ソフト。透過とレイヤを扱えるので、スクリーンショットの加工に役立つ。

Paint.NET

スクリーンショットの一部を切り抜きたい場合に、その範囲を選択してから、メインメニューの「イメージ」→「選択範囲に合わせてトリミング」を実行することが多い。

クリップボードに画像データがコピーされているとき、それよりもキャンバス サイズを小さくしてから張り付けると、サイズを画像にあわせて拡張するか、維持するのか選べる。画像を貼り付けた後に移動できるので、サイズ維持と組み合わせると、切り抜き機能として便利。

画像に注釈を入れたい場合は、テキストや図形機能を利用する。レイヤ分けしておけば、元の画像に影響を与えずに試行錯誤できる。仕上げに影付けをするのもよいだろう。手前味噌だが、影や縁取りを付けるプラグインについて紹介した、このブログの過去記事を貼っておく。

Mac編

キーボード ショートカット

Mac 用のキーボードには Print Screen キーはなく、代わりに以下のキーを組み合わせたショートカットでスクリーンショットを撮る。

Key 処理結果
⌘ + Shift + 3 画面全体のスクリーンショットを撮影し、デスクトップに画像ファイルとして保存する。
⌘ + Shift + 4 指定範囲を撮影し、デスクトップに画像ファイルとして保存する。
⌘ + Shift + 4 + Space ウィンドウやメニューバーを選択して撮影し、デスクトップに画像ファイルとして保存する。
Control + ⌘ + Shift + 3 画面全体のスクリーンショットを撮影し、クリップボードに画像データとしてコピーする。
Control + ⌘ + Shift + 4 指定範囲を撮影し、クリップボードに画像データとしてコピーする。
Control + ⌘ + Shift + 4 + Space ウィンドウやメニューバーを選択して撮影し、クリップボードに画像データとしてコピーする。

Windows に比べてキーの組み合わせが多いけど慣れた。撮影した内容をそのまま画像ファイルに保存できるのは非常に便利。

ウィンドウ単位で撮影したときに透過情報と影も一緒に保存してくれるのが嬉しい。ダッシュボードのウィジェットや Skitch のような非矩形のウィンドウでもキチンと撮影できる。

キーボード操作が面倒ならアプリケーション フォルダのユーティリティに用意されている、グラブ.app を利用するとよい。こちらはメニューから選ぶだけで撮影できてタイマー機能もついている。ただし画像の保存形式は TIFF。なぜ PNG ではないのだろう?

Screenshot Plus

ウィジェット形式のスクリーンショット撮影ツール。

グラブ.app の機能を分かりやすく整理したような感じ。保存先やファイル名、画像の形式などを変更できる。

Screenshot Plus

キーボード ショートカットに慣れるまで使っていた。タイマー機能は今でもたまに利用する。単純でわかりやすい UI なのでキーボードやグラブに馴染めないならオススメ。

Skitch

撮影したスクリーンショットに注釈を入れるときは、Skitch を利用している。

Skitch App
カテゴリ: 仕事効率化

価格: 無料

かつては有償だったが、昨年の夏、Evernote によって買収されて無料化。実にありがたい。

Skitch

説明用の画像作成は大抵、ツールでこと足りる。便利すぎる。

適当に操作してもそれなりに綺麗な注釈を入れられる点がよい。専門的なグラフィック ツールを使えばもっと高度なことができるけど、一定以上のクオリティをサクッと作れるほうが重要。

テキスト機能も素晴らしい。テキストはオブジェクトとして管理されているので画像に追加した後でも自由に移動や文を変更できる。私の場合、まずはざっくり書いて余力や時間があれば微調整、というスタイルで編集している。

Android 版iPad 版もリリースされている。iPhone 版は準備中とのこと。Windows 版はどうなのだろう。あれば嬉しいけれど難しいかな?

Android 編

Android の場合、端末 ( Galaxy S など ) によってはスクリーンショット撮影機能が用意されており、Android 4.0 では標準サポートしているが、それ以外の環境では DDMS ( Dalvik Debug Monitor Server ) を利用する。これは Android SDK に含まれている。

Android 開発しない場合でもスクリーンショット作成するなら入れておくと便利。

DDMS は SDK のインストール先にある tools フォルダ内に用意されている。Windows はddms.bat、Mac なら ddms という名前のファイルを実行する。

DDMS を起動すると認識されている Android 端末 ( 実機やエミューレータ ) がリスト表示されるので対象を選択。その後、メニューから「Device」→「Screen capture」を選択すると撮影される。生成された画像はウィンドウとして表示される。

DDMS

ウィンドウに並んでいるボタンは以下のようになっている。

ボタン 内容
Refresh 画像を更新する。端末側の表示に変化があった場合、それが反映される。
Rotate 画像を回転する。方向は反時計回りになるようだ。
Save 画像を保存する。ファイル形式は PNG。
Copy 画像をクリップボードにコピーする。
Done ウィンドウを閉じる。

実機だと端末によっては ADB ドライバが必要になるかもしれない。PC に USB 接続したとき自動的にインストールしてくれる ( 初代 Xperia など ) こともあるが、そうでない場合は個別にセットアップする。

ADB ドライバは端末メーカーの Web サイトに用意していることが多い。もし DDMS が端末を認識しない場合はそちらをあたってみること。ただしインストール方法が厄介 ( 素のドライバ ファイルを参照させられたりする ) な端末もあるので注意すること。

Windows 環境で手順をしくじるとデバイス マネージャーを操作する必要があったりするので、そのあたりの知識がないと危険。

iPhone/iPad シミュレータ シミュレータ編

iPhone/iPad シミュレータには標準でスクリーンショット撮影機能 ( キーボードで ⌘ + Control + C を押す ) が付いているけれど、私は iOS-Simulator Cropper で撮影することが多い。

このツールは、iPhone の枠も含めたり画像のファイル名に連番をつけたりできて非常に便利。

iOS-Simulator Cropper

シミュレータでも十分に動作するアプリやモックを作っているときは、このツールでスクリーンショットを作成することが多い。