2012年5月24日木曜日

Android Dialog の大きさを自分で設定する

毎回、過去に実装したアプリのコードを見るのがめんどいのでメモしておきます。

例えば、入力欄のあるオリジナルのダイアログを作るとします。

public void showOriginalDialog(Context context) { LinearLayout ll = new LinearLayout(context); ll.setOrientation(LinearLayout.VERTICAL); TextView tv = new TextView(context); tv.setText("お名前を入力してください。"); EditText et = new EditText(context); ll.addView(tv); ll.addView(et); Dialog dialog = new Dialog(context); dialog.setContentView(ll); dialog.setTitle("アカウント作成"); dialog.show(); } この場合、次のようにダイアログの幅は TextView の幅、つまり「お名前を入力してください。」の幅になります。



3.0 以降は Theme.Holo.Dialog がデフォルトのテーマになり、一定の幅のダイアログになります。また、Theme.Holo.Dialog.MinWidth というテーマだとそれよりもさらに少し幅が広いダイアログになります。

しかし 3.0 以前では、デフォルトでは上記のようにダイアログの幅はコンテンツがちょうど収まる幅になってしまいます。

ダイアログの幅を自分で設定し、例えば画面の幅の8割にするには、次のように WindowManager.LayoutParams で指定します。 public void showOriginalDialog(Context context) { DisplayMetrics metrics = getResources().getDisplayMetrics(); int dialogWidth = (int) (metrics.widthPixels * 0.8); ... Dialog dialog = new Dialog(context); dialog.setContentView(ll); dialog.setTitle("アカウント作成"); WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); lp.width = dialogWidth; dialog.getWindow().setAttributes(lp); dialog.show(); }



また、ダイアログが表示される位置も変えることができます。デフォルトではセンターですが、ダイアログを下部に表示したい場合は dialog.getWindow().setGravity(Gravity.BOTTOM); のように、ダイアログの Window に setGravity() で位置を指定します。






2012年5月23日水曜日

Android Support Package の Fragment から startActivityForResult() を使うときの注意点

今回は Support Package で Fragment を使う場合の注意点です。

まず、FragmentActivity で startActivityForResult() を使う場合、requestCode は 16bit 以下にしなければなりません。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#654 654 /** 655 * Modifies the standard behavior to allow results to be delivered to fragments. 656 * This imposes a restriction that requestCode be <= 0xffff. 657 */ 658 @Override 659 public void startActivityForResult(Intent intent, int requestCode) { 660 if (requestCode != -1 && (requestCode&0xffff0000) != 0) { 661 throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); 662 } 663 super.startActivityForResult(intent, requestCode); 664 } このように、16bit 以上の場合は IllegalArgumentException が発行されるようになっています。

なぜこのような処理をしているかというと、requestCode を使って Activity から呼ばれた場合と、Fragment から呼ばれた場合を区別するようにしているからです。

requestCode が 0x0000ffff より小さい場合 → Activity から呼ばれた
requestCode が 0x0000ffff より大きい場合 → Fragment から呼ばれた

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#666 666 /** 667 * Called by Fragment.startActivityForResult() to implement its behavior. 668 */ 669 public void startActivityFromFragment(Fragment fragment, Intent intent, 670 int requestCode) { 671 if (requestCode == -1) { 672 super.startActivityForResult(intent, -1); 673 return; 674 } 675 if ((requestCode&0xffff0000) != 0) { 676 throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); 677 } 678 super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); 679 } さらに、どの Fragment から呼ばれたかも requestCode に反映されます。

ポイントは super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); ですね。

FragmentActivity では、自身が持っている Fragment にそれぞれ index が振られます。それが mIndex です。
つまり、Fragment から Fragment#startActivityForResult() を呼んだ場合、最終的に Activity#startActivityForResult() に渡される requestCode は、上位16ビットがその Fragment の index、下位16ビットが Fragment#startActivityForResult() に渡した requestCode になります。


で、どこではまるかというと、Fragment#startActivityForResult() から呼んだ Intent を FragmentActivity#onActivityResult() で受ける場合です。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#128 128 /** 129 * Dispatch incoming result to the correct fragment. 130 */ 131 @Override 132 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 133 int index = requestCode>>16; 134 if (index != 0) { 135 index--; 136 if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { 137 Log.w(TAG, "Activity result fragment index out of range: 0x" 138 + Integer.toHexString(requestCode)); 139 return; 140 } 141 Fragment frag = mFragments.mActive.get(index); 142 if (frag == null) { 143 Log.w(TAG, "Activity result no fragment exists for index: 0x" 144 + Integer.toHexString(requestCode)); 145 } 146 frag.onActivityResult(requestCode&0xffff, resultCode, data); 147 return; 148 } 149 150 super.onActivityResult(requestCode, resultCode, data); 151 } このように FragmentActivity#onActivityResult() では、requestCode の値を見て、それが Fragment から呼ばれたものなら、Fragment の onActivityResult() を呼んでいます。このときちゃんと requestCode の下位16ビットだけ渡しています。

Fragment#startActivityForResult() で呼んだ Intent を同じ Fragment の onActivityResult() で受け取っている場合には特に問題はないのですが、 Fragment#startActivityForResult() で呼んだ Intent を FragmentActivity の onActivityResult() で処理する場合には、渡された requestCode の下位16ビットだけを比較対象にする必要があります。

具体的に書くと、

public class ActivityA extends FragmentActivity { public static final int REQUEST_CODE_3 = 3; FragmentA mFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mFragment = new FragmentA(); getSupportFragmentManager().beginTransaction().add(R.id.container, mFragment).commit(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // ここでは requestCode は 3 ではないので、下位16ビットを比較 if(resultCode == RESULT_OK && (requestCode & 0xffff) == REQUEST_CODE_3) { // do something } } class FragmentA extends Fragment { ... private void openNewActivity() { Intent intent = new Intent(getActivity(), ActivityB.class); startActivityForResult(intent, ActivityA.REQUEST_CODE_3); } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); // ここでの requestCode は 3 = REQUEST_CODE_3 } } }

このように、 ActivityA の中の FragmentA から requestCode = 3 で startActivityForResult() を呼んだ場合、FragmentA の onActivityResult() で渡される requestCode は 3 ですが、ActivityA の onActivityResult() で渡される requestCode は例えば 665539 のような値になります。

なので、Fragment から startActivityForResult() で呼んだ Intent を FragmentActivity の onActivityResult() で処理する場合は、下位16ビットだけで比較しましょう。




2012年5月16日水曜日

FabLab 鎌倉に行って看板つくってきた!

昔の蔵を改良したような建物でした。すてき!





入り口横の看板



レーザーカッターで作られた作品がいっぱい飾ってありました



二階が作業コーナー。ここにも作品がいっぱい



静岡県のパズル?



工具とか



レーザーカッターを使ったときの手順です。今回は uPhyca の看板を作ってきました。

1. 画像を AI ファイルで用意する

レーザーカッターに使う画像は AI ファイル(.ai)で用意しておきます。レーザーがあたる領域はアートボードの領域になります。つまり、ベニヤ板の左上がアードボードの左上にくる感じ。

これはロゴの周りに余白をいれてなかったので失敗バージョン。



Adobe Illustrator で余白が入るようにアートボードの大きさを修正。


2. AI ファイルを CorelDRAW で開く

CorelDRAW の印刷設定先にレーザーカッターを指定できるようになっていて、このソフトからレーザーカッターにデータを送る。 1.で用意した AI ファイルを CorelDRAW で開き、印刷設定を開く。

今回は、

厚さ6mm くらいの杉(だったはず)のベニヤ板で
スピード 90%
強さ 70%

で彫刻
しました。

もっと色を濃くしたい場合は、スピードを落とすか、強さをあげる。
材質が変わると適切な設定値も変わるので、何回もトライ&エラーで設定値を見つけるそうです。今回はベニヤ板だったので説明してくれた FabLab お兄さんの言う通りの値にしました。


3. ベニヤ板をレーザーカッターにセット

レーザーカッターに蓋を開けてベニヤ板を左上にセット。蓋を開けているときはレーザーがでないようになっているとのこと。どの辺りにレーザーがあたるのか先にチェックできるようになっていて、ポインターをオンにすると赤い光がでるようになります。 この状態でスタートすると、レーザーは出ずに実際のように動くので、正しい位置にレーザーがオフセットがセットされているかみることができます。

レーザーカッターの操作パネル



位置が確認できたら、排気のための装置のスイッチをON!これを入れないとレーザーカッターの中に煙が充満して危険なので必ずいれること! では、蓋を閉めてスタート!

レーザーがでる部分が左右にものすごい速さで動いて彫刻されていくー。



できたー!



遅くなったけど、報告完了!
もう少し細かく写真とればよかったなぁ。。。