ListViewを使うとなると、行のコンポーネントをカスタマイズしたいところ。 iPhoneで言うと、UITableCellView、Flexで言うと(ry
Android Adapter & ListView & ExpandableListView
想像していたよりやっかいでした。 http://hoge/api/person/list にアクセスして人名リストデータをJSONで取得し、ListViewに人名と画像を表示するサンプル。 (サンプル用にオブジェクト名を変えているのでコンパイルエラーしているかも) ※画像はプロジェクトに埋め込み
layout/person_row.xml
src/xxxx/PersonList.java [java] public class PersonList extends ListActivity { DefaultHttpClient httpClient;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initHttp();
loadPersons();
}
static class ViewHolder {
TextView nameTextView;
ImageView imageView1;
}
class Person {
String name;
String no;
public Person() {
}
}
public int personImageId(int no) {
switch (no) {
case 1:
return R.drawable.image1;
case 2:
return R.drawable.image2;
case 3:
return R.drawable.image2;
}
return 0;
}
public class MyAdapter extends ArrayAdapter<Person> {
private LayoutInflater inflater;
private int layoutId;
public MyAdapter(Context context, int layoutId, List<Person> objects) {
super(context, 0, objects);
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.layoutId = layoutId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
holder = new ViewHolder();
convertView = inflater.inflate(layoutId, parent, false);
holder.nameTextView = (TextView) convertView.findViewById(R.id.nameTextView);
holder.imageView1 = (ImageView) convertView.findViewById(R.id.imageView1);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if(!isEnabled(position)) {
convertView.setBackgroundColor(Color.BLACK);
} else {
Person person = getItem(position);
holder.nameTextView.setText(person.name);
int resourceId1 = personImageId(Integer.parseInt(person.no));
if (resourceId1 > 0) holder.imageView1.setImageResource(resourceId1);
}
return convertView;
}
}
public void initHttp() {
//スキーマ登録
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(
new Scheme(
HttpHost.DEFAULT_SCHEME_NAME,
PlainSocketFactory.getSocketFactory(),
80
)
);
//HTTPパラメータ設定
HttpParams httpParams;
httpParams= new BasicHttpParams();
HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8);
//HTTPクライアント生成
httpClient = new DefaultHttpClient(
new ThreadSafeClientConnManager(httpParams, schReg),
httpParams
);
}
public void loadPersons() {
Uri.Builder uriBuilder = new Uri.Builder();
uriBuilder.scheme("http");
uriBuilder.authority("hoge");
uriBuilder.path("/api/person/list");
String uri = uriBuilder.toString();
HttpUriRequest httpRequest = new HttpGet(uri);
//HTTP Request送信
HttpResponse response = null;
try{
response = httpClient.execute(httpRequest);
} catch(Exception e) {
return;
}
// レスポンスコードを確認
if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK){
AlertDialog.Builder diag = new AlertDialog.Builder(PersonList.this);
diag.setTitle("ネットワークエラー");
diag.setMessage("サーバに接続できませんでした");
// ダイアログに表示するボタンの定義
DialogInterface.OnClickListener listner = new DialogInterface.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
setResult(RESULT_OK);
}
};
diag.setPositiveButton("OK",listner);
diag.create();
diag.show();
return;
}
//JSON取得
StringBuilder json = new StringBuilder();
try{
HttpEntity entity = response.getEntity();
InputStream input = entity.getContent();
InputStreamReader reader = new InputStreamReader(input);
BufferedReader bufReader = new BufferedReader(reader);
String line;
while((line = bufReader.readLine()) != null) {
json.append(line);
}
} catch(IOException e) {
return;
}
items = new ArrayList<Person>();
// JSON解析
try{
JSONObject jsonRoot = new JSONObject(json.toString());
JSONArray jsonAreas = jsonRoot.getJSONArray("person");
int i;
for(i = 0; i < jsonAreas.length(); i++){
JSONObject jsonRslt = jsonAreas.getJSONObject(i);
Person person = new Person();
person.name = jsonRslt.getString("name");
person.image1 = jsonRslt.getString("image1");
items.add(person);
}
MyAdapter adapter = new MyAdapter(this, R.layout.person_row, items);
setListAdapter(adapter);
} catch(JSONException e) {
return;
}
}
[/java]
ポイントやハマったところ。
普通にActivityを使ってもできますが、手続きを多少簡略化できるListActivityを使いました。 Activityの場合、ListViewレイアウトを作ったり、Adapterのセットの仕方が異なってくるかと。
2011/08/11 追記 ListActivity を継承しない場合は、 [java] .... MyAdapter adapter = new MyAdapter(this, R.layout.list_row, values); (ListView) listView = (ListView) findViewById(R.id.listView); listView.setAdapter(adapter); [/java] のように、setAdapter() メソッドで Adapter を直接指定します。 ListActivity は1画面1リスト(だと思う)なので、複数リストを利用したい場合は、このやり方でよいかと。
JSONデータ取得&Listデータ生成後、ArrayAdapterを利用してListViewにバインドしてやります。 [java] MyAdapter adapter = new MyAdapter(this, R.layout.person_row, items); setListAdapter(adapter); [/java] 2番目の引数が、行レイアウトになります。
リストを描画しようとする度に呼ばれます(多分)。 iOSで言うと、TableView:cellForRowAtIndexPath にあたる部分と思ってよいかも。
Androidでは何やらif文で分岐してまどろっこしい書き方だけど、 既に行のコンポーネントが作成されていれば再利用します。
つまりは、ListViewの行のキャッシュ処理
行のキャッシュは、静的クラス(ViewHolder)として定義しています。
positionは行のインデックスにあたり、データであるList
これはどうにかして欲しいかも。 ちょっと、画面を作る気にならないほど面倒くさいです。
と言う訳で、リスト表示に関してはコーディング的にもツール的にAppleの方が軍配が上がるかな?
画像ファイルなど、リソースファイル名が数字にすると、 コンパイルでgen(R.java)がエラーになります。
仕様でファイル名が、プロパティとして自動生成されるからのようです。 [java] public static final class drawable { public static final int icon=0x7f020000; public static final int player1=0x7f020001; public static final int player2=0x7f020002; public static final int player3=0x7f020003; public static final int player4=0x7f020004; public static final int player5=0x7f020005; public static final int player6=0x7f020006; } [java]
プロパティからリソースIDを取得して、画像を生成。
[java]
ImageView imageView;
imageView = (ImageView) convertView.findViewById(R.id.imageView);
imageView.setImageResource(R.drawable.player1);
[/java]
だそうです。 Titanium Mobile 1.6.0 is Released!
既に、日本語訳も。 Titanium Mobile 1.6.0がリリースされました(速報→追記しました)
iphoneもAndroidoもFacebook Graph SDKに対応とのこと。
Android NDKのメリットとして、 (1) DalvikVMはJava言語を利用するので、NativeのC/C++のコードより遅くなる (2) C/C++のライブラリを組み込める
ただ、Android 2.2以降では、JITコンパイラの採用により高速化されているとのこと。 自分もC/C++はやらないので、必要ないかな?
Android + Eclipseでアプリを起動すると、エミュレータのエラーがちょくちょくある。
ActivityManager: Warning: Activity not started, its current task has been brought to the front
既にActivityが起動していて、新たにActivityを起動できない。 【esc】キーでActivityを終了してやる必要がある。
the user data image is used by another emulator. aborting
エミュレータを複数起動しようとしている。
Titaniumと同時で起動してると、エミュレータの権限を奪われてしまうのか?おかしくなってしまう。 解決としては、単にTitaniumとエミュレータのプロセスを完全に落として、Eclipseを再起動。
また、Titaniumをインストールしてしまうと、Titanium用のエミュレータも追加されるので、 Runする時にエミュレータを明確に指定する必要がある。
Objective-Cを勉強し始めた頃、Objective-C 基礎編 Vol2で、@synthesizeについてメモをした。
この時は実践してなかったので、チンプンカンプン。 で、数ヶ月経った今、本当に基本的な機能を知らなかった事が判明・・・。
@synthesize で定義したプロパティはselfでないとアクセッサが機能しない!
ちょっと脱力です(^^;)
今までは、プロパティには self をつけずにコーディングしてました。 で、deallocの処理で律儀に全部 release していたのです。
@synthesizeで定義されたプロパティは以下のsetter、getterを処理してくれます。
- (id) foo {
return foo;
}
- (void) setFoo:(id) aFoo {
if (aFoo != foo) {
[aFoo retain];
[foo release];
foo = aFoo;
}
}
よって、dealloc() で release する必要がなく nil にするだけです。
@property (nonatomic, retain) Person person;
@synthesize person;
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc] init];
}
- (void)walk {
self.person = ....
}
- (void)dealloc {
person = nil;
}
[[Person alloc] init] しないケース(他の画面遷移からオブジェクトを受ける等)場合は、 deallocのperson = nil さえ要らないケースもあるかも知れませんが、とりあえず nilにしておいた方が安全ですかね?
はっきり言って、自分はわかりません(賢い人教えてw)。 selfを利用するかどうかは人それぞれかも知れませんし、 現に自分もdealloc→releaseで取りあえず何とかなっていたみたいなので。
ただ、releaseを書くくらいなら、selfでアクセスする癖を付けた方が良いのかなぁ?とも思いました。 あとNSIntegerとかポインターでないオブジェクトは別に関係ないかも知れません。
カスタムクラスのインスタンスの扱いでも記載したように、init する場合は明示的に
Person _person = [[Person alloc] init];
self.person = _person;
[_person release];
とrelease した方がAnalyze で警告を防げます。 また、以下のコードでも問題はないかも。
person = [[Person alloc] init];
ただし init 以降の person のアクセスは、self.person で行うべきです。
Android 3.0 Platform Android 3.0 Platform Highlights Android API Differences Report
SDKはHoneycombからAPI Level 11のアップです。
現状はタブレット向けにしかのらないと思いますが、animation framework、Hardware-accelerated 2D、Renderscript 3Dなどグラフィック周りが進化してるみたい。
早速、一部日本語訳してます。 Android 3.0 Platform - 1 -
あぁ、タブレット買っちゃおうかな?
簡単なアプリならあまりハマる事はないと思うんですけど、 オブジェクトが増大するとメモリ管理が本当に大変です。
今作ってるアプリが、100ファイル以上あるので大ハマり! 現状iOSではretainCount方式しかないので自分の脳ではちょっと整理ができません(T_T) あとでいいや!とか半ば諦めていましたが、ついに糸口が見えてきました。
シミュレータやInstruentsで一見うまくいっているように見えても、実機で落ちる事があります。 これは環境の速度やタイミングの問題でしょうか? オブジェクトの定義ミス(getterやreleaseなど)を正確に処理してないことが原因が多かったです。
<a href="
はじめは、
特定の処理をする毎に5M消費
というループにハマって、あっという間に100Mを消費してしまいます。 iOS4はマルチタスク(バックグランド待機)なので、メモリは消費したままで解放できない。 これじゃぁ・・・とずっと悩んで、Instrumentsと睨めっこする日々。そして、どのセンテンスがどれくらい影響してるか?がおよその見当がつけられます。
という事で、ちょっと出口が見えてきました!
Android、iPhoneを取りあえず手っ取り早く作りたい為のツール。 Corona、Titanium、Unity 3D
Coronaをちょっと触ってみたんだけど、ゲームなどのアニメーション系をサクッと作るのには向いてると思う。 日本コロナの会にも行って来ましたが、Flasherぽい人が多数感じで。 だけど、
Luaを覚えなきゃいけないし、開発環境がイマイチ
Flasherの人には良いかもしれないが、開発環境的にちょっと非効率的な気もする。 またバリバリのゲームだったら、試してはいないけどUnity 3Dの選択肢もあるかも知れない。
とりあえずCoronaもアンテナだけは張っておくとして、今月のWeb+DBでも紹介されていたTitanium Developerを試してみた。
まずTitanuimとは?
・Titanium Mobileで始める iPhone/Androidアプリ開発※注)iWorks資料 ・Titanium Mobileで作る! iPhone/Androidアプリ
まず、iOSとAndroid SDKはインストールしておく必要があります。 次に、AppCeleratorのTitanium MobileからダウンロードしてTitanium Developerをアプリケーションにインストール。 ※iPhoneもあるのでOSX
Titanium Mobileを初回時に起動すると、以下のディレクトリにSDKがインストールされる。
/Library/Application\ Support/Titanium
また、自動的に最新アップデータするか?と聞いてくるのでアップデートする。
次に、AndroidSDKのパスを選択する必要がある(iPhoneは自動認識)。 Android SDK 2.3.1以前では
/Developer/android-sdk-mac_x86/platform-tools/adb
のパスでは起動しないので、
/Developer/android-sdk-mac_x86/tools/
へadbをコピーしてから選択する。
とりあえずの動作確認として「KitchenSink」が用意されているのでダウンロード。 Titanium Developerで【Project Import】し、Test&Package>Run EmulatorでLaunchするとエミュレータが起動する。
Androidはうまくインストールされない・・・。 どうやら、選択するSDKによって挙動が違うらしい。
Android SDK & AVD Managerを以下の通り更新。 ※Google APIは必須 Titaniumの起動SDKを「APIs 2.3」にしてみると起動した。
【New Project】をクリックして新規プロジェクトを作成。 Project type:Mobileすると「iPhone SDK 」「Android SDK」が検知されるので、必要項目を入力して【Create Project】する。
removeFromSuperview でアニメーションそのまま記述すると、 アニメーションが実行される前に、viewが取り除かれてしまう。
そこで、setAnimationDidStopSelector を利用してアニメーション実行後に処理をしてやることで実現できる。
- (IBAction)closeViewPressed:(id)sender {
[UIView beginAnimations:@"transition2" context:NULL];
[UIView setAnimationDuration:0.5f];
sampleView.alpha = 0.85f;
[UIView setAnimationDidStopSelector:@selector(closeLyricView)];
lyricsSelectView.alpha = 0;
[UIView commitAnimations];
}
- (void)closeView {
[sampleView removeFromSuperview];
}