†この章では画像リサイズツールの開発を通して、インテントによる外部アプリケーションとの連携について解説します。このアプリケーションはインテントによりギャラリーやカメラアプリを呼び出し、そこから受け取った画像を指定サイズにリサイズしてギャラリーに保存を行います。新規Androidアプリケーションプロジェクトを以下の設定で作成します。今回作成するアプリケーションでは、縦画面時と横画面時でそれぞれのレイアウトを用意します。図3-1が縦画面、図3-2が横画面のイメージになります。図3-1図3-2黒い余白部分は他アプリケーションから受け取った画像をプレビュー表示する領域となり、その下、もしくは右に操作部分が配置されています。今回は操作部を別レイアウトとして作成し、それぞれのレイアウトで取り込んで利用することとします。レイアウトを作成する前に利用する文字列リソースを定義しておきます。リソースのstrings.xmlをダブルクリックして開き、以下の文字列リソースを追加します。name="save">保存</string>まずは操作部のレイアウトを作成します。Other...」を選択します。File」を選択して「Next」ボタンをクリックして下さい。File"でFileに「controller.xml」と入力し、「Finish」ボタンをクリックするとレイアウトリソースファイルが作成されます。作成したcontroller.xmlを開き、ボタンやテキストボックスを配置していきます。Widgetsをクリックして展開し、「Button」をドラッグ&ドロップで配置します。配置したボタンをクリックし、レイアウタ下部の「Properties」で以下のように設定を変更します。Idに「@+id/SelectImage」と入力します。Textに「@string/select_image」と入力します。heightに「wrap_content」を指定します。rightに「10dip」と入力します。widthに「fill_parent」を指定します。同様にボタンをもう一つ追加して、以下のように設定を変更します。Idに「@+id/TakePhoto」と入力します。Textに「@string/take_photo」と入力します。heightに「wrap_content」を指定します。rightに「10dip」と入力します。widthに「fill_parent」を指定します。続いて横サイズの入力項目を配置していきます。(Horizontal)」をドラッグ&ドロップで配置し、以下のように設定を変更します。Idに「@+id/WLayout」と入力します。heightに「wrap_content」を指定します。rightに「10dip」と入力します。widthに「fill_parent」を指定します。TextViewを上記レイアウトに追加し、以下のように設定を変更します。Idに「@+id/WLabel」と入力します。Textに「@string/label_w」と入力します。heightに「wrap_content」を指定します。widthに「wrap_content」を指定します。EditTextを上記レイアウトに追加し、以下のように設定を変更します。Idに「@+id/WSize」と入力します。typeに「number」を指定します。heightに「wrap_content」を指定します。weightに「1」と入力します。widthに「wrap_content」を指定します。TextViewを上記レイアウトに追加し、以下のように設定を変更します。Idに「@+id/WSuffix」と入力します。Textに「px」と入力します。heightに「wrap_content」を指定します。widthに「wrap_content」を指定します。縦サイズの入力項目も横サイズ同様に配置していきます。(Horizontal)」をドラッグ&ドロップで配置し、以下のように設定を変更します。Idに「@+id/HLayout」と入力します。heightに「wrap_content」を指定します。rightに「10dip」と入力します。widthに「fill_parent」を指定します。TextViewを上記レイアウトに追加し、以下のように設定を変更します。Idに「@+id/HLabel」と入力します。Textに「@string/label_h」と入力します。heightに「wrap_content」を指定します。widthに「wrap_content」を指定します。EditTextを上記レイアウトに追加し、以下のように設定を変更します。Idに「@+id/HSize」と入力します。typeに「number」を指定します。heightに「wrap_content」を指定します。weightに「1」と入力します。widthに「wrap_content」を指定します。TextViewを上記レイアウトに追加し、以下のように設定を変更します。Idに「@+id/HSuffix」と入力します。Textに「px」と入力します。heightに「wrap_content」を指定します。widthに「wrap_content」を指定します。Widgetsをクリックして展開し、「CheckBox」をドラッグ&ドロップで配置します。配置したチェックボックスの設定を以下のように変更します。Checkedに「true」を指定します。Idに「@+id/KeepRatio」と入力します。Textに「@string/keep_ratio」と入力します。heightに「wrap_content」を指定します。rightに「10dip」と入力します。widthに「fill_parent」を指定します。最後にもう一つボタンを追加します。Idに「@+id/Save」と入力します。Textに「@string/save」と入力します。heightに「wrap_content」を指定します。rightに「10dip」と入力します。widthに「fill_parent」を指定します。以上で操作部のレイアウトは完成です。完成後のアウトラインとレイアウトイメージ(図3-3)を下記に示します。LinearLayoutSelectImageTakePhotoWLayoutWLabelWSizeWSuffixHLayoutHLabelHSizeHSuffixKeepRatioSave図3-3縦画面用のレイアウトを作成していきましょう。IResizerActivity!」というテキストが設定されたTextViewを削除します。Chooser」というダイアログが表示された場合は「Clear」ボタンをクリックして下さい)。配置した画像ビューをクリックし、レイアウタ下部の「Properties」で以下のように設定を変更します。boundsに「true」を指定します。Idに「@+id/Preview」と入力します。typeに「fitCenter」を指定します。heightに「0dip」と入力します。marginに「5dip」と入力します。weightに「1」と入力します。widthに「fill_parent」を指定します。ここで先ほど作成した操作部レイアウトを取り込みます。レイアウタ下部にある「main.xml」タブをクリックしてXMLエディタを表示し、上記ImageViewの下に以下を追加します。/>これで先ほど作成した操作部のレイアウトが取り込まれ、図3-1のようなレイアウトになります(※)。Layoutタブで確認するとcontrollerリソースが無いとエラーが出ることがあります。この場合は一度アプリケーションを実行したあとにmain.xmlを閉じ、Eclipseを再起動すると正常に表示されます。同様にして横画面用のレイアウトも作成します。Folder」を選択し「layout-land」フォルダを作成します。このフォルダに配置されたレイアウトは、横画面時に優先的に利用されます。layoutフォルダにあるmain.xmlをドラッグ&ドロップでlayout-landフォルダにコピーし、これを横画面用に変更していきます。最上位にあるLinearLayoutの設定を以下のように変更します。Orientationを「horizontal」に変更します。続いてプレビュー用の画像ビュー(Preview)の設定を以下のように変更します。heightを「fill_parent」に変更します。最後に取り込んだcontrollerレイアウトの設定を以下のように変更します。heightを「fill_parent」に変更します。widthを「wrap_content」に変更します。以上で横画面用レイアウトも完成です。Layoutタブで図3-2のようになっていることを確認してください。前項で作成したレイアウトをアクティビティに反映し、ボタンやテキストボックスに処理を実装していきます。IResizerActivity.javaを開き、IResizerActivityに以下のメンバ変数を追加します。0;ImageViewとEditText、Bitmapクラスを利用可能にするためにはパッケージのimportが必要になります。(android.widget)」行をクリックすると自動的にそのクラスのパッケージimportが挿入されます。図3-4mBitmap、mOrgWとmOrgHの三つのメンバ変数は他のアプリケーションから取得した画像とそのサイズを保持するための物です。続いて、このクラスで各ボタンのクリック処理を行うためandroid.view.ViewのimportとView.OnClickListenerのimplementsを追加します。{onCreateメソッドの後にonClickメソッドと二つのprivateメソッドを追加します(リスト3-1)。onClickメソッドの処理は後ほど実装するため、空のままにしておきます。}}setupUIメソッドではまず、setContentViewでmainレイアウトをアクティビティに設定したあと、「画像を選択」「写真を撮影」「保存」ボタンをfindViewByIdで取得し、このアクティビティをOnClickListenerとして登録しています。続いて画像プレビュー用のImageView、横/縦サイズ入力用EditTextを取得して保持しておき、setPreviewメソッドを呼び出しています。setPreviewメソッドでは他のアクティビティから受け取った画像がある場合、画像プレビュー用のImageViewに設定し、オリジナル画像サイズを保持しています。その後横/縦サイズ入力用EditTextそれぞれにOnFocusChangeListenerを登録しています。ここでは「比率を維持」チェックボックスがチェックされていた場合に、リサイズ後の縦横比を可能な限り維持するための自動計算処理を行っています。onFocusChangeはそのウィジェットのフォーカス状態が変更される際に呼び出されるメソッドで、引数flagがfalseの場合はフォーカスを失ったことを示します。このタイミングで、入力された値にオリジナル画像サイズから算出された比率を掛け、四捨五入した物をもう一方のサイズとして設定しています。計算結果が0になることもありますが、この場合は1で上書きすることで最低限の画像サイズを確保しています。四捨五入で使用したBigDecimalと「比率を維持」チェックボックスを保持するためのCheckBoxを利用可能とするため、次のimport行を追加します。android.widget.CheckBox;以上の作成が完了したら、自動生成されたonCreateを以下のように変更します(リスト3-2)。setupUI();}元々のコードではここで直接setContentViewを呼び出していましたが、これをsetupUI呼び出しに変更しています。requestWindowFeatureメソッドにWindow.FEATURE_NO_TITLEを渡して呼び出すことにより、アクティビティのタイトルバーを非表示にしています。図3-5左がタイトルバーを表示、右が非表示としたものです。図3-5ここまでで一度アプリケーションを実行してみましょう。Android端末を傾けて、縦画面表示と横画面表示を切り替えてみて下さい。エミュレータで実行している場合はCtrl+F12で縦横切替を行うことが出来ます(Windowsの場合)。†ここからは「画像を選択」ボタンと「写真を撮影」ボタンの処理を実装し、インテントで外部アプリケーションを呼び出す手順を解説します。外部アプリケーション呼び出しに使用する二つの定数と、カメラからのデータ受け取りに使用するメンバ変数をIResizerActivityクラスに追加します。mUriFromCamera;続いて前項で追加したonClickメソッドを以下のように変更します(リスト3-3)。}}引数に渡されたviewからgetIdメソッドでIDを取得し、「画像を選択」ボタンであればselectImage、「写真を撮影」ボタンであればtakePhotoメソッドの呼び出しを行っています。これらのメソッドで使用する文字列リソースを追加しておきます。strings.xmlを開き以下を追加して下さい。name="app_notfound">対応アプリケーションが見つかりません。</string>リスト3-4がselectImageメソッドになります。Intent、AlertDialog、DialogInterfaceを利用するためパッケージのimportも忘れずに行なって下さい。}}tryブロックで囲まれた部分では、まずIntent.ACTION_PICKを引数にしたインテントを生成しています。コンテキストとクラスを直接指定したものと違い、"データ取得のため"といったような曖昧な指定になっていますが、こうして生成されたインテントでは対応可能なコンポーネントをAndroidが自動的に探し出してくれます。両者を区別するため、前者を「明示的なインテント」、後者を「暗黙的なインテント」と呼びます。今回のアプリケーションでは画像データを必要とするため、インテントのsetTypeメソッドに「image/*」を渡して対象を絞り込んでいます。"REQUEST_SELECT_IMAGE"を渡しています。指定したインテントに対応可能なアクティビティをAndroidが見つけられなかった場合、ActivityNotFound例外が発生します。これに対応するため、catchブロックではAlertDialog.Builderを利用して警告ダイアログを生成し表示しています。続いてtakePhotoメソッドも実装していきます(リスト3-5)。新たに使用するContentValues、MediaStoreのパケージimportも忘れずに行なって下さい。}}カメラを呼び出すインテントにはMediaStore.ACTION_IMAGE_CAPTUREを指定します。Intent.ACTION_PICKでは選択した画像をUriとして返してくれるのですが、カメラからはサムネイルのデータが拡張データとして返ってきます。実際の撮影データを同様に取り扱うため、"image/*"をMIMEタイプに設定したContentValuesをContentResolverに追加し、割り当てられたUriをインテントの拡張データMediaStore.EXTRA_OUTPUTとして追加すると、撮影した画像がこのUriに保存されるようになります。これを先程追加したメンバ変数mUriFromCameraに保持しておくことで、カメラから画像を取得する場合に参照出来るようにしています。結果がカメラから返ってきたことを判断するため、startActivityForResultには定数"REQUEST_TAKE_PHOTO"を渡しています。アプリケーションを実行して「画像を選択」「写真を撮影」ボタンの動作を確認してみましょう。対応可能なコンポーネントが複数見つかった場合、図3-6のようなダイアログが表示されますが、基本的にどちらを選んでも問題ありません。前項までで外部アプリケーションを呼び出すことが出来ました。ここからは外部アプリケーションから渡されたデータを受け取り、画面に反映するまでを実装していきます。startActivityForResultで呼び出したアプリケーションからの結果はonActivityResultメソッドで受け取ることが出来ます(リスト3-6)。}}superクラスのonActivityResultを呼び出した後、resultをチェックしています。これがRESULT_OKの場合、呼び出したアプリケーションがデータを返してくれたことを示します。reqCodeにはstartActivityResultを呼び出した際のリクエストコードが入っています。これが"REQUEST_SELECT_IMAGE"であれば「画像を選択」での呼び出しとなり、渡されたインテントからgetDataメソッドでUriを取得出来ます。"REQUEST_TAKE_PHOTO"の場合は「写真を撮影」での呼び出しになりますので、予め保持しておいたmUriFromCameraを使用しています。resultがRESULT_OKでなかった場合はContentResolverのdeleteメソッドでこのUriを削除しています。取得したUriを後ほど実装するgetImageメソッドに引き渡してビットマップ画像を取得し、setPreviewメソッドでプレビュー用ImageViewへの設定と画像サイズの取得を行います。リスト3-7がgetImageメソッドになります。InputStream、BitmapFactoryパッケージのimportも併せて行います。}}ContentResolverのopenInputStreamメソッドにUriを渡すと、そのUriにあるファイルのInputStreamが取得出来ます。これをBitmapFactoryのdecodeStreamに引渡し、Bitmapオブジェクトをデコードして返しています。指定のUriにファイルがなかった場合にopenInputStreamメソッドがFileNotFoundException例外を発行するので、catchブロックではこれを受けてnullを返します。以上でデータ受け取りの実装は完了ですが、Androidのバージョンによってはインテントの発行後アプリケーションで画面の縦横切替が起きると、元のアクティビティが結果を受け取れなくなることがあります。これは縦横切替によって一度アクティビティが破棄される(onDestroyが呼ばれる)ためで、これを避けるには縦横切替時にアクティビティの破棄ではなく、コンフィギュレーション変更が発生するようにします。IResizerのマニフェストファイル(AndroidManifest.xml)を開き、android:configChanges属性をIResizerActivityに追加します(リスト3-8)。>続いてIResizerActivity.javaにonConfigurationChangedメソッドを追加します(リスト3-9)。setupUI();}superクラスのonConfigurationChangedを呼び出したあと、setupUIでレイアウトの設定を改めて行なっています。これをしないと(アクティビティが破棄されないため)切替前のレイアウトがそのままになってしまいます。†ここからは受け取った画像をリサイズし、保存するまでの処理を実装していきます。保存に際しての仕様は以下の通りとします。フォーマットはJPEG固定とする。ファイル名は現在時刻を用いて「YYYYMMDD_HHMMSS.jpg」という形式で自動生成する。保存先はカメラフォルダに固定とし、ギャラリーに表示されるよう登録を行う。保存前に確認ダイアログを表示する。保存処理で使用する文字列リソースを追加しておきます。strings.xmlを開き、以下を追加して下さい。name="save_desc">で保存します。name="save_failed">保存に失敗しました</string>続いて確認ダイアログ用のレイアウトを作成していきます。IResizerのレイアウトリソースフォルダに以下の内容でdialog.xmlというレイアウトファイルを作成します(リスト3-10)。</LinearLayout>ダイアログのアウトラインとレイアウトイメージは以下のようになります(図3-7)。LinearLayoutFileNameLayoutFileLabelFileNameImageSizeLayoutSizeLabelImageSizeDescriptionButtonLayoutExecSaveCancel図3-7保存時のファイル名とダイアログを保持するため、IResizerActivityに以下のメンバ変数を追加します。null;IResizerActivityの「保存」ボタンに応答するため、onClickのswitchに以下のcaseを追加します。break;リスト3-11がshowConfirmDialogメソッドになります。DateFormat、TextViewのパッケージimportも追加して下さい。}}リサイズ元画像とリサイズ解像度が空でないことを確認し、現在日時から「YYYYMMDD_HHMMSS.jpg」形式のファイル名を作成、保持しておきます。ボタンのOnClickListenerをIResizerActivityに、生成したファイル名とリサイズ後の画像サイズをダイアログの各項目にセットしてダイアログを表示します。ダイアログの両ボタンに応答するため、onClickのswitchに以下のcaseを追加します。ボタンとも、ダイアログがnullでなければdismissメソッドで破棄しています。IDがExecSave、すなわち「保存」ボタンの場合には実際の保存処理を行うexecSaveを呼び出します。リスト3-12がexecSaveメソッドになります。Toastのパッケージimportも忘れずに行なって下さい。}}横/縦サイズ用EditTextからサイズを取得したら、Bitmap.createScaledBitmapに元画像とサイズを引き渡してリサイズ画像を生成します。生成した画像をMediaStore.Images.Media.insertImageメソッドに引き渡すとカメラフォルダに画像が保存され、ContentResolverによりギャラリーにも表示されるよう登録されます。このメソッドはFileNotFoundExeception例外を発行する可能性があるためtryブロックで囲み、catchブロックでは保存失敗時のメッセージをToastで表示するようにしています。これでリサイズ画像を保存することが出来るようになりました。図3-8のようなダイアログが表示されることを確認し、実際に保存してみましょう。図3-8保存に失敗する場合、保存先が外部ストレージになっている可能性があります。IResizerのマニフェストファイルを開いて、次のパーミッションを追加して下さい。前項まででインテントを利用して外部アプリケーションと連携し、データのやり取りを行うことが出来るようになりましたが、もう少し進めて、Androidの共有機能を利用して他の画像アプリケーションからIResizerへ画像を送信出来るようにしてみましょう。画像の共有先としてIResizerを選択可能にするには、アクティビティにandroid.intent.action.SENDインテントフィルターを追加する必要があります。IResizerのマニフェストファイルを開き、IResizerActivityに以下を追加して下さい。</intent-filter>これで画像アプリケーションの共有メニューにIResizerが表示されるようになります。引き続き外部からの共有を処理出来るよう、IResizerActivity.javaに変更を加えていきます。IResizerActivityのonCreateメソッドでsetupUIを呼び出した後に、以下のように追加して下さい。ギャラリーなどの共有機能を持った画像アプリを起動し、共有メニューにIResizerが表示されるのを確認してみましょう(図3-9)。