電脳戯言記

プログラムのこととか日々のどうでもいいこととかをつらつらと記します。 Twitter ID: @nagakenjs

ActionBarSherlockでActionBarとViewPagerを連携する

ActionBarSherlockのFragmentデモにあるTabs and Pagerは、OSネイティブのタブUIを使っているため2.3以前の端末で動かしたときと4.0以降で動かしたときで表示が異なります。

そこで、ActionBarのタブとViewPagerを連携することでどの端末でも見た目を統一できます。

ソースコード全体は以下に置きました。

ダミーFragmentの作成

各タブに表示するためのダミーFragmentを作ります。

public class DummyFragment extends SherlockFragment {
    private int mNum;

    public static DummyFragment getInstance(int num) {
        DummyFragment fragment = new DummyFragment();

        Bundle args = new Bundle();
        args.putInt("num", num);
        fragment.setArguments(args);

        return fragment;
    }

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

        Bundle args = this.getArguments();
        mNum = (args != null) ? args.getInt("num") : 0;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        TextView view = (TextView) inflater.inflate(R.layout.hello_world, container, false);
        view.setText("Tab" + mNum);

        return view;
    }
}

ダミーFragmentのXMLファイル(hello_world.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/hello_text"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_vertical|center_horizontal"
    android:text="@string/hello_world"
    android:textAppearance="?android:attr/textAppearanceMedium" />

FragmentPagerAdapterの作成

ViewPagerを管理するためにFragmentPagerAdapterのサブクラス(TabAdapterクラス)を作ります。

ActionBarでタブを使えるようにするためにActionBar.NAVIGATION_MODE_TABSを設定します。
mViewPager.setOnPageChangeListener()で設定するイベントについては後述します。

public TabAdapter(SherlockFragmentActivity activity, ViewPager pager) {
    super(activity.getSupportFragmentManager());

    mActionBar = activity.getSupportActionBar();
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    mViewPager = pager;
    mViewPager.setOnPageChangeListener(mOnPageChangeListener);
}

@Override
public Fragment getItem(int position) {
    TabInfo info = mTabs.get(position);
    if (info != null) {
        if (info.fragment == null) {
            info.fragment = DummyFragment.getInstance(position);
        }
        return info.fragment;
    } else {
        return new Fragment();
    }
}

@Override
public int getCount() {
    return this.mTabs.size();
}

タブの追加処理

タブを追加できるよう、TabAdapterクラスにメソッドを追加します。
setTabListener()で設定するイベントについては後述します。

private final SparseArray<TabInfo> mTabs = new SparseArray<TabInfo>();

public void addTab(String tag, Class<?> clazz, Bundle args) {
    ActionBar.Tab tab = this.mActionBar.newTab();
    tab.setText(tag);
    tab.setTabListener(mTabListener);
    this.mActionBar.addTab(tab);

    TabInfo info = new TabInfo(tag, clazz, args);
    mTabs.put(tab.getPosition(), info);
}

タブ情報の管理クラス(TabInfoクラス)以下のように定義しました。

static final class TabInfo {
    public final String tag;
    public final Class<?> clss;
    public final Bundle args;
    public Fragment fragment;

    TabInfo(String _tag, Class<?> _class, Bundle _args) {
        tag = _tag;
        clss = _class;
        args = _args;
    }
}

タブ制御イベントの追加

ActionBarのタブをタップしたときの制御イベントを定義します。
ActionBarとViewPagerは別制御なので、タブを選択したときにViewPagerを選択するようonTabSelected()メソッドに処理を追加します。

private final ActionBar.TabListener mTabListener = new ActionBar.TabListener() {
    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
};

次にViewPagerからActionBarを制御するための制御イベントを定義します。
ViewPagerのページが切り替わったらActionBarのタブも切り替わるようにonPageSelected()メソッドに処理を追加します。

private final ViewPager.OnPageChangeListener mOnPageChangeListener = new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        mActionBar.selectTab(mActionBar.getTabAt(position));
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }
};

Activityからの呼び出し

SherlockFragmentActivityのonCreateでタブの管理処理を呼び出します。

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

    ViewPager pager = (ViewPager) findViewById(R.id.pager);
    TabAdapter tabAdapter = new TabAdapter(this, pager);
    pager.setAdapter(tabAdapter);

    tabAdapter.addTab("Tab1", DummyFragment.class, null);
    tabAdapter.addTab("Tab2", DummyFragment.class, null);
    tabAdapter.addTab("Tab3", DummyFragment.class, null);
}

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <FrameLayout
        android:id="@android:id/tabcontent"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="0" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
</LinearLayout>

これでAndroid 4.0より前の端末でも4.0のようなTab+ViewPagerが使えるようになります。
f:id:xion:20121223175005p:plain

netduino pLusでfeLicaリーダー動かしてみました(補足編)

この投稿はLL/ML Advent Calendarの12/11向けの記事です。

いつものごとくTLを眺めていたら@teruroさんに


巻き込まれたお誘いがあったので放置気味のブログを復活させて参加してみました。

LL/MLにちなむと言うことでいろいろ考えたのですが、先日のNGK2012Bで話した内容の補足をしようかと思います。発表資料はこちら。

補足と言っても全てを取り扱うことはできませんので、NetduinoとFeliCaリーダーの通信方法について触れたいと思います。

ハードウェア

今回紹介するのは次の二つです。通販サイトのスイッチサイエンスさんにリンクを張っています。

準備

開発環境の構築は先日書いた記事を参照して下さい。

NetduinoとRC-S620Sとはシリアル通信で接続します。
作りたてのソリューションではシリアルポートは使えませんので、参照の追加より「Microsoft.SPOT.Hardware.SerialPort」を追加します。

f:id:xion:20121209212029p:plain

NetduinoとRC-S620Sの接続

Netduino Plusには、二つのシリアルポートがあります。
COM1がD0(RX)とD1(TX)、COM2がD2(RX)とD3(TX)となりますが、今回はCOM1の方を使います。
RC-S620Sのピンアサインはこうなっています。

ピン番号 ピン名称 機能 備考
1 VDD 電源端子 DC3.3V±5%もしくはDC5.0V±5%
2 RXD RXD信号  
3 TXD TXD信号  
4 GND GND端子 GND接地
5 Reserve テスト出力端子 コントローラ側でOpen処理が必要
6 GND GND端子 GND接地

Netduinoと繋ぐにはクロス接続する必要があるので、互いの端子のRXとTXを繋ぐように配線します。ピン1は電源、ピン4と5はGNDなのでNetduinoの該当端子にそれぞれ繋ぎます。
Netduino PlusとRC-S620Sを繋いだ図が以下になります。

f:id:xion:20121210235554p:plain

シリアル通信

シリアルポートを扱うには、System.IO.Ports.SerialPortクラスを使います。コンストラクタでポート名、ボーレート、パリティ、などのシリアル通信に必要な値を指定します。
処理を開始する前にOpenメソッドを実行し、ポートを開く必要があります。

SerialPort serialPort = new SerialPort(Serial.COM1, 9600, Parity.None, 8, StopBits.One);
serialPort.Open();
// なにか処理
serialPort.Close();

シリアルポートにデータを書き込むのは簡単です。
SerialPortクラスのWriteメソッドにbyte配列を指定します。

byte[] buffer = new byte[] { 0x01, 0x02, 0x03 };
serialPort.Write(buffer, 0, buffer.Length);

シリアルデータの読み込みにはReadメソッドを使います。イベントハンドラを用いる非同期なやり方もあるのですが、RC-S620Sでは頻繁に応答があるためReadメソッドを使った同期的なやり方を採用しました。
始めに受信したいバイト数(例では256バイト)分シリアルポートにデータがたまるまで待機し、Readメソッドでたまったデータをバッファに読み込みます。

byte[] buffer = new byte[256];
int len = buffer.Length;
while (serialPort.BytesToRead < len)
{
    Thread.Sleep(5);
}
int receivedBytes = serialPort.Read(buffer, 0, buffer.Length);


以上が.NET Micro Frameworkでのシリアル通信の基本になります。
一度ハードウェアを組み立てれば、後はソフトウェアの問題なので、興味のある方はRC-S620Sのコマンドリファレンスマニュアルを読みつつドライバを書いてみてはいかがでしょうか。
Arduino向けのソースコードがありますので、私はこれをベースにNetduino用のドライバを書きました。

今回実装したFeliCa認証システムのソースは、調整ののちBitbucket上で公開予定です。

Netduinoの開発環境を整える

Netduinoとは

簡単に言うとC#で開発のできるマイコンボードです。
.NET Micro Frameworkという小規模組み込み機器用の.NET環境が組み込まれており、Visual Studioで書いたC#プログラムを動作させることができます。
詳しくはこちらを。

準備

Netduinoでの開発を行うためには開発環境を整えなくてはいけません。
まずはこちらより、以下の物をインストールして下さい。

インストールが完了したらVisual Studioを起動してソリューションを作成します。
テンプレートの「Micro Framework」から「Netduino Plus Application」を選んで下さい。

f:id:xion:20121209212023p:plain

Lチカ

Hello World代わりとしてNetduinoにあるオンボードLEDを点滅させてみます。(Lチカ)
Netduino上のピンやLEDはOutputPortクラスで取得できます。Pins.ONBOARD_LEDを指定してオンボードLEDのオブジェクトを取得し、WriteメソッドでLEDをオン/オフさせます。

using System.Threading;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;

namespace NetduinoPlusApplication1
{
    public class Program
    {
        public static void Main()
        {
            // オンボードLEDの点滅
            OutputPort boardLed = new OutputPort(Pins.ONBOARD_LED, false);
            boardLed.Write(false);

            // LED点滅は別スレッドで実行
            new Thread(() =>
            {
                while (true)
                {
                    boardLed.Write(false);
                    Thread.Sleep(500);
                    boardLed.Write(true);
                    Thread.Sleep(500);
                }
            }).Start();

            // メインスレッドを終了させるとプログラムが終了するので無限に停止
            Thread.Sleep(Timeout.Infinite);
        }

    }
}

ICS向けAndroidのビルド環境を整える

最近、Galaxy Nexusを手に入れました。
この端末が欲しかった理由は自分専用のカスタムROMを作ること。Android 4.0のリファレンス機だけあって、カスタムROMは作りやすいのです。
とはいえAndroidのソースの入手からビルドを行うのはなかなか困難。
そこでカスタムROM作りの顛末を、自分の備忘録も兼ねてこのブログに残していきます。

あ、お約束ですが注意点。
このブログの記事を真似て端末が起動しなくなった、保証が切れたなどのトラブルが起きても当方では一切責任を負いません。
あくまで自己責任でお願いします。

開発環境

Androidのビルドに使用した環境は次のとおりです。

ハードウェア

ThinkPad X201s

CPU Intel Core i7 L620 2GHz(論理4コア)
メモリ 8GB
HDD 320GB(うち150GBをLinux用として確保)

HDDは十分な空き容量(50GB程度)を確保しておいてください。
ソースコードとビルドしたバイナリで25GB以上の容量を使います。
また、後述のccacheを使う場合はさらに10〜20GB使います。

ソフトウェア

OS Ubuntu 11.10 64bit
カーネル Linux 3.0.0-16-generic

環境の構築

まずはAndroidをビルドするために必要なアプリケーションやライブラリをインストールします。
基本的にはAOSPのサイトに書いてあるとおりに行いますが、若干手順を変えております。

JDKのインストール

OracleのJDKのサイトより、JDK6をインストールします。記事執筆時のバージョンはJava SE 6 Update 31でしたが、JDK6であれば問題ないと思います。

$ mkdir ~/work
$ cd ~/work
$ wget http://download.oracle.com/otn-pub/java/jdk/6u31-b04/jdk-6u31-linux-x64.bin
$ chmod +x jdk-6u31-linux-x64.bin
$ ./jdk-6u31-linux-x64.bin

以下は.bashrcに記述

export JAVA_HOME=~/work/jdk1.6.0_31
export PATH=$PATH:$JAVA_HOME/bin
export ANDROID_JAVA_HOME=$JAVA_HOME

sourceコマンドで記述内容を反映

$ source ~/.bashrc

ビルドに必要なパッケージのインストール

コンパイラなど必要なパッケージをaptでインストールします。

$ sudo apt-get install git-core gnupg flex bison gperf build-essential zip curl zlib1g-dev libc6-dev lib32ncurses5-dev ia32-libs x11proto-core-dev libx11-dev lib32readline-gplv2-dev lib32z-dev libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown libxml2-utils xsltproc
$ sudo apt-get install libx11-dev:i386

USBの設定

Galaxy Nexus用にUSBの設定をします。

$ sudo vi /etc/udev/rules.d/51-android.rules

51-android.rulesに以下を記述する。OWNERの****の部分は自分のユーザー名を指定

# adb protocol on maguro/toro (Galaxy Nexus)
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6860", MODE="0600", OWNER="****"
# fastboot protocol on maguro/toro (Galaxy Nexus)
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e30", MODE="0600", OWNER="****"

追記後、以下のコマンドを実行する。

$ sudo chmod a+x /etc/udev/rules.d/51-android.rules
$ sudo /etc/init.d/udev restart

Android SDKのインストール

adbやfastboot用に必要です。Androidアプリを開発するわけではないので「Android SDK Platform-tools」のみインストールすればOKです。

$ mkdir -p ~/work/android
$ cd ~/work/android
$ wget http://dl.google.com/android/android-sdk_r16-linux.tgz
$ tar xvf android-sdk_r16-linux.tgz
$ ./android-sdk-linux/tools/android

.bashrcに以下を追記

export ANDROID_SDK_HOME=~/work/android/android-sdk-linux
export PATH=$PATH:$ANDROID_SDK_HOME/tools:$ANDROID_SDK_HOME/platform-tools

sourceコマンドで記述内容を反映

$ source ~/.bashrc

ccacheの設定

ccacheを使うと、コンパイル結果をキャッシュしてくれるので2回目以降のビルド時間を短縮することができます。
Androidはccacheに対応し
sourceコマンドで記述内容を反映

$ source ~/.bashrc

ているため、環境変数を設定すれば自動でキャッシュしてくれます。

.bashrcに以下を記述

export USE_CCACHE=1

ccacheは通常、~/.ccacheにキャッシュを保存します。ホームディレクトリに置きたくないという場合は、以下の設定をすることでキャッシュの保存先を変更できます。

.bashrcに以下を記述。ここでは/work/ccacheにキャッシュディレクトリを設定します。

export CCACHE_DIR=/work/ccache

sourceコマンドで記述内容を反映

$ source ~/.bashrc

ccacheのキャッシュディレクトリのデフォルトサイズは1GBと小さいため変更する必要があります。
AOSPのサイトでは50〜100Gほど設定するといいとあるので、ここでは50GBを設定します。
ccacheコマンドはAndroidのソースコード内に含まれています。ソースコードのダウンロード後に以下を実行してください。具体的な方法は改めて説明します。

Androidのソースコードのディレクトリへ移動
$ prebuilt/linux-x86/ccache/ccache -M 50G

repoのインストール

ソースコードのダウンロードや管理に利用するrepoをダウンロードします。

$ mkdir ~/bin
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod a+x ~/bin/repo

.bashrcに以下を記述

export PATH=$PATH:~/bin

sourceコマンドで記述内容を反映

$ source ~/.bashrc

以上で環境構築は終わりです。おつかれさまでした。