心魅 - cocoromi -

半角スペース時々全角

AndroidアプリのActivityの画面の作り方 予備知識編

日記のタイトルにやたらと「の」が入っていて全くセンスがないですが、ヌルく見守ってください。

Androidアプリ開発で基本的な画面の作り方を(眠いので)何回かに分けてメモっていきます。
大体以下のような内容を予定しています。
Activityって言われてさっぱりわからない人向けではないと思います。
サンプルとか、本やドキュメントを少し読んだけど、いまいちレイアウトが上手く作れねーんだけどとか、
上手くいくことは行くんだけど、いまいち仕組みが良く解らん。
っていう人の助けになればいいなぁぐらいの感覚で書いてます。

  1. 予備知識
  2. レイアウトの種類
  3. パーツの種類
  4. Eclipse上での開発
  5. 思い通りの画面にするためのワンポイント

予備知識編

という感じで初めは、予備知識的なところを扱って(お茶を濁して)いこうと思います。

具体的な作業の前に、初めて画面を作る上で把握しておいた方がいいかなと思う点として、以下の4つを挙げてみます。

  1. レイアウトの設定はXMLで行う
  2. Activityとレイアウトのつなぎ込みは、Activityのコード上で行う
  3. XMLファイルの名前から.xmlを覗いた部分がコード上での名前になる
  4. 画面はLayoutとViewで作る

サンプルコードに関して

以下で出てくるサンプルコードはAndroid_sdkに含まれている、NotePadプログラムの物を引用しています。
android_sdk/samples/android-7/NotePadにあります。
このサンプルの中身はandroid-8でも同じだと思います。

レイアウトの設定はXMLで行う

レイアウトの作り込み自体はJavaソースコードベースでももちろん可能ですが、ロジックに影響がなければXMLで作ってしまったほうが楽でしょう。


SDKのNotePadのサンプルでは以下のようなXMLres/layout/title_editor.xmlというファイル名で配置されています。

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
  
          http://www.apache.org/licenses/LICENSE-2.0
  
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" 
   android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingLeft="6dip"
    android:paddingRight="6dip"
    android:paddingBottom="3dip">

<edittext android:id="@+id/title" 
        android:maxLines="1" 
        android:layout_marginTop="2dip"
        android:layout_width="wrap_content"
       android:ems="25"
        android:layout_height="wrap_content" 
        android:autoText="true"
        android:capitalize="sentences"
        android:scrollHorizontally="true" />

<button android:id="@+id/ok"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_gravity="right"
        android:text="@string/button_ok" />

</LinearLayout>


Eclipseを使ったりするとGUIでこのXMLを生成することもできます。


中身はとりあえずだらっと眺めてもらって、「やたらとandroidとか出てくるな」とか「@+idとか@stringあたりがポイントかな」とか思ってもらえばいいかなと思います。
また、置き場所がres/layoutディレクトリというのは後述する、コードとのつなぎ込みの上で大変重要なことになります。

Activityとレイアウトのつなぎ込みは、Activityのコード上で行う

で、肝心のつなぎ込みですが、こちらはJavaで記述します。title_editor.xmlを呼び出しているJavaのコードをさがすと、com/example/android/notepad/TitleEditor.javaというファイルの中に、その部分が見つかります。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.title_editor);

        // Get the uri of the note whose title we want to edit
        mUri = getIntent().getData();

        // Get a cursor to access the note
        mCursor = managedQuery(mUri, PROJECTION, null, null, null);

        // Set up click handlers for the text field and button
        mText = (EditText) this.findViewById(R.id.title);
        mText.setOnClickListener(this);
        
        Button b = (Button) findViewById(R.id.ok);
        b.setOnClickListener(this);
    }


onCreateはActivityが生成されるときに呼び出されるAPIで、その最初の方にあるsetContentView(R.layout.title_editor);の部分でXMLファイルで作った画面と対応させています。

XMLファイルの名前から.xmlを覗いた部分がコード上での名前になる

このR.layout.title_editorの部分を理解することはスムーズに開発するために重要になってきます。
Androidアプリ開発ではresディレクトリの下に定義・配置したものにアクセするためのオブジェクトを自動生成してくれます。このRというクラスはgenディレクトリの下に、アプリのパッケージに属するように生成され、resディレクトリの中身を良きに理解してクラスを定義します。res/layoutディレクトリはRの内部クラスlayoutとして、XMLファイルの名前はlayoutクラスのプロパティとして定義されます。
このような処理を(antで)コンパイル前に自動でやってくれます。
Eclipseの場合も同様にres/layoutの下にあるXMLの名前で自動生成されます。


つまりR.layout.title_editorのtitle_editorはres/layout/title_editor.xmlというファイル名から自動生成されたものなのです。


この様にXMLのファイル名がJavaのコード上でアクセスする際の名前となるため、XMLのファイル名変更をする場合、ソースコードも変更する必要があります。
そのためXMLファイルの名前はあとのことも考えて変更しなくてすむような名前を付ける必要があります。
どうしてもXMLのファイル名を変更する場合は慎重に行う必要があるでしょう。


画面はLayoutとViewで作る

最後にXMLファイルの中身に少し触れて、次につないでいきたいと思います。


上述したXMLにはLinearLayout、EditText、Button要素の3つしか出てきません。
この3つは画面要素の大まかな位置を決定するLineaLayoutと要素そのものであるEditTextとButtonの二つに分ける事ができます。前者をViewGroup、後者をViewと呼びます。


Androidの画面開発ではこのViewGroupとView、加えてそれらの属性を組み合わせて進めていきます。
そのため、思い通りの画面にするためにはViewGroup・Viewの種類さらに、それぞれで利用出来る属性への習熟が重要になってきます。


まとめ

  • レイアウトはres/layoutの下にXMLファイルで定義する
  • 自動生成されたR.layoutのプロパティを使ってActivityにレイアウトを適用する
  • ViewGroupとViewと属性は暗記したい

次回はあるのか!!!!?

ヘッドホンが抜かれたことを検知する

Intentが飛んでくるので受信しましょう。


AudioManager.ACTION_AUDIO_BECOMING_NOISY

AudioManager.ACTION_AUDIO_BECOMING_NOISY


サンプル

	private static IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);    
	private static BroadcastReceiver onBecomingNoisy = new BroadcastReceiver() {
		
		@Override
		public void onReceive(Context context, Intent intent) {
			//ヘッドホンが抜かれた時の処理
			
		}
	};
     

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		registerReceiver( onBecomingNoisy , filter );
	}

	@Override
	protected void onDestroy(){
		super.onDestroy();
		unregisterReceiver( onBecomingNoisy );
	}

まとめ

ヘッドフォンが抜かれてしまい、スピーカから音がなろうとするとき、AudioManager.ACTION_AUDIO_BECOMING_NOISYがIntentで飛んできます。
受信すれば、そのときに何らかの処理をすることができます。

自作ステッカーをゲットする方法

※tableタグのデザインが死んでるのでなんとかしてます。

あー自分でオリジナルのステッカーはりてぇなー って先週の日曜に思いついたんですよ。

手段

オリジナルステッカーを作る手段は大きく分けて3つです。

  • 業者に頼む
  • 自分で印刷+自分でカッティング
  • カッティングマシンで印刷


それぞれをざくっと比較

手段        質          手間 コスト    生産力 備考
業者に頼む     良          楽  高い     高   業者によっては信用ならないかもしれないのでよく調査すること
自分でカッティング カッタースキルに依存 辛い シール用紙 (A4 5枚入りで903円 thx @mirakui )      低   直線的なアウトラインなら現実的か
カッティングマシン 良          楽  シール用紙(初回はカッティングマシン代) 高   カッティングマシンは場所をとるし、安くて2〜3万だし、刃のメンテナンスや、インク代もかかってくる


たぶん1枚つくるだけなら、シール用紙を自分でカッティングするのがいい。
複雑なアウトラインのステッカーを複数枚つくるなら、業者かマシンを使った方がいい。


それぞれの詳細を少し解説

業者にたのむ

オリジナルステッカーを作ってくれる業者がいるのでそれに依頼する。
メリットは手間がかからないことと、


たぶん


失敗しないこと。


いやもちろん業者しだいなんだけど。
オリジナル ステッカーとかで検索するとADが出てくるのでその辺を参考にすればいいんじゃないでしょうか。


注意すべき点は入稿データで、慣れていないと、いわゆるRGB-YMCK問題がつきまといます。
RGBで作ったデータはマジで同じ色になりません。あとはDPIとか。
この辺の単語を聞いてリスクがイメージ出来ない人は、依頼する場合慎重に進めましょう。


ちなみに俺はCitto+に依頼したことがあります。
ここはデータを入稿すると、サンプルイメージをメールで送り返してきてくれるので、色が変わっている場合はここでわかります。
俺も案の定、色が変わっていて慌てて修正して入稿しなおしました。


値段はステッカーの面積や枚数、素材によって変わります。
多くの場合たくさん作った方が単価は安くなります。
需要と供給と予算と相談してください。


自分でカッティング

インクジェットプリンタで印刷出来るシール用紙がヨドバシとかで普通に売っているので、それに印刷。
印刷されたものを自分でカットする。
シール用紙のカッティングはペーパクラフトのように単なる紙を切るのに比べて若干クセがあるので、初めてのときは失敗する前提で作業すること。


自分でカッティングするカッティングシートを使う方法もある。
カッティングシートで検索すればどんなものかお分かりいただけると思う。
出来上がりは切り絵のようなシルエットのものになり、基本単色(概ね白黒)。すっごい努力すればコレぐらい行けるみたい。

これはこれで味があっていいですよね。

カッティングマシン

やっぱりせっかく作るならアウトラインが複雑なものを作りたくなるもので、しかも仕上がりは綺麗な方がいいに決まっている。
そんなとき自宅でも選択肢として挙がってくるのがカッティングマシンを使ったステッカー作成。

家庭用だとCraft Robo択一っぽい。
Amazonだと22000円ぐらい。

クソ欲しい。

素材について

個人で作る場合も業者に頼む場合に普通のシール用紙で印刷するのに加えてラミネート加工をするかどうかを選択します。
ラミネート加工をすると印刷面の保護や剥がれ、破けの防止になり、長持ちします。


業者の場合ラミネート加工する方がもちろん高いです。
個人の場合はラミネート紙が普通に売っているのでそれをシールの上からさらに貼ります。


個人でやる場合注意して欲しいのは、ラミネート加工する専用のマシンが必要なものを購入しないように注意してください。

まとめ

手先に自信があって無限に時間があれば、自分でカッティング。
しょっちゅう自作のステッカー作るならカッティングマシン。
とりあえず1回、複数枚のステッカーを作るなら金をぶんまわして業者に依頼。


いか、俺が業者に頼んだ時のログ

サンプル

上でも少し述べましたが、先日Citto+さんにステッカー作成を依頼しました。その時のログ。

6/6(日)突然ステッカーを作りたくなり調査

自作が難しそうで業者もありかなと、思う。
いくつか業者のサイトを回ってCitto+が一番、好感度の高いサイトだったので、もし注文するならここかな?
ただし、「デザイン完成より7営業日程で納品可能です。」とのこと。ちょっと無理かな。
でも「お客様のご希望納期にできる限り対応させて頂きますので、ご要望をお聞かせください」って書いてあるから聞いてみよう。

土日は電話対応無し。
この時点ではカッティングマシンの購入が最有力候補に。

6/7(月)

油断してたら寝坊したので、午前半休を取得。
時間ができたので、Citto+に「木曜日(6/10)中に受け取りたいの」っていったら。
「できるよー」


mjd?!
即手続き開始。見積もりフォームから見積もり依頼の送信+データ入稿。
15cmx3cmを10枚注文。
午前中の内にFlashからデータを書き出して、提出。
提出データのフォーマットも明示されていてイラレかEPSかフォトショということだったので、それらに変換した。


夕方ごろ返信に出力イメージがついてくる。色が破滅していた。


会社のフリースペースのPCからリモートデスクトップで自宅のPCにイラレCS5の体験版をインストールしてYMCKへの変換作業開始。
はじめてのYMCKに悪戦苦闘する。


0時付近に修正データを再度メールで入稿。

6/8(火)

お昼ごろメールの返事がくる。
デザインの一部が物理的にカッティングが難しいところがあるため、修正していいか?との確認がくる。
修正内容がデザインに大きく影響していなかったので、デザインを確定し、作業に入ってもらう。


正直こういうのは経験がないとわからないので、頼んで正解だったなと、このとき思う。


夕方ぐらいに支払いの案内が来るので、帰宅後振込。

6/9(水)

「振込早いねありがとね」って言うメールがきた後に「送ったよ」ってくる。
仕事早い。

6/10(木)

佐川で家に到着するも、本人不在で、受け取れず。

6/11(金)

自宅からメッセで後輩に「前略 よろしくお願いします」って送ったら、「了解です」って返ってきて、無事勤怠メールが飛ぶ。
12時、5分まえに佐川到着。

15時打ち上げ。


そしてコレが完成品
f:id:umezo:20100612001644j:image

MediaStoreからある音楽ファイルの情報を取得する

コード上である音楽ファイルのタイトルやアーティストなどを取得したい時がある。


え?ない?


いやまぁあったんですよ。


はじめに

AndroidではMediaScannerというのが定期的にSDカード内のメディア情報(画像や、音楽、動画)を収集しています。


この収集されたデータにはAPIからコンテンツプロバイダとしてアクセスすることができます。


コンテンツプロバイダに関しては正直まだよくわかってない部分もあるので、他のサイトも参考にしてください。
ContentResolverで画像を読み込む | Android Techfirm Lab
anddev.org • View topic - How to extract ID3 tag from an mp3 file with android

音楽情報のコンテンツプロバイダへのアクセス方法


まずアクセするために以下の情報が必要です。

  • コンテンツプロバイダへのUri
  • 提供している情報の項目名


これらはわかりにくいのですが、android.provider.MediaStoreのあたりを見ていくとわかってきます。
MediaStoreはさらに以下のクラスを内包しています。
それぞれ、音楽、画像と動画情報にアクセするための色々をもっています。

  • MediaStore.Audio
  • MediaStore.Images
  • MediaStore.Video


今回は音楽情報を扱うのでMediaStore.Audioをみていきます。


MediaStore.Audio

このクラスもさらに内部にクラスを持っていますが、それぞれ取り扱っている情報が違います。
クラス名でなんとなくわかると思います。


今回は曲のタイトルにアクセスしたいので(消去法で)MediaStore.Audio.Mediaを使います。

データを取得してみる

まずはMediaStore.Audio.Mediaが持っている情報を全部取得してみます。

        ContentResolver resolver = c.getContentResolver();
        Cursor cursor = resolver.query(
        		MediaStore.Audio.Media.EXTERNAL_CONTENT_URI ,  //データの種類
        		null ,                                                                          //取得する
項目 nullは全部
        		null , //フィルター条件 nullはフィルタリング無し 
        		null , //フィルター用のパラメータ
        		null   //並べ替え
        );
        Log.d( "TEST" , Arrays.toString( cursor.getColumnNames() ) );  //項目名の一覧を出力
        Log.d( "TEST" , cursor.getCount() + "" ); //取得できた件数を表示

次に取得した情報をもっと詳細に表示してみます。
詳細に表示するためには項目名を知る必要がありますが、それはMediaStore.Audio.Media | Android Developersのinherited Constantsを見てください。


android.provider.MediaStore.Audio.AudioColumnsの下にARTISTやALBUM、android.provider.MediaStore.MediaColumnsの下にTITLEがあるのでこれを取得してみましょう。


        ContentResolver resolver = c.getContentResolver();
        Cursor cursor = resolver.query(
        		MediaStore.Audio.Media.EXTERNAL_CONTENT_URI , 
        		new String[]{
        				MediaStore.Audio.Media.ALBUM ,
        				MediaStore.Audio.Media.ARTIST ,
        				MediaStore.Audio.Media.TITLE
        		},    // keys for select. null means all
        		null,
        		null,
        		null
        );
        

        while( cursor.moveToNext() ){
        	Log.d("TEST" , "====================================");
        	Log.d("TEST" , cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ALBUM ) ) ); //アルバム名の取得
        	Log.d("TEST" , cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ARTIST ) ) ); //アーティスト名の取得
        	Log.d("TEST" , cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.TITLE ) ) ); //タイトルの取得
        }


cursorの説明は省きますが、項目の指定にMediaStore.Audio.Mediaの定数を使いました。
こんな感じでAndroidのコンテンツプロバイダへのアクセスに必要なデータは種類に応じたクラスが持っているのでそれを使えばコーディングすることが出来ます。




最後に、ファイルに対応した、曲情報を取得します。さっきのinherited Constantsを眺めていくとDISPLAY_NAMEが使えそうですね。

DISPLAY_NAME
The display name of the file
Type: TEXT


検索条件はSQLのwhere句のように(っていうかまるっきりwhere句ですが)指定します。
その際"?"を埋め込んでおくと次の文字配列で置き換えてくれます。

        //f is instance of File
        ContentResolver resolver = c.getContentResolver();
        Cursor cursor = resolver.query(
        		MediaStore.Audio.Media.EXTERNAL_CONTENT_URI , 
        		new String[]{
        				MediaStore.Audio.Media.ALBUM ,
        				MediaStore.Audio.Media.ARTIST ,
        				MediaStore.Audio.Media.TITLE
        		},    // keys for select. null means all
        		MediaStore.Audio.Media.DISPLAY_NAME + "=?",
        		new String[]{
        			
        			f.getName()
        		},
        		null
        );
        

        while( cursor.moveToNext() ){
        	Log.d("TEST" , "====================================");
        	Log.d("TEST" , cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ALBUM ) ) ); //アルバム名の取得
        	Log.d("TEST" , cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.ARTIST ) ) ); //アーティスト名の取得
        	Log.d("TEST" , cursor.getString( cursor.getColumnIndex( MediaStore.Audio.Media.TITLE ) ) ); //タイトルの取得
        }

まとめ

Android内の画像、音楽や動画のメタ情報などにアクセスしたくなったら、MediaStore以下のクラスをながめつつContentResolverのqueryメソッドの引数を決める。

ぶっちゃけめんどくさい。

ContentResolver.queryメソッドの第3に?を使っておくと、第4引数で展開出来る

android.content.ContentResolver.queryメソッドの各引数は、それぞれSQLのSELECT句や、FROM句に対応している。
第3引数はWHERE句にあたるが、中に?を埋め込んでおくと第4引数で展開することが出来る。


queryの参考文献などはこちらがいいんじゃないでしょうか。
ContentResolverで画像を読み込む | Android Techfirm Lab
公式 API Document


サンプル

サンプルとしてAndroid備え付けのContentStoreからMP3のファイル名で情報を引いてくる例をあげる。


まずは単純に埋め込む例。

        Cursor cursor = resolver.query(
        		MediaStore.Audio.Media.EXTERNAL_CONTENT_URI , 
        		new String[]{
        				MediaStore.Audio.Media.DISPLAY_NAME
        		},    // keys for select. null means all
        		"_display_name='"+f.getName()+"'" , 
        		null,
        		null
        );

これでもうまく行くような気はするが、f.getNameの戻り値をエスケープしたりしなければいけないし、場合によってはシングルクォートで囲わなくても良かったりして、めんどくさい。
そこで以下のように?埋込+第4引数を使う。

        Cursor cursor = resolver.query(
        		MediaStore.Audio.Media.EXTERNAL_CONTENT_URI , 
        		new String[]{
        				MediaStore.Audio.Media.DISPLAY_NAME
        		},    // keys for select. null means all
        		"_display_name=?" , 
        		new String[]{
        				f.getName()
        		},
        		null
        );


?を埋め込む場合は引用符など気にしなくていいし、エスケープも勝手にやってくれて楽で安全。


まとめ

queryメソッドの?埋込を使って安心クエリ生成。
っていうか?使わないとインジェクションされんぞ!!!

MediaPlayerの状態遷移

音を再生するためのクラスMediaPlayerというのがあるのだが、こいつにたいして送るメッセージはMediaPlayerの状態に応じて変更する必要がある。

状態遷移図がAPIドキュメントに読むのでコーディングするときはにらめっこしながらやりましょう。
MediaPlayer | Android Developers

音を再生するためには

図を初期状態(Idle)の方から見ていくと再生するため(状態Startedに遷移するため)には、MediaPlayer(以下MP)の状態がPreparedになっている必要があることがわかる。


MPのインスタンスを生成する方法は大きく2種類あって、MediaPlayer.createを使うかnewするかが選べる。


さて、MediaPlayer.createの説明をみると以下のようなことが書いてある。
http://developer.android.com/intl/ja/reference/android/media/MediaPlayer.html#create(android.content.Context, android.net.Uri)

On success, prepare() will already have been called and must not be called again.

prepareメソッドはすでに呼んであるから、もう呼んじゃダメよって書いてある。
つまりこの方法でMPのインスタンスを取得するとすでに状態がPreparedまで遷移しているインスタンスを取得できるため、すぐにstartをコールすることができる。


一方で、newした場合はどうなるかというと初期状態のIdleのインスタンスが取得できる。
なのでここから、startをコールするためには、先にsetDataSourceとprepareを呼ぶ必要がある。

DataSourceの再指定

さて、MPのインスタンスを使い回して再生するデータを変更する場合はどうしたらいいだろうか。
また、状態遷移図を確認すると、setDataSouceをコールするためにはMPをIdle状態へと遷移する必要があることがわかる。
Idle状態へはresetをコールすることで遷移できる。


なので、例えば、再生中のMPから別の音声を再生するためには一旦resetをコールした後にsetDataSouceをコールする

IllegalStateException

状態に合わないメソッドをコールすると例外、IllegalStateExceptionが飛んでくる。
開発中にこの例外に出くわしたら、特定の操作で意図しない状態から遷移不可能な状態へと遷移しようとしているということである。


どこかに状態遷移がおかしいところがないかチェックする。


まとめ

MediaPlayerクラスは状態を持っており、コールできるメソッドはその時の状態に依存している。
また、遷移先の状態はAPI ドキュメントの状態遷移図で確認できる。