Androidでフォーム画面をつくるとき、入力部分の幅を可変にする!

Android

Androidでフォーム画面をつくるということは、多々ありますね。
そんなとき、テーブルを使って左に項目名、右に入力部分を持ってくるというのが一般的かと思われます。
こんな感じで。
variable_table.png

レイアウトする前に - ゴールとなるxmlの構造を把握してみよう -

今回のゴールは以下のようなレイアウト構造になります。

 <LinearLayout>
   <TableLayout>
     <TableRow>
       <TextView>
       <EditText>
     </TableRow>
   </TableLayout>
 </Linearlayout>

下準備編 - テーブルを画面幅いっぱいに表示する -

テーブルを画面いっぱいにするためには、まずはLinearLayoutの幅を画面幅いっぱいにしましょう。

 
 android:layout_width="match_parent"

次に、テーブルの中で一番大枠のTableLayoutの幅を常に画面幅いっぱいになるようにしましょう。
幅にfill_parentを指定するだけでLinearLayoutの幅いっぱいになります。

 <TableLayout
   android:layout_width="fill_parent"

fill_parentは、その名の通り、親の幅をfillします!つまり、自分の横幅を親要素の幅いっぱいにします。
ここでの親要素はLinearLayoutですね!

次に、テーブルの一行ごとの要素である、TableRowの幅も画面いっぱいになるようにしちゃいましょう!!

 
 <TableRow
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"

「center_vertical」というのは、TableRowの中の要素を、TableRowの縦の中心に表示させるというものです。
上下均等に綺麗に余白ができるということです。

これで下準備が完了です!

実践編 - テーブルの中身をつくりましょう -

では、下準備ができたことですので、テーブルの中身をつくりましょう。
使うのは、

 TextView -> 項目名
 EditText -> 入力部分

TextViewがテーブルの左側に表示させる項目名、EditTextが右側に表示させる入力部分。

まずは、この二つをTableRowの中に配置しましょう。

 <TableRow
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical">
 
    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="項目名1">
    </TextView>
    <EditText
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:inputType="text"
      android:layout_weight="1">
    </EditText>
 
 </TableRow>

TextViewとEditTextのwidthとheightには、wrap_contentを指定しました。
これは、Androidが自動的に幅を決めてくれる優れモノです。一概には言えませんが、画面全体に表示させたいというわけでないなら、wrap_contentを使いましょう。

ここで、EditTextだけに「layout_weight」が指定されています。
これは、親要素の余白を使う比率を表しています。
これを1に指定すれば、親要素のすべての余白を吸収して自分の横幅に使うことができます。
この場合、EditTextはTableRowの余白を吸収し、自分の横幅にしてしまいました。
EditTextとTextViewの間に格差ができたわけですね。

これで、横幅が可変になるフォームを作れます。
あとは自力でマージンやパディングを指定して、綺麗なフォームを作ってください!

おわりに・・・

ついに今回の白熱講義が終わってしまいました。
ちなみに、画面を横にした時の画像はこれです。
device-2013-09-10-120207.png
ちゃんと入力部分の横幅が変わっていますね。

Androidで撮影したビデオがWebサイトで見られない

Android
Android ビデオ録画機能を作る

Androidで撮影したビデオがWebサイトで見られない

かなりマニアックなシチュエーションだと思いますが、今、次のようなことにトライしています。なお、ここまでの歩みはAndroid ビデオ録画機能を作るにもまとめてあります。

今回は、スマホで動画を撮影に加えて、

  1. Androidアプリで動画を撮影
  2. Webサーバーにアップロードして、Webでもその動画が楽しめる!

というものなんですが、Androidアプリでちょこっとパラメーターをいじるとブラウザで見られないという事象が発生していました。
なので、調べたことを書いておきます。

検証したスマホはXperia SO-04Dです。PCはWin7と、下記で紹介するブラウザたちです。

①まず、最初は下記のようにやっていました。
MediaRecorderを作るときに、getCamcoderProfileというメソッドのほうで、なるべく低い画質のビデオを作るべく、CamcorderProfile.QUALITY_LOWを指定しています。

 if(mrec == null){
      mrec = new MediaRecorder(); 
      mrec.setOnInfoListener(this);
      mrec.setOnErrorListener(this);       
 }
         
 mrec.setCamera(mCamera);
 
 mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
        
 mrec.setVideoSource(MediaRecorder.VideoSource.CAMERA);
 
 int displayRotation = getOrientation(context, mrec);
 mrec.setOrientationHint(displayRotation);
 
 //ビデオの画質
 CamcorderProfile camcorderProfile = getCamcoderProfile(context, mrec);
 mrec.setProfile(camcorderProfile);
       
 mrec.setVideoSize(camcorderProfile.videoFrameWidth, camcorderProfile.videoFrameHeight);        
 
 mrec.setOutputFile(file_path);
        
 mrec.setPreviewDisplay(surfaceHolder.getSurface());
 private static CamcorderProfile getCamcoderProfile(Context context, MediaRecorder mrec){
       // 録画される画面の縦横を決める
       int degrees = getSurfaceDegrees(context);
 
       Camera.CameraInfo camInfo = new Camera.CameraInfo();
       int camera_id = findFrontFacingCameraID();
       Camera.getCameraInfo(camera_id, camInfo);
 
       //ビデオの画質
       CamcorderProfile camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_LOW);
       
       return camcorderProfile;
   }
   

しかし、上記でアップロードされたビデオファイルを見ると、

Firefox 38.0.5 「サポートされたファイル形式およびMIME形式のファイルが見つかりませんでした」
IE11 「無効なソース」
Chrome 再生のコントロールパネルが表示されるけれども、再生されない状態

となります。下記は悲しいIE11の画面です。
ie11.png

ちなみに、ブラウザ側のHTML5のビデオプレーヤーは下記のように指定しています。

 <video controls>
   <source src="hogehoge.mp4" type="video/webm">
   <source src="hogehoge.mp4" type="video/mp4">
    I'm sorry; your browser doesn't support HTML5 video in WebM with VP8 or MP4 with H.264.
 </video>

下記のサイトなどを調べて、MP4だったら再生されるはず? と思っていたため、最初かなり時間がかかりました。
https://developer.mozilla.org/ja/docs/Web/HTML/Supported_media_formats

たとえば、撮影した動画ではなくって、別のMP4なら再生されるため、もしかしてビデオコーデックのせいかな?と考えました。
で、動画ファイルのコーデックを調べられるMediaInfoというフリーソフトをインスコして調べてみます。

MediaInfo
http://www.gigafree.net/media/mediainfo.html

mediaInfo.png

えっ H263のビデオコーデックになっています。

コーデックについて、本腰で調べるときがきたようです…。
下記のWikipediaで調べてみます。
https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%87%E3%83%83%E3%82%AF

ふうむ。


ビデオコーデックを指定しないといけないのか!と思い、前にも散々、Androidでビデオ録画機能を作る時に気をつけたいことで公式ドキュメント読みなさーいっ って言ってたんですが、またまた公式ドキュメントを読んでいたにもかかわらず、英語力があまりなくって誤解していたみたいです[sad]

下記のページにある
http://developer.android.com/guide/topics/media/camera.html

次の記述ですね。

>Set the video output format and encoding. For Android 2.2 (API Level 8) and higher, use the MediaRecorder.setProfile method, and get a profile instance using CamcorderProfile.get(). For versions of Android prior to 2.2, you must set the video output format and encoding parameters:
>
> setOutputFormat() – Set the output format, specify the default setting or MediaRecorder.OutputFormat.MPEG_4.
> setAudioEncoder() – Set the sound encoding type, specify the default setting or MediaRecorder.AudioEncoder.AMR_NB.
> setVideoEncoder() – Set the video encoding type, specify the default setting or MediaRecorder.VideoEncoder.MPEG_4_SP.

Set the video encoding type, specify the default setting or MediaRecorder.VideoEncoder.MPEG_4_SP. というのは、指定しなければMPEG_4_SPが指定されるのかと思っていました。

そうではなく、指定しないとそのスマホのデフォルトが使われる、ということなんですね。そして、今回のようなトラブルになるわけですorz

で、MediaRecorderにsetVideoEncoderを設定するぞ!と下記のように変更してみると

 //中略
 int displayRotation = getOrientation(context, mrec);
 mrec.setOrientationHint(displayRotation);
 
 //下記を追加
 mrec.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
 mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
 mrec.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP);
        
 //ビデオの画質
 CamcorderProfile camcorderProfile = getCamcoderProfile(context, mrec);
 mrec.setProfile(camcorderProfile);
 
 //後は略

で、上記を実行すると

 setOutputFormat called in an invalid state: 4

というエラーが出て、録画を開始できません。

あわわー 

 mrec.setProfile(camcorderProfile);

とやっているので、CamcorderProfileの中で設定しないといけなかったんですね。

上記で追加したところを削除し、CamcoderProfileのフィールドのvideoCodecを
MediaRecorder.VideoEncoder.H264で指定します。

   private static CamcorderProfile getCamcoderProfile(Context context, MediaRecorder mrec){
       // 録画される画面の縦横を決める
       int degrees = getSurfaceDegrees(context);
 
       Camera.CameraInfo camInfo = new Camera.CameraInfo();
       int camera_id = findFrontFacingCameraID();
       Camera.getCameraInfo(camera_id, camInfo);
 
       //ビデオの画質
       CamcorderProfile camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_LOW);
       
       //次を追加
       camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;
       
       return camcorderProfile;
   }
   

上記を試してみると、下記のような感じになりました。

  • Firefox 38.0.5 「サポートされたファイル形式およびMIME形式のファイルが見つかりませんでした」
  • IE11 「無効なソース」
  • Chrome 再生できた!

おおーっ Chromeだけではとりあえず、再生できたようです!!!
しかし、これはゴールではありません。。。IE11で再生できるのが、最終ゴールなのです・・・。


これからは、あまり明確な解決方法がないんですが、下記のサイトで、Win7+IE11では、1920×1088 pixels の動画しかサポートしない、という情報がありました・・・。マジ?

http://stackoverflow.com/questions/21124885/html5-video-not-working-in-ie-11

しかし・・・動画の大きさはもしかしたら関係あるのかもしれない・・・
80Kぐらいの、とても小さい動画なので・・・。
で、次のようにビデオのサイズを上げてみました。

 //ビデオの画質
 CamcorderProfile camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_720P);
 camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;

すると、
Firefox 38.0.5 再生できた!
IE11 再生できた!
Chrome 再生できた!!!

ついに!再生したい全部のブラウザで再生ができました。

ちなみに、上記のようにしておくと、CamcorderProfile.QUALITY_720Pがないカメラでは、ビデオが開始できません。
なので、下記のようにカムコーダーのプロファイルがあるかどうか調べて使ったほうがいいでしょう。

 CamcorderProfile camcorderProfile = null;
 if(CamcorderProfile.hasProfile(camera_id, CamcorderProfile.QUALITY_480P)){
           
           camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_480P);
       
       }else if(CamcorderProfile.hasProfile(camera_id, CamcorderProfile.QUALITY_720P)){
       
           camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_720P);
       
       }else{
       
           camcorderProfile = CamcorderProfile.get(camera_id, CamcorderProfile.QUALITY_HIGH);
       
       }
       
  camcorderProfile.videoCodec = MediaRecorder.VideoEncoder.H264;
  • エクスペリアの英語表記はXperiaですよ。 — ゆき {2015-06-17 (水) 09:55:52}
  • ゆきさん、ご指摘有難うございました!直しました。 — 書いた人 {2015-06-22 (月) 11:33:54}

AndroidのGoogle Play Serviceを利用する位置情報API

2013年にAndroidの位置情報を取得するAPIが新しくなりました。
より精度がよく、より速やかに、よりバッテリー消費が少なく!!位置情報が取得できるようになりました。

新APIはGoogle Play Serviceライブラリを利用します。
Fused location providerと呼んでいる人もいるようですが、それは新APIの位置情報プロバイダがそのように呼ばれているだけで、一部の話です。

http://developer.android.com/google/play-services/location.html

Androidのディスプレイサイズがxhdpiなのかxxhdpiなのか

Android

Androidのディスプレイサイズがxhdpiなのかxxhdpiなのか

Androidアプリを作っていて、とてもめんどくさいのが多種なスクリーンサイズに対応することですね。

「あれ、この端末で見た目がおかしいよ?」という時、まずはその端末が下記のどの画面サイズのグループに該当するのか確かめるのが先です。

mdpi:160dpi
hdpi:240dpi
xhdpi:320dpi (Galaxy S3a)
xxhdpi:480dpi (HTC J butterfly)
xxxhdpi : 640dpi

というわけで、上記の画面密度グループを表示するサンプルコードです。

 WindowManager windowManager = getWindowManager();
 Display display = windowManager.getDefaultDisplay();
 DisplayMetrics displayMetrics = new DisplayMetrics();
 display.getMetrics(displayMetrics);
 Log.d(TAG, "ディスプレイの密度グループ"+displayMetrics.densityDpi);

HTC J butterflyって乃木坂46がCMしているんですね!(・∀・)

Androidアプリからnode.jsに接続するとFile Not Found Exception

Java
Android
node.js
Express

ちょっと解決まで時間がかかったので書いておきます。
Expressは最近触り始めたのであまり知識がありません。

Androidアプリから、node.js + expressのサーバーに通信して、結果を取得するAPIを作成していました。
が、AndroidアプリでURLに接続すると

 File Not Found Exception 

で接続できない。
ブラウザでは普通に接続できるんですけど。
Javaではあるある~ ですよね。
サーバーからは404が帰ってきます。

で、最初はexpressの方かな?と思いまして、下記のようにわざわざ

 res.status(200)

をつけてみました。

 router.get('/route_out', function(req, res, next) {
 	console.log("route_out");
 
  res.status(200).send('broadcasted');
 
 });

ですが、

 File Not Found Exception 

は依然としてそのまま…。

node.jsを走らせていると、コンソールに次のような表示が…。

 POST /broadcast/route_out 404 35.966 ms - 947

あれっ アプリからはGETではなくて、POSTで接続してるじゃん!
とアプリのコードをよく読むと

 @Override
 protected String doInBackground(String... params) {
 
 	String string = null;
 	HttpURLConnection urlConnection = null;
 

try {

 			
 		URL url = new URL(params[0]);
 		   
 		urlConnection = (HttpURLConnection) url.openConnection();
 	    	urlConnection.setDoOutput(true);
 	    	urlConnection.setChunkedStreamingMode(0);
 
 	    	InputStream in;
 		    
       		int status = urlConnection.getResponseCode();
 
                if (status != HttpURLConnection.HTTP_OK){
 		            
 	            	in = urlConnection.getErrorStream();
 	            	Log.e(TAG, "サーバーからのレスポンスエラー " + status);
 	            
 	            }else{
 		 
 	            	in = new BufferedInputStream(urlConnection.getInputStream());
        		string =convertInputStreamToString(in);
 		    	Log.d(TAG, string);
 		    		    
 	            }
                
 		} catch (IOException e) {
 			// TODO Auto-generated catch block
 			e.printStackTrace();
 		}finally {
 		    urlConnection.disconnect();
 		}
 
 		return string;
 }

となっていて、

 urlConnection.setDoOutput(true);
 urlConnection.setChunkedStreamingMode(0);

この二つがいらなかったですね(;^ω^)

この二つを消して、下記を加えたら解決しました!

 urlConnection.setRequestMethod("GET");