先月の話だけど、Titanium Mobile 1.6.1 Released です。
・iOS:XCode4(iOS4.3)に対応 ・iOS:Facebook認証の修正(iOS3.1.x系とSafari未対応機種) ・Android:JavaScriptコンパイルのUTF-8エンコーディング対応 ・Android:ActivityとWindowのライフサイクル問題修正 ・Android:Intent のフラグ対応
話はそれるが、Titanium の仕組みを良くわかっていなかった。 iOSにソースが書き出されているので、てっきりソースを書き出しているのかと思っていたが、 Android では Ti のパッケージファイルを読む記述しかない。
Android も iOSもJSインタプリタで動作している
超基本的な事でした。。。
英語版、日本語版などローカライズする場合は、string.xml を分けて設定すれば良い。
日本語の場合は、values-ja というディレクトリを追加して、日本語用の string.xml を追加します。
Sample
- 15sec
- 30sec
- 1min
- 2min
- 10min
- 30min
Memory
SD Card
Battery
サンプル
- 通常音
- バイブ
- サイレント
- 15秒
- 30秒
- 1分
- 2分
- 10分
- 30分
本体メモリ残量
SDカード残量
バッテリー残量
あとは、R.string でアクセスしてバインディングしてやるだけです。
ちなみに画面お越しの際に string.xml でプロパティ化して作る癖をつけた方が、 効率的だなぁと実感しました。
画面を指で横にスライドさせる方法。 ScrollView あたりを使うのかな?と調べてみると、ViewFlipper なるものがある。
layout/ に first.xml、second.xml、third.xml を作成し、main.xml に ViewFlipper として追加する。
アニメーションは、R.anim に定義されているが、スライドを表現するには実現できないのでカスタマイズが必要となる。
以下の4ファイルを res/anim/ に作成する。 ■move_in_left.xml
■move_in_right.xml
■move_out_left.xml
■move_out_right.xml
[java] import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.animation.AnimationUtils; import android.widget.ViewFlipper;
public class FlipperTestActivity extends Activity implements View.OnTouchListener { private ViewFlipper viewFlipper; private float firstTouch; private boolean isFlip = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
viewFlipper = (ViewFlipper) findViewById(R.id.flipper);
viewFlipper.setAutoStart(false);
findViewById(R.id.firstlayout).setOnTouchListener(this);
findViewById(R.id.secondlayout).setOnTouchListener(this);
findViewById(R.id.thirdlayout).setOnTouchListener(this);
}
public boolean onTouch(View v, MotionEvent event) {
int x = (int)event.getRawX();
switch(v.getId()) {
case R.id.firstlayout:
case R.id.secondlayout:
case R.id.thirdlayout:
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstTouch = event.getRawX();
return true;
case MotionEvent.ACTION_MOVE:
if(!isFlip) {
if(x - firstTouch > 50) {
isFlip = true;
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.move_in_left));
viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.move_out_right));
viewFlipper.showNext();
}
else if(firstTouch - x > 50) {
isFlip = true;
viewFlipper.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.move_in_right));
viewFlipper.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.move_out_left));
viewFlipper.showPrevious();
}
}
break;
case MotionEvent.ACTION_UP:
isFlip = false;
break;
}
}
return false;
}
} [/java] View.OnTouchListener を implements して View のタッチイベントを登録する。 onTouch でタッチの状況を判別して ViewFlipper のアニメーションと画面切り替えをする。
画面移動先の Activity を終了したときに、元画面で判別させて処理する方法。
何の難しい事もなかった、setResult() を設定してやるだけでした。
[java] private function result() { Intent intent = new Intent(); intent.putExtra("ResultValue", 1); setResult(RESULT_OK, intent); finish(); }
private function cancel() { Intent intent = new Intent(); setResult(RESULT_CANCELED, intent); finish(); } [/java] startActivity() 同様、intent と Activity の定数をセットして finish() します。
[java] @Override public void onActivityResult(int requestCode, int resultCode, Intent data){ if (resultCode == RESULT_OK ) { finish(); } else if (resultCode == RESULT_CANCELED) {
}
} [/java] finish() が実行されると onActivityResult()イベントが自動的に呼ばれます。
設定ファイル保存 では SharedPreferences を利用してデータ保存したが、今回はそれに加えて PreferenceActivity を継承して設定画面を作ってみる。
values/arrays.xml を作成して項目を追加する。
- 8:00
- 7:00
- 6:00
- 5:00
- 4:00
- 3:00
- 2:00
- 1:00
- 0:00
- 8
- 7
- 6
- 5
- 4
- 3
- 2
- 1
- 0
- 23:00
- 22:00
- 21:00
- 20:00
- 23
- 22
- 21
- 20
xml/preference.xml を作成する。 ここで、PreferenceScreen を追加する事で定型化された設定画面を作成する事ができる。 入れ子にすれば階層化もできます。 PreferenceScreen で設定項目をカテゴライズできます。
ListPreferenceの、entries、entryValues に arrays.xml で定義したパラメータを設定するだけ。 ここで疑問に思ったのが、
設定した値のパラメータがない
summary に値を表示できれば良いと思ったが、xml だけで動的に設定できないようだ。
という事で、プログラムソースに記述する必要がある。 かなり美しくないソースだが、とりあえず実装。 [java] import java.util.HashMap; import java.util.Map;
import android.content.SharedPreferences; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen;
public class SettingActivity extends PreferenceActivity {
Preference preference;
Map<String, String> scheduleStartTimes = new HashMap<String, String>();
Map<String, String> scheduleEndTimes = new HashMap<String, String>();
Preference scheduleStartPref;
Preference scheduleEndPref;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference);
PreferenceScreen screenPref = (PreferenceScreen)findPreference("preferenceScreen");
PreferenceCategory scheduleCategoryPref = (PreferenceCategory) screenPref.findPreference("schedulePref");
scheduleStartPref = (Preference) scheduleCategoryPref.findPreference("schedule_start_time_key");
String[] scheduleStartTimeValues = getResources().getStringArray(R.array.schedule_start_time_keys);
String[] scheduleStartTimeKeys = getResources().getStringArray(R.array.schedule_start_time_values);
for (int i = 0; i < scheduleStartTimeKeys.length; i++) {
scheduleStartTimes.put(scheduleStartTimeKeys[i], scheduleStartTimeValues[i]);
}
loadPreference(scheduleStartPref.getSharedPreferences(), "schedule_start_time_key");
scheduleEndPref = (Preference) scheduleCategoryPref.findPreference("schedule_end_time_key");
String[] scheduleEndTimeValues = getResources().getStringArray(R.array.schedule_end_time_keys);
String[] scheduleEndTimeKeys = getResources().getStringArray(R.array.schedule_end_time_values);
for (int i = 0; i < scheduleEndTimeKeys.length; i++) {
scheduleEndTimes.put(scheduleEndTimeKeys[i], scheduleEndTimeValues[i]);
}
loadPreference(scheduleEndPref.getSharedPreferences(), "schedule_end_time_key");
}
@Override
protected void onResume() {
super.onResume();
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
}
@Override
protected void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
}
private SharedPreferences.OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
loadPreference(sharedPreferences, key);
}
};
private void loadPreference(SharedPreferences sharedPreferences, String key) {
String index = sharedPreferences.getString(key, "");
if (key.equals("schedule_start_time_key")) {
scheduleStartPref.setSummary(scheduleStartTimes.get(index));
}
if (key.equals("schedule_end_time_key")) {
scheduleEndPref.setSummary(scheduleEndTimes.get(index));
}
}
} [/java]
onCreate されたときと設定変更後のリスナー SharedPreferences.OnSharedPreferenceChangeListener で summary に設定値を表示しています。
PreferenceActivity を継承しているので、sharedPreferences.getString() で対象の項目の設定値を取得できる。 ただここで問題なのが、entries と entryValues を別途設定していので関連付けが必要。 onCreate で 設定項目を HashMap 化してから対象のキーを判別して取得しています。 何とも力技で、もうちょっとシステマチックできそうな気がしますが。。。
Android というか Javaネタ。
ArrayList
・Iterator の戻し方 ・ArrayList の多次元化(っていうのか?)
ちょっと、いい加減なサンプルかと思うが。
[java]
ArrayList
なんて事はない。
[java]
iterator.moveToFirst();
[/java]
もしくは、
[java]
Iterator
わかってしまえば何だ!って感じだけど、ハマった。
[java]
ArrayList<ArrayList
Intentで画面遷移する際、オブジェクトを受け渡したい場合どうするのか? Intent.putExtra() の引数型は「String」「double」「int」「float」「long」「byte」「CharSequence」「char」「Bundle」「Parcelable」「Serializable」「boolean」「short」 となってるので、それ以外のオブジェクトやカスタムクラスを、そのまま受け渡しできないようです。
カスタムオブジェクトを受け渡す場合、事前に Serializable を implements しておく。 [java] import java.io.Serializable; import java.util.Date;
public class Schedule implements Serializable { private static final long serialVersionUID = -7168471472978288786L;
private Date dtstart;
private String title;
public Date getDtstart() {
return dtstart;
}
public void setDtstart(Date value) {
dtstart = value;
}
public String getTitle() {
return title;
}
public void setTitle(String value) {
title = value;
}
} [/java]
[java]
private void pageDetail(Schedule schedule) {
Intent intent = new Intent(CalendarActivity.this, ScheduleDetailActivity.class);
intent.putExtra("schedule", schedule);
startActivityForResult(intent, 1);
}
[/java]
[java] public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.schedule_detail);
Intent intent = getIntent();
Schedule schedule = (Schedule) intent.getSerializableExtra("schedule");
}
[/java]
2011/06/08追記 こちらの方が正しいかも。 [java] public void onActivityResult(int requestCode, int resultCode,Intent data) { if( resultCode == RESULT_OK) { Schedule schedule = (Schedule) data.getSerializableExtra("schedule"); } } [/java]
Android Market 登録用のアプリを作成するには署名が必要だが、Eclipseを利用するのが便利。
(1) プロジェクトを右クリックして、Android Tools > Export Signed を選択する
(2) プロジェクトを確認する
(3) 初回は storekey の作成が必要となるので「Create new keystore」を選択し、keystoreのパスとパスワード入力する。 今後、アップデートする時はこの storekey を使う必要があるので、公開したら確実に保存しておく事。
(4) 各項目を入力する。
Alias:プロジェクト名など Password:storekeyのパスワード Validity(years):25 (推奨)
「First and Last Name」以下はアプリ登録者によるが、
個人なら「First and Last Name」 会社なら「Organization」
を入力する感じで良いかと。
(5) 最後に .apk の書き出し先を指定する。
表示・非表示のメソッド setVisibility() は boolean で渡すと思いきや int で渡すという View の仕様。 [java] function void changeTextVisible(boolean isActive) { TextView textView = (TextView) findViewById(R.id.textView); int active = isActive ? View.VISIBLE : View.INVISIBLE; textView.setVisibility(active); } [/java]
いやぁ、ハマッたハマった!
Xperia arcでレイアウト表示が拡大される
回転させたり、アプリを開き直すと正常に戻ります。 シミュレータや他の実機ではその症状が起こらない。
はじめは ListView のスクロール処理がおかしいのか?とずっと思い込んでいたが、 簡単なサンプルでも同じ現象が起きました。
Xperia arc上の他のアプリではそのような症状はないので、何かしら設定がある。 でも、わからない。。。 最後はAndroidの会にMLしました。
あっさり解決!
結論から言うと、SDKのレベルと機種依存でした。
minSdkVersion=4(Android1.6)以上で解決します。
[java]
という事で、minSdkVersionは必ず設定する事ですね。 しかし今回の件で、関係ない API を調べて少しばかり勉強になったり(^^;)