久しぶりにRailsなんて触っちゃってみる

sudo gem install rails

こんなエラーが出るかもしれない。

Installing ri documentation for rails-3.1.3...
File not found: lib

Rdocを新しいやついれれば解決。

sudo gem install rdoc

プロジェクト作成&サーバー起動!

rails new hogehoge
cd hogehoge
rails server

こんなエラーが出るかもしれない。

/usr/local/lib/ruby/gems/1.9.1/gems/execjs-1.2.9/lib/execjs/runtimes.rb:47:in `autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)
	from /usr/local/lib/ruby/gems/1.9.1/gems/execjs-1.2.9/lib/execjs.rb:5:in `<module:ExecJS>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/execjs-1.2.9/lib/execjs.rb:4:in `<top (required)>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/coffee-script-2.2.0/lib/coffee_script.rb:1:in `require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/coffee-script-2.2.0/lib/coffee_script.rb:1:in `<top (required)>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/coffee-script-2.2.0/lib/coffee-script.rb:1:in `require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/coffee-script-2.2.0/lib/coffee-script.rb:1:in `<top (required)>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/coffee-rails-3.1.1/lib/coffee-rails.rb:1:in `require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/coffee-rails-3.1.1/lib/coffee-rails.rb:1:in `<top (required)>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler/runtime.rb:68:in `require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler/runtime.rb:68:in `block (2 levels) in require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler/runtime.rb:66:in `each'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler/runtime.rb:66:in `block in require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler/runtime.rb:55:in `each'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler/runtime.rb:55:in `require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/bundler-1.0.21/lib/bundler.rb:122:in `require'
	from /hogehoge/config/application.rb:7:in `<top (required)>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/commands.rb:52:in `require'
	from /usr/local/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/commands.rb:52:in `block in <top (required)>'
	from /usr/local/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/commands.rb:49:in `tap'
	from /usr/local/lib/ruby/gems/1.9.1/gems/railties-3.1.3/lib/rails/commands.rb:49:in `<top (required)>'
	from script/rails:6:in `require'
	from script/rails:6:in `<main>'

https://github.com/sstephenson/execjs

のリストにあるJSエンジン入れろと言っているので

sudo apt-get install nodejs

再度実行!

rails server

=> Booting WEBrick
=> Rails 3.1.3 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2011-11-29 22:04:02] INFO  WEBrick 1.3.1
[2011-11-29 22:04:02] INFO  ruby 1.9.3 (2011-10-30) [i686-linux]
[2011-11-29 22:04:02] INFO  WEBrick::HTTPServer#start: pid=23096 port=3000

でとりあえず起動できたよ。

ProgressBarに画像を使う

ProgressBarってバージョンごとに色が違ったり色合いがダサかったりでヤダ!
ってことで独自で定義するやり方。


背景画像(progress_back.png


メーター画像(progress_front.png


どちらも幅1pxの画像。

プログレスバーのdrawable

custom_progress.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
    	android:id="@android:id/background"
    	android:drawable="@drawable/progress_back"/>
    <item android:id="@android:id/progress">
        <clip android:drawable="@drawable/progress_front"/> 
    </item>
</layer-list> 

レイアウト

main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:background="#AAAAAAAA">
	<ProgressBar
		android:id="@+id/progress_bar"
		android:layout_width="200dp"
		android:layout_height="wrap_content"
		android:layout_gravity="center"
		android:progressDrawable="@drawable/custom_progress"
		style="?android:attr/progressBarStyleHorizontal" />
</FrameLayout>

アクティビティ

MainActivity.java

package com.sample;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ProgressBar;

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
        progressBar.setMax(100);
        progressBar.setProgress(50);
    }
}

実行するとこんな感じになる。


ただし、現行のADT 10.0.1では、これ(main.xml)をグラフィカルエディタで表示した場合に、

java.lang.UnsupportedOperationException
Exception details are logged in Window > Show View > Error Log

となり、
android:progressDrawable="@drawable/custom_progress"
の部分でエラーが起きてエディタが使えない。


なので、プログラム上で

progressBar.setProgressDrawable(getResources().getDrawable(R.drawable.custom_progress));

って設定するほうが良いかも。

タブレット時代のレイアウト FragmentLayout

Androidは解像度の異なる端末がいろいろ出てきて悩みどころ。
タブレット型に関してはFragments API使えばiPadっぽいレイアウトが組めるらしいけど現状3.0のみだし。


iPadiPhoneのようにマーケットが分かれていれば片方のみ対応とかできるけどAndroidはそうもいかないし...

とか、いろいろ困ってる人たちに朗報が!


GoogleAndroid 3.0の「Fragments API」で非互換性問題を解消へ
http://japan.internet.com/allnet/20110309/8.html

Android Developers Blog「Fragments For ALL」
http://android-developers.blogspot.com/2011/03/fragments-for-all.html


なんとFragments APIが1.6までの後方互換が効くようになった。

ってことでやってみよう。


まず、「Android SDK and AVD Manager」の「Available Packages」に

Android Compatibility package

が増えているのでインストール。


そうするとSDKのディレクトリにandroid-compatibilityフォルダができていて、
その下にv4/android-support-v4.jarがあるのでライブラリに追加する。
これがFragmentsの後方互換ライブラリ。


ではアプリ作成。
作るのはリストのアイテムを選択すると詳細画面が表示される、というシンプルなもの。

レイアウトファイルの作成

まず、縦向きと横向き用のレイアウトを用意

縦:layout/fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <fragment
    	class="com.sample.MainActivity$TitlesFragment"
    	android:id="@+id/titles"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent" />
</FrameLayout>

横:layout-land/fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <fragment
    	class="com.sample.MainActivity$TitlesFragment"
    	android:id="@+id/titles"
    	android:layout_width="fill_parent"
    	android:layout_height="fill_parent"
    	android:layout_weight="1"/>
    <FrameLayout
    	android:id="@+id/details"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:layout_weight="1"/>
</LinearLayout>

詳細画面のレイアウト:layout/details.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
  	android:id="@+id/detail_text"
  	android:layout_width="wrap_content"
  	android:layout_height="wrap_content"
  	android:textSize="20sp"/>
</FrameLayout>

※リスト画面のレイアウトはプログラム内で作ってしまうのでいらない

アクティビティの作成

これがなかなか面倒くさい。ApiDemosにあるソースを拝借して必要な部分のみ記述した。

package com.sample;

import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends FragmentActivity {

	private static final String[] ITEMS = { "A", "B", "C" };

	private static final String[] DETAILS = {
			"ラテン文字(アルファベット)の1番目の文字。小文字は a 。ギリシャ文字のΑ(アルファ)に由来し、キリル文字のАに相当する。",
			"ラテン文字(アルファベット)の2番目の文字。ギリシャ文字のΒ(ベータ)に由来する。小文字は b 。キリル文字のБ、Вと同系である。",
			"C は、ラテン文字(アルファベット)の3番目の文字。小文字は c 。" };

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.fragment_layout);
	}

	/**
	 * ポートレイト(縦向き)のアクティビティ
	 */
	public static class DetailsActivity extends FragmentActivity {
		@Override
		protected void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);

			if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
				// ランドスケープ(横向き)モードの場合、このアクティビティはいらない
				finish();
				return;
			}

			if (savedInstanceState == null) {
				// フラグメント(右ペイン)を作成
				DetailsFragment details = new DetailsFragment();
				details.setArguments(getIntent().getExtras());
				getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
			}
		}
	}

	/**
	 * 左のペイン
	 */
	public static class TitlesFragment extends ListFragment {

		boolean mDualPane;
		int mCurCheckPosition = 0;
		int mShownCheckPosition = -1;

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

			// リストの作成
			setListAdapter(new ArrayAdapter<String>(getActivity(),
					android.R.layout.simple_list_item_checked, ITEMS));

			// デュアルモードかどうかを確認
			View detailsFrame = getActivity().findViewById(R.id.details);
			mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;

			if (savedInstanceState != null) {
				// 前回の選択位置を取得
				mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
				mShownCheckPosition = savedInstanceState.getInt("shownChoice", -1);
			}

			if (mDualPane) {
				getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
				showDetails(mCurCheckPosition);
			}
		}

		@Override
		public void onSaveInstanceState(Bundle outState) {
			super.onSaveInstanceState(outState);
			outState.putInt("curChoice", mCurCheckPosition);
			outState.putInt("shownChoice", mShownCheckPosition);
		}

		@Override
		public void onListItemClick(ListView l, View v, int position, long id) {
			showDetails(position);
		}

		void showDetails(int index) {
			mCurCheckPosition = index;

			if (mDualPane) {
				// デュアルモードの場合は選択されているアイテムをハイライト
				getListView().setItemChecked(index, true);

				if (mShownCheckPosition != mCurCheckPosition) {
					// 別のアイテムが選択されたら対応する画面を作成
					DetailsFragment df = DetailsFragment.newInstance(index);

					// トランザクションを開始して既存のフラグメントと入れ替える
					FragmentTransaction ft = getFragmentManager()
							.beginTransaction();
					ft.replace(R.id.details, df);
					ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
					ft.commit();
					mShownCheckPosition = index;
				}
			} else {
				// デュアルモードではない場合、詳細画面を起動
				Intent intent = new Intent();
				intent.setClass(getActivity(), DetailsActivity.class);
				intent.putExtra("index", index);
				startActivity(intent);
			}
		}
	}

	/**
	 * 右のペイン
	 */
	public static class DetailsFragment extends Fragment {
		
		public static DetailsFragment newInstance(int index) {
			DetailsFragment f = new DetailsFragment();

			// リストに対応するインデックスを仕込む
			Bundle args = new Bundle();
			args.putInt("index", index);
			f.setArguments(args);

			return f;
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			if (container == null) {
				// ポートレイトでリスト画面の場合は詳細画面を作らない
				return null;
			}

			View view = inflater.inflate(R.layout.details, null);
			TextView textView = (TextView) view.findViewById(R.id.detail_text);
			textView.setText(DETAILS[getArguments().getInt("index", 0)]);
			return view;
		}
	}
}

実行

まず縦向きで起動。


リストのアイテムをクリックして詳細画面表示。


ここでCtrl + F11でエミュレータの向きを変更。


おお、iPadっぽいぞ。

このレベルのソースだと、Honeycombと後方互換ライブラリの違いは

  • FragmentActivityを継承する
  • getFragmentManager()ではなくgetSupportFragmentManager()


というだけ。
ListFragmentはそのままでgetFragmentManager()が使えるんだけど、それはよく分からない。


仕事だと1.6からnewタブレットまで対応しろよボケ((((;゜Д゜)))
なんていうケースがあるので、アプリによってはこれで対応出来る部分も増えるかしら。

ExpandableListViewっぽいものを独自で実装してみる

ExpandableListViewの使い方覚えるが面倒で、更にカスタマイズしてみたけど開閉ボタンの消し方が分からなかったので、結局それっぽものを自作してみた。


※drawableのxmlは省略


ヘッダーのレイアウト
list_header.xml

<?xml version="1.0" encoding="utf-8"?>
<com.sample.RowHeader
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:background="@drawable/header">
  <TextView
  	android:id="@+id/header"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_gravity="center_vertical"
	android:textColor="#FFFFFFFF"
	android:textSize="20sp"/>  
</com.sample.RowHeader>

列のレイアウト
list_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:background="@drawable/row">
  <TextView
  	android:id="@+id/row"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_gravity="center_vertical"
	android:textColor="#FF000000"
	android:textSize="15sp"/>
</LinearLayout>

メイン
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/virtual_listview"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
</LinearLayout>


ヘッダークラス
RowHeader.java

package com.sample;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;

public class RowHeader extends LinearLayout {
	public boolean mIsExpanded;
	public List<View> mRows;

	public RowHeader(Context context, AttributeSet attr) {
		super(context, attr);
		mIsExpanded = false;
		mRows = new ArrayList<View>();
	}

	public boolean isExpanded() {
		return mIsExpanded;
	}
	
	public void expand() {
		mIsExpanded = true;
		for (final View v : mRows) {
			v.setVisibility(View.VISIBLE);
		}
	}
	
	public void collapse() {
		mIsExpanded = false;
		for (final View v : mRows) {
			v.setVisibility(View.GONE);
		}
	}
	
	public List<View> getRows() {
		return mRows;
	}

	public int getRowCount() {
		return mRows.size();
	}

	public void addRow(View row) {
		mRows.add(row);
	}
}

メイン

package com.sample;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
    private Map<String, List<String>> map;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        map = new HashMap<String, List<String>>() {
        	{ put("哺乳類", new ArrayList<String>() {{ add("イヌ"); add("ネコ"); add("イルカ"); }} ); }
        	{ put("鳥類", new ArrayList<String>() {{ add("スズメ"); add("ペンギン"); }} ); }
        	{ put("魚類", new ArrayList<String>() {{ add("ブリ"); }} ); }
        };
        
        // 擬似リストビュー
        final LinearLayout listView = (LinearLayout) findViewById(R.id.virtual_listview);        
        
        for (final Entry<String, List<String>> set : map.entrySet()) {
        	// ヘッダーのレイアウトを取得してテキストを設定
        	RowHeader headerLayout = (RowHeader) View.inflate(this, R.layout.list_header, null);
        	TextView headerText = (TextView) headerLayout.findViewById(R.id.header);
        	headerText.setText(set.getKey());
        	
        	// ヘッダークリックイベント
        	headerLayout.setOnClickListener(new OnClickListener() {				
				@Override
				public void onClick(View v) {
					RowHeader headerView = (RowHeader) v;
					
					// ヘッダーが親から見て何番目にあるかを取得
					int index = listView.indexOfChild(headerView);
					
					if (headerView.getRowCount() == 0) { // 0の場合は子どもがまだ生成されていない									
						List<String> rows = set.getValue();
						for (String row : rows) {
							LinearLayout rowLayout = (LinearLayout) View.inflate(MainActivity.this, R.layout.list_row, null);
							TextView rowText = (TextView) rowLayout.findViewById(R.id.row);
							rowText.setText(row);
							// ヘッダーの次のインデックスにどんどん追加
							listView.addView(rowLayout, ++index);
							headerView.addRow(rowLayout);
						}						
					}
					
					if (headerView.isExpanded()) {
						headerView.collapse(); // 閉じる
					} else {
						headerView.expand(); // 開く
					}
				}
			});
        	listView.addView(headerLayout);
        }
    }
}

こんな感じになるよ。ヘッダーをクリックして開閉。


アニメーションがうまくつかねー



>>2011/07/25 追記


expandableListView.setGroupIndicator(null)


でインジケーター消せた<<2011/07/25

node.jsに触れてみた

node.js公式

http://nodejs.org/

node.jsとは

公式のAbout抜粋
・ノンブロッキングI/O
・2MB/スレッドを割り当てるシステムよりメモリ効率がいいよ
デッドロックの心配なし
・よって誰でも高速なシステムが開発できるよ

インストール

wget http://nodejs.org/dist/node-v0.4.1.tar.gz
tar zxvf node-v0.4.1.tar.gz

cd node-v0.4.1

./configure
make
make install

Hello node.js!!

公式にあるサンプルを実行してみる。

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(8124, "127.0.0.1");
console.log('Server running at http://127.0.0.1:8124/');


サーバーにブラウザでアクセスすると「Hello World」が表示された!


なんだかオラわくわくしてきたぞ!
仕事で使うことはないけどな!!

初心者に厳しいAndroid開発環境構築

Androidネタがたまってきたので、まず開発環境構築から。


よくWindowsで…とかMacで…とかってあるけど、基本的にそんなの関係ねぇ、ということを認識して欲しいです。

eclipseのダウンロード


Eclipse公式(英語)
http://www.eclipse.org/downloads/
*1


デフォルトで日本語がいい!
MergeDoc
http://mergedoc.sourceforge.jp/
*2

Androidプラットフォームのインストールとエミュレータ起動


%SDK_HOME%tools/android を実行してAndroid SDK and AVD Managerを起動。
[Available packages]を選択し[Packages available for download]の[Android Repository]を展開。


Android SDK Tools
SDK Platform Android 3.0


にチェックをいれてインストール。(結構時間がかかる)


完了したら[Virtual devices]を選択し[New]。
NameとTarget(Android3.0)を設定して[Create AVD]。


作成したAVDを選択して[Start]で[Launch Options]が開くので[Scale display to real size]にチェックを入れて


Screen Size: 68
Monitor dip: 160 *3


で[Launch]。
*4


なぜかMOTOROLA XOOMが立ち上がります。(これまた結構時間がかかる)


MOTOROLA XOOM公式
http://www.motorola.com/Consumers/US-EN/Consumer-Product-and-Services/Tablets/ci.MOTOROLA-XOOM-US-EN.overview


>>2011/07/25 追記

XOOMじゃなくてこれがAndroid3.0のUIだったのね。。。<<2011/07/25


KDDI公式
http://www.kddi.com/corporate/news_release/2011/0228a/index.html

*5


Android ADT Pluginのインストール


プラグインリポジトリに以下を追加します。


Name: Android ADT Plugin (任意)
Location: https://dl-ssl.google.com/android/eclipse/


Developer Toolが出てくるのでチェックしてすべてインストール・再起動。

eclipseAndroid SDKのパスを設定

設定画面に[Android]の項目が増えてるので選択。


※ここでレポートを送信するみたいなダイアログが出るので消す。これ重要。


SDK LocationにAndroid SDKへのパスを設定しApply。
一覧にプラットフォーム名等ずらっとでれば設定完了。

Androidプロジェクトの作成

eclipseツールバーのこれクリック(たぶんデフォルトは左から5つ目)


Contents: Create project from existing sample
Build Target: Android 3.0


を選択して[finish]。

※最初はSamplesのプルダウンをApiDemosがおすすめ


生成されたプロジェクトを選択して[Run As]→[Android Application]でアプリケーションのインストールが開始・起動。


※コンソールにこんなエラーが出ちゃった場合

You must perform a full uninstall of the application. WARNING: This will remove the application data!
Please execute 'adb uninstall com.example.android.apis' in a shell.


%SDK_HOME%/platform-toolsにadbってのがあるので、書いてあるとおりに


adb uninstall com.example.android.apis


を実行し、再度Run As Android Applicationしましょう。


*1:軽量な Eclipse IDE for Java Developers あたりがおすすめ

*2:All in One なので重いから時間かかるけど

*3:dip(dod intdependent pixel) ちなみにプリンタでよく使うdpi(dod per inch)と間違えないよーに

*4:指定しなくてもいいけど、とんでもないことになるよ♪

*5:3G関係ないのになんでKDDIからやねん、基本料金云々か