2012年12月23日日曜日

Typescriptでライフゲーム

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

jsdo.itで動くのがこれ。ソースは下


あんまりぽくない気もするけど書きやすかった感がある。
/**
* ライフゲームの世界。ボード
*/
class World{
 interval: number = 10;
 canvas: HTMLCanvasElement;
 ctx: CanvasRenderingContext2D;
 cells: Cell[][];
 constructor(public width:number, public height:number){
  this.canvas = document.createElement("canvas");
  this.canvas.width=width*Cell.cellSize;
  this.canvas.height=height*Cell.cellSize;
  this.ctx = this.canvas.getContext("2d");
  document.body.appendChild(this.canvas);
  this.cells = new Array(width);
  for(var i = 0; i < width; i++){
   this.cells[i] = new Array(height);
   for(var j = 0; j < height; j++){
    var mo = Math.floor( Math.random()*10)%2;
    this.cells[i][j] = new Cell(mo==0);
   }
  }
 }
 timeGoseBy(){
  for(var i = 0; i < this.width; i++){
   for(var j = 0; j < this.height; j++){
    this.cells[i][j].draw(this.ctx, i, j);
   }
  }
  for(var i = 0; i < this.width; i++){
   for(var j = 0; j < this.height; j++){
    this.cells[i][j].nextGeneration(i, j, this);
   }
  }
  this.start();
 } 
 start(){
  var self = this;
  setTimeout(function(){self.timeGoseBy();}, 50);
 }
}
/**
* 細胞
*/
class Cell{
 static cellSize: number = 4;
 public now: bool = false;
 public next: bool = false;
 constructor (next: bool){
  this.next = next;
  this.now = next;
 };
 leftUp(x:number, y:number, world:World){
  if(x-1 < 0){
   x = world.width;
  }
  if(y-1 < 0){
   y = world.height;
  }
  return world.cells[x-1][y-1].now ? 1 : 0;
 }
 left(x:number, y:number, world:World){
  if(x-1 < 0){
   x = world.width;
  }
  return world.cells[x-1][y].now ? 1 : 0;
 }
 leftDown(x:number, y:number, world:World){
  if(x-1 < 0){
   x = world.width;
  }
  if(y+1 >= world.height){
   y = -1;
  }
  return world.cells[x-1][y+1].now ? 1 : 0;
 }
 rightUp(x:number, y:number, world:World){
  if(x+1 >= world.width){
   x = -1;
  }
  if(y-1 < 0){
   y = world.height;
  }
  return world.cells[x+1][y-1].now ? 1 : 0;
 }
 right(x:number, y:number, world:World){
  if(x+1 >= world.width){
   x = -1;
  }
  return world.cells[x+1][y].now ? 1 : 0;
 }
 rightDown(x:number, y:number, world:World){
  if(x+1 >= world.width){
   x = -1;
  }
  if(y+1 >= world.height){
   y = -1;
  }
  return world.cells[x+1][y+1].now ? 1 : 0;
 }
 up(x:number, y:number, world:World){
  if(y-1 < 0){
   y = world.height;
  }
  return world.cells[x][y-1].now ? 1 : 0;
 }
 down(x:number, y:number, world:World){
  if(y+1 >= world.height){
   y = -1;
  }
  return world.cells[x][y+1].now ? 1 : 0;
 }
 nextGeneration(x:number, y:number, world:World){
  var aroundLivingCount = 0;
  aroundLivingCount += this.rightUp(x, y, world);
  aroundLivingCount += this.right(x, y, world);
  aroundLivingCount += this.rightDown(x, y, world);
  aroundLivingCount += this.leftUp(x, y, world);
  aroundLivingCount += this.left(x, y, world);
  aroundLivingCount += this.leftDown(x, y, world);
  aroundLivingCount += this.up(x, y, world);
  aroundLivingCount += this.down(x, y, world);
  switch(aroundLivingCount){
   case 0:
   case 1:
   case 4:
   case 5:
   case 6:
   case 7:
   case 8:
    this.next = false;
    break;
   case 2:
    break;
   case 3:
    this.next = true;
    break;
  }
 }
 draw(ctx: CanvasRenderingContext2D, x: number, y: number){
  this.now = this.next;
  if(this.next){
   ctx.fillStyle = "#000000";
  }
  else{
   ctx.fillStyle = "#ffffff";
  }
  ctx.fillRect(x*Cell.cellSize, y*Cell.cellSize, Cell.cellSize, Cell.cellSize);
 }

}
window.onload=function(){
 var w = new World(100, 100);
 w.timeGoseBy();
}

2012年12月20日木曜日

Bing Translator APIを使ったSublime Text 2の翻訳プラグインが無かったので作った話

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

http://www.flickr.com/photos/89031137@N00/5612615769/


Sublime Text 2 Advent Calendar 2012の12/20(木)担当の@sys1yagiです。最近Sublime Text 2に入門しました。

はじめに


Sublime Text 2をちらーっと触って、へーいいんじゃね、と思ってしばらく放置して
また触って、プラグイン入れて、あーいいじゃん、と思ってしばらく放置して
C言語とか触る事になって「SublimeClang」とか「CTags」とか入れてたら前回エントリっつーかAndroid Advent Calendarネタの「aaptを修正してres配下でディレクトリ階層を構築できるようにして、AOSPにrepo uploadした話」でSublime Text 2が活躍しちゃってすっかり離れられなくなった次第というわけで。
そしてプラグインがpythonで書けるという事で。
pythonは何故か無条件で好きなので、せっかくだしプラグイン書いて見ようかと思ってまず書いてみたのが
Sublime Text 2のステータスバー上で素数を数えるだけの画期的なプラグインを作りました。」です。
まぁそれではちょっと味気ないのでもうちょっと使えそうなの作ろうかと思って、まず

Google翻訳APIかTwitterAPIやな!

と思ってたら
Sublime-Text-2-GoogleTranslate-Plugin
とか
Sublime-Tweet
とか
TweetLine(Sublime Text 2 Advent Calendar 9日目 ST2からTwitterに投稿するプラグイン作った@fukayatsuさん作)
とか

既にあるやんけ!

という事で、絶望していたら

無償の Bing Translation APIを使用する(20110407)
Microsoftの翻訳APIはすごいと聞いたので使ってみた。Rubyで。

とか見つけて

よっしゃBing Translator APIで!


という次第。

どんなプラグイン?


Sublime Text 2上で選択したテキストをBing Translator APIで翻訳して、結果を別ファイルに表示するやつ。
これが

こう


詳細は下部でくやしく。

完成!


はい完成!
SublimeBingTranslator
ソースはそんなに長くないけど全部載せると長いので幾つか使ったAPIとかtips的なのをちょい並べます。

日本語に対応する


翻訳プラグインなので英語→日本語に翻訳したりします。
翻訳結果を表示する為にsublimte text 2が提供しているAPIに日本語の文字列を渡すと以下の様なエラーが出てしまします。
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)
はいはい〜っと
# -*- coding:utf-8 -*-
を追加。
が、ダメ。

最終的にはこれをimport文の後に追加しました。
reload(sys)
sys.setdefaultencoding("utf-8")
これでSublime Text 2の各種APIに日本語が渡せます。

※その代わりにコンソールにprint文やエラーが表示されないので注意
※反映させるにはSublime Text 2を再起動する必要があります。はずす時も。

スレッドを使う


スレッドは普通に使えます。
import threading

settings.thread = threading.Thread(target=self, args=(command, edit, source_text, _from, to,))
settings.thread.setDaemon(True)
settings.thread.start()
targetにメソッド。argsに引数を渡してstartするとスレッドで実行できます。
ここではselfを渡しています。
selfというかオブジェクトを渡した場合は暗黙的に
__call__()メソッドが実行されます。
今回は
__call__(self, command, edit, source_text, _from, to)
というメソッドを実装しました。

スレッドからSublime Text 2 APIを呼び出す


スレッドで処理した後、何らかの結果をSublime Text 2側に返したくなります。
普通にAPIをコールすると怒られるのでsublime.set_timeoutを使ってメインスレッドで実行してもらう様に登録します。
sublime.set_timeout(lambda:self.show_result(edit, source_text, translated), 100)
第一引数にメソッド、第二引数にdelayをミリ秒で指定します。
今回はlambda式で結果表示用メソッドを呼び出しています。

新しいファイルを開き結果を表示する


pluginでというかSublime Text 2で使える結果表示の方法は以下しか選択肢がないっぽいです。

  • クイックパネルに出す
  • ステータスバーに出す
  • ブラウザ等外部アプリケーションを開く
  • 新しいファイルを作成する
  • コード補正の奴で出す
  • アウトプットパネルに出す

  • なんだかなぁと思いつつ、今回はあたらしいfileを開いて結果をinsertする事にしました。
    ファイル操作する場合はTextCommandを使う必要があるっぽいです。
    TextCommandは実行時にEditオブジェクトを渡してくれます。
    これを使ってファイル操作を行います。

    view.insert(edit, 0, result)
    

    viewは新しく作ったファイルのViewオブジェクト。insertメソッドにEditオブジェクトとオフセットと追加する文字列を渡しています。
    先頭に追加する場合はオフセットを常に0にしとけばいいので楽です。

    インストールの仕方


    インストールはpackage_control_channelに登録したので簡単です。
    Sublime Package Controlがインストールされているものとします。

    インストールする


    Cmd+Shift+PでCommand Paletteを開き、"install"を入力


    インストールするプラグインを絞り込む為に"Bing Trasnlator"と入れると出てきます。


    ※インストール後Sublime Text 2の再起動が必要です

    使い方


    Readme読めと言いたい所ですが、簡単なので。

    翻訳設定


    [Preference]-[Package Settings]-[SublimeBingTranslator]-[Settings - Default]
    で翻訳設定をいじれます。
    デフォルトは以下です。
    {
     //Translator Language Codes
     //http://msdn.microsoft.com/en-us/library/hh456380.aspx
        "from" : "en",
        "to" : "ja"
    }
    

    適宜from, toを書き換えて下さい。
    言語コードは以下にあります。
    http://msdn.microsoft.com/en-us/library/hh456380.aspx

    from-to 翻訳する


    cmd + shift + m
    

    選択したテキストを翻訳します。
    from=en
    to=ja
    の場合以下の様になります。

    これが

    こう


    新しいファイルを開き、翻訳前と翻訳後を表示します。
    普通のファイルなので保存してもいいし編集も出来ます。
    このファイルIDを保持しているのでプロセスが生きている間は翻訳結果がこのファイルに追記されていきます。
    なんか翻訳結果が怪しいけど気にしない!

    to-from 翻訳する


    cmd + shift + alt + m
    

    これでto-fromで翻訳出来ます。


    こう


    おわりに


    Sublime Text 2おもろいでー
    あと「Advent Calendarとかまだ自分には早いわー」とか思ってる場合じゃない!
    脊髄反射で飛び込んでこそ面白い体験が待っているのだ。
    とか何とか思った。疲れるけど。

    2012年12月15日土曜日

    aaptを修正してres配下でディレクトリ階層を構築できるようにして、AOSPにrepo uploadした話

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    photo by statuelibrtynps

    Android Advent Calendar 2012 12/15(土)担当の@sys1yagiです。

    どうしてもAOSPにコントリビュートしたいと日々悶々としていましたがどうコントリビュったらいいのか思いつかず今まで放置してきました。やはり最初に「コントリビュートしたい」があるともうコントリビュるのが目的になってしまってダメですね。なので現状自分が開発の中で感じた不便を改善するという方向性で考えてみました。

    はじめに


    resの下が全部フラットなのがすげーめんどいじゃないですか。例えば、layoutとか特定画面で数個使ったりする場合もあるし、そもそも機能単位で分けたいじゃないすか。例えばアプリ内課金の画面フローがあったとしたらそれに関連するレイアウトをディレクトリに切って管理したいじゃないですか。一人でやっててもあのlayout配下に増え続けるファイルを管理するのは大変だってのに、複数人で開発やるってなるともうてんやわんやですね。

    これは僕はAndroidを触り始めた1.5辺りの頃からずっと感じていました。なんでフラットやねん、と。でiPhoneアプリケーションの開発をやる事になってXCodeを触り始めてますます強く感じるようになりました。XCodeではプロジェクト内のリソースを論理的にグループ化できたんですよ!Androidェ・・・

    res配下はビルド時に解析され、res配下の内容を元にR.javaが生成されます。R.javaに生成されたR.layout.mainとかはintの定数なんで、別にもともとファイルが置かれているパスとかどーでもいいはずです。更にバイナリ化されたresファイルはR.javaのIDとマッピングされているはずなので実XMLファイルがどこにあったかなど問題にならんはずなんですよ!



    1.こうしたい


    こうしたい!まとめたいよ!


    現状だとres/layout/settings配下は無視されてR.layoutにsettings_mainは出てこないんですねー

    2.改善するには?


    だれがR.javaをgenerateしているんだろうか!?aaptだよ!
    aaptのソースは以下のパスにいます
    /frameworks/base/tools/aapt

    このaaptがresディレクトリ以下をスキャンする処理を修正すればいいのではないか。


    3.改善する


    改善の要件として「修正によって既存のAndroidアプリケーションのコードに影響を及ぼさない」という事を考えると、res/layout/storeというディレクトリがあったとして、R.javaがR.layout.store.mainという風に書き方が変わってしまうのは避けたいですね。するとres/layout配下はXCodeにおけるグループの様に扱うとよさそうです。

    つまりディレクトリで分かれているけどres/layout配下のファイル名はユニークである必要があるという事。生成されるR.javaもres/layout配下のディレクトリ名は無視し、res/layout/store/main.xmlというものがあったらR.layout.mainを生成します。他のres/drawableなども同じです。


    修正したファイル


    修正したファイルはこれ
    #android-4.2_r1
    /frameworks/base/tools/aapt/Resource.cpp
    
    385行目のcollect_files関数でXMLパースの対象となるファイルの準備をやっています。
    //元のコード
    static void collect_files(const sp<AaptDir>& dir,
            KeyedVector<String8, sp<ResourceTypeSet> >* resources)
    {
        const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
        int N = groups.size();
        for (int i=0; i<N; i++) {
            String8 leafName = groups.keyAt(i);
            const sp<AaptGroup>& group = groups.valueAt(i);
            const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
                    = group->getFiles();
    
            if (files.size() == 0) {
                continue;
            }
    ...
    

    ここにlayout配下のサブディレクトリも対象に含める様に再帰処理を追加しました。

    //追加後
    static void collect_files(const sp<AaptDir>& dir,
            KeyedVector<String8, sp<ResourceTypeSet> >* resources)
    {
        //begin
        const DefaultKeyedVector<String8, sp<AaptDir> >& subDirs = dir->getDirs();
        int n = subDirs.size();
        for(int i = 0; i < n; i++){
            const sp<AaptDir>& subDir = subDirs.valueAt(i);
            collect_files(subDir, resources);
        }
        //end
        const DefaultKeyedVector<String8, sp<AaptGroup> >& groups = dir->getFiles();
        int N = groups.size();
        for (int i=0; i<N; i++) {
            String8 leafName = groups.keyAt(i);
            const sp<AaptGroup>& group = groups.valueAt(i);
            const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files
                    = group->getFiles();
    
            if (files.size() == 0) {
                continue;
            }
    ...
    
    collect_files()が呼ばれる前段でresディレクトリ内を走査してAaptDirなどのツリーを作成しています。
    この段階ではサブディレクトリ内のファイルなどもツリーが作成されていました。
    ところがcollect_files()内ではサブディレクトリの情報は利用せずスルーしていたんですねー。
    幸いにもXMLパースの対象にサブディレクトリを含めるだけで「修正によって既存のAndroidアプリケーションのコードに影響を及ぼさない」という要件は満たせました。

    ※上記コードは一回repo upload後rejectを食らってます。真の修正コードは一番下の方のパッチへのリンクから見ることが出来ます

    4.ビルドして使う


    repo syncしたディレクトリのルートで、以下のコマンドを叩けばaaptだけビルド出来ます。
    make aapt
    
    ビルドが済むと以下の場所にaaptの実行ファイルが吐かれます。
    /out/host/darwin-x86/bin/aapt
    
    darwin-x86はMacでの出力先なのでWindowsやLinuxでは別のパスになるはずですが大体同じです。
    出来上がったaaptをAndroid SDK内のaaptと入れ替えればオッケーです。
    android-sdk-mac_x86/platform-tools/aapt
    あるいは
    adt-bundle-mac/sdk/platform-tools/aapt
    

    こんなプロジェクトをビルドしてみます
    出来た!

    5.修正パッチを送る


    まずはAOSPに入門!
    Android Open Source Project
    この辺のドキュメントって日本語化されてないのかなー。
    実際コントリビュートする人達はある程度英語読めるだろうから無いという事なんだろうか?!

    AOSPに修正パッチを送るにはrepo uploadをする必要があります。というかrepo syncして持って来てたらあとは簡単らしい。ここにパッチを投げる手順が・・・!
    Submitting Patches
    http://source.android.com/source/submit-patches.html

    送ったった・・・!手順に沿えば結構簡単だった。

    aapt : Support sub directory resources


    6.レビューを待つ


    どうなるか全裸待機していたら・・・
    あっーー!


    ち、ちくしょおおおおお!!



    という事で見直し。
    エラーログを見ると
    /framework/base/packages/SystemUI/res/values-sw600dp-land/dimens.xml
    で死んでいるらしい。
    どれどれ


    なんじゃこりゃああああああ!!
    res/values-sw600dp-land
    の下にvalues-sw600dp-landがあるやん。
    でdimens.xmlが入っている。衝突してエラーになったらしい。
    なんでこんな所にこんなディレクトリ切ってんの。なんでそれが採用されてマージされてんねん。

    7.エラーを直す


    "Duplicate file"とメッセージ出ているので、重複を検出してエラーにしている所があるようです。
    そこを探し出して修正。重複があった場合エラー吐いて死んでましたが、warningにする様にしました。
    makeも無事通り、再度repo upload!

    aapt : Support sub directory resources and ignore duplicated files.

    8.レビューを待つ2


    パッチを投げて悶々とする日々。実に10日が過ぎようとしていました。
    パッチが通るにはコードレビューと検証が必要です。コードレビューってパッチがキューに積まれてGoogleの中の人が順次やってるんやろなーとイメージしていたんですがどうも違う様子です。どうも自分でレビュアーを追加するか、誰かがふらりとレビューしてくれる必要があるようです。

    「あ、あかん、放置や・・・」と思ったのでadtの開発についてやりとりをしているadt-devというMLにメールを送らないとなーと思っていた、矢先

    Deckardからメールがキテタ━━━━(゚∀゚)━━━━!!。



    そしてVerifiedにチェックマークついてたーーーー!


    いずれマージされるような気がする・・・!

    9.試したい人


    aapt : Support sub directory resources and ignore duplicated files.
    このパッチを適用してmake aaptし、作成されたaaptを既存のaaptと置き換えればいけます。
    MacでしかやってないけどLinuxはすぐできるでしょう。
    aaptは普通のネイティブプログラムなんでWindowsではあれかなーCygwinでやるのかなーしらねーよ!

    10.終わりに


    修正はたったの数行でしたが、そこにたどり着くまでに相当ソースを読みました。
    ソース読んでログ仕込んでmakeして動かしてまたソース読んでログ仕込んで・・・の繰り返し。
    何度か心が折れかけましたが執拗くmakeを繰り返しておりましたら遂に修正すべき箇所を発見。
    そこからは早かったです。

    「AOSPにコントリビュートしてわいも世界デビューやでぇ!」
    と思ってましたが結構あっけなかったです。
    日々AOSP Gerrit Serverにぶん投げられるパッチの一つに過ぎんな、と。

    せっかくコントリビュったので継続的にちょくちょくrepo uploadしようかなと思います。

    意外と簡単にrepo uploadれるので皆やりなよ!

    でも別に儲からないけどね。

    誰か焼き肉奢って下さい。


    完.



    ※2012/12/17(月) 追記!

    続き


    @vvakameからこんな指摘が!





    ぎ、ぎにゃ〜〜〜!!

    でもありがとおおおお!!

    あが…ん
    adt-devにやはりメールか。

    続き2


    という事でおれたちのたたかいはまだはじまったばかりだぜ。
    多分別エントリになる。



    2012年12月5日水曜日

    Sublime Text 2のステータスバー上で素数を数えるだけの画期的なプラグインを作りました。

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    http://www.flickr.com/photos/whitepebble/1381373166/

    Sublime Text 2が面白いのでプラグインを作ろうと思い立ち、
    API ReferenceとかHow to Create a Sublime Text 2 Pluginとかをチラ見しつつ作成しました。
    凄いです。こんな画期的なプラグインが1日もかからず作れました。

    プラグインの概要


    タイトルにある通り、今回作成した「PrimeCounter」はステータスバーで素数を数えるだけです。
    世界中で使われているSublime Text 2ですのでメッセージはもちろん英語。

    "Let's calm down and count the prime..."
    (素数を数えて落ち着こう・・・)

    このメッセージの後に素数が表示されます!

    このプラグインは以下の様な様々なシーンでご活用頂けます。
  • バグで手が止まった時に
  • 原稿が一文字も進まない時に
  • うっかり素数を忘れてしまった時に
  • 石を叩く音によるモールス信号で自分の正体がバレそうな時に
  • 簡単なSublime Text 2プラグインを勉強したい時に


  • 仕組み


    Sublimte Text 2ではプラグイン開発のベースとなるクラスとして
    EventListener
    ApplicationCommand
    WindowCommand
    TextCommand
    を提供しています。
    今回はWindowCommandを使いました。機能的には別にWindowCommandじゃなくても良かった気がします。

    ファイル構成はこうです


    これをSublimte Text 2のパッケージ置き場に適当にディレクトリ作って放り込めば動くんですから凄いですね。

    キーバインド


    素数のカウントを開始する為のキーバインドを定義しました。
    キーバインドは以下のファイルにjson形式で記述する事で実現出来ます。
    各プラットフォーム毎に分かれてます
    Default (Linux).sublime-keymap
    Default (OSX).sublime-keymap
    Default (Windows).sublime-keymap

    中身はこんな感じです
    [
     { "keys": ["ctrl+shift+p"], "command": "prime" }
    ]
    

    ctrl+shift+pで素数のカウントをスタート・ストップ出来ます。

    実装


    実装は以下の通り。素数計算は適当です。
    重要なのはrunメソッドとdescriptionメソッド。これはWindowCommandクラスのメソッドをオーバーライドしています。
    sublime.status_messageメソッドでステータスバーに文字列を表示させています。
    sublime.set_timeoutメソッドはjavascriptのsetTimeoutと同じで、指定した関数を指定ミリ秒後に実行します。lambda式で生成したメソッドでincrementメソッドを呼び出してます。これによって1秒毎に素数を数え上げていっています。
    今回は利用していないですがスレッドも使えるし、もちろん通信も出来るのでなんでもありですね。
    ファイルアクセス系は試してませんが、どこでもアクセス出来るとなるとセキュリティ怖いですね。
    そもそもEventListenerで色々取れるし、というか開いている文書の情報には全アクセス出来る気がするので色々お漏らし出来そうな悪寒はありますね。怖い怖い。

    # -*- coding: utf-8 -*-
    import sublime, sublime_plugin
    import thread
    
    #Prime Counter
    isStart=False
    class PrimeCommand(sublime_plugin.WindowCommand):
     prime = 1
     def run(self):
      global isStart
      if isStart:
       isStart = False;
       self.prime = 1
      else:
       isStart = True
       self.increment()
     def description(self, args):
      return "prime counter."
     def increment(self):
      if isStart == True:
       self.prime = self.next_prime(self.prime)
       sublime.status_message("Let's calm down and count the prime..."+str(self.prime))
       sublime.set_timeout(lambda:self.increment(), 1000)
     def next_prime(self, now):
      p = now
      while True:
       is_prime = True
       p = p+1
       div_max = p/2
       if div_max < 2:
        return p
       for i in range(2, div_max+1):
        if p%i == 0:
         is_prime = False
         break
       if is_prime==True:
        return p
    


    インストールの仕方


    PrimeCounterはgithubで公開しています。Sublime Text 2にインストールするにはまずリポジトリを追加します。(Sublime Package Controlがインストールされている事とします)

    command+shift+pでCommand Paletteを開き、"add repository"と入れると出てきます。


    画面下部にリポジトリ追加のエディットが出てくるので
    https://github.com/sys1yagi/PrimeCounter
    を入力してEnter


    その後またCommand Paletteを開き、次は"install"を入力


    インストールするプラグインを絞り込む為に"PrimeCounter"と入れると出てきます。


    Enterを押せばPrimeCounterがインストールされます。

    実行!


    ctrl+shift+pで実行!
    素数がカウントされていきます・・・!


    終わりに


    素数計算部分がしょぼいのでpull request待ってます。
    あとこれ並列計算に何か使えないかなーとか。おもた

    2012年12月1日土曜日

    EclipseでNDK(ndk-build)を使う為の設定を自動生成するスクリプト for Mac

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    Setting up Automatic NDK Builds in Eclipse
    を参考に、EclipseでNDKを利用する設定を行なってみたわけですが

    毎回手動でBuildersを追加するのめんどくねー!?


    と思ったので自動化しました。

    スクリプト


    作ったのが以下のスクリプト
    xpathが入っていれば普通に動くはず



    環境


    adt-bundle-mac
    android-ndk-r8c

    使い方


    ANDROID_NDK_ROOTにパスを通す


    ndk導入時にANDROID_NDK_ROOTにパスを通しているとは思いますが念のため。
    ついでにPATHにも通しておくとよりよい

    ANDROID_NDK_ROOTの位置にndk-gen-externalToolBuildersを置く


    ANDROID_NDK_ROOTにはndk-buildが居るはず。
    同じ所に上のgistのコードを置く。
    ファイル名は適当でいいけどここでは"ndk-gen-externalToolBuilders"
    chmod +xで実行権限の付与を忘れずに



    EclipseでAndroid Projectを作る


    ndk-gen-externalToolBuildersはEclipseのプロジェクトファイルである.projectの内容に依存するのでまずEclipseプロジェクトを作ります。
    NDKなので試しにsampleプロジェクトをimportしてみます。





    .projectが出来ます





    プロジェクトのディレクトリでndk-gen-externalToolBuildersを実行する


    で、ターミナルからプロジェクトの所に行って、ndk-gen-externalToolBuildersを実行
    その後Eclipse側のプロジェクトをRefreshしての設定からBuildersを見ると・・・

    (゚д゚)ウマー




    キタ━━━━(゚∀゚)━━━━!!
    ndk-buildが追加されています。
    後は普通にビルドして実行すると・・・



    動いた・・・!


    仕組み


    めんどくさいので適当に。
    Eclipseのプロジェクトファイルである.projectにBuildersを追加してます。
    Buildersの定義ファイルを.externalToolBuilders/ndk-build.launchに吐いてます。
    ndk-build.launchを生成するのに.project内の情報が必要なのでxpath使ってます。
    以上

    2012年11月13日火曜日

    Ubuntu 12.04.1 LTSにNode.js環境を作る

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    はじめに


    朝、早く起きれたので朝練という事でNode.jsの環境をUbuntu 12.04.1 LTSに作成してみた。
    まぁGithub
    Installing Node.js via package manager
    https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager
    ここの手順に沿えばいいだけですが。

    Node.jsをインストールする


    sudo apt-get install python-software-properties
    sudo add-apt-repository ppa:chris-lea/node.js
    sudo apt-get update
    sudo apt-get install nodejs npm
    

    Node.jsはアドオンをC++で追加出来るそうで。
    アドオンを作りたい場合は以下のコマンドで環境をインストール出来るそうな
    sudo apt-get install nodejs-dev
    

    Node.jsを動かす


    インストールするとnodeコマンドを使える様になります。
    サーバ用のjsを書いて

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

    node hello.js
    

    で動き出す!

    終わり


    結構簡単に環境構築出来た。
    あとは以下のドキュメントなんかを見ながら好きに作る感じで

    Nodeビギナーズブック
    http://www.nodebeginner.org/index-jp.html

    Node.js v0.8.14 Manual & Documentation
    http://nodejs.org/api/

    2012年10月19日金曜日

    Google Playの某カテゴリで「青空文庫ビューア」が有料人気4位になったわけだが収益を赤裸々に綴ってみる

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    有料人気4位!


    ある日何気なくGoogle Playを見ていたんですよ。久しぶりに。
    で、まぁ自分もアプリを出しているわけですから自分のアプリをチラ見するわけですよ。
    んでカテゴリ辿って行くとファーストビューに飛び込んでキタ━━━━(゚∀゚)━━━━!!

    人気(有料) 4位


    うっひょー億万長者じゃねーの!?
    さっすがだぜ〜。

    はて


    しかし



    はたらけど はたらけどなお わがくらし らくにならざり じっとてをみる
    by. 石川啄木


    おっかしーなーーー

    どんなアプリ?



    青空文庫ビューア」は青空文庫で公開されている著作権の切れた文学作品をAndroidで閲覧する為のアプリです。青空文庫で公開されている作品をダウンロードし縦書きで表示したり明朝フォント入れたり栞挟んだりページめくったりなどの基本的な機能を提供しています。もともとは自分用に作りました。太宰治読みたかったのよ。
    作ったのは2009年7月。まだまだAndroidが始まったばかりの頃。エミュやHTC-Magicでセコセコ開発する日々。その後一旦放置時期を挟んで2011年の初旬に刷新、今に至る感じです。

    マネタイズポイント


    マネタイズなどは最初は特に意識してませんでしたが、AdMobとかあれこれ入れてみるかー程度の気持ちで適当に入れ、ついでに有料版も出してみるかーと、全ては「これも経験よぉ」というノリでやってみました。これらを導入したのも2011年の1月辺りのリリースです。

    もともと「俺用」みたいな所があるので「どうせだし皆使ってくれ!」という事でリリースしていて、あんまりマネタイズポイントを考えていませんでした。なのでとりあえず適当に「広告表示を消すオプション」を有料とする事にしました。機能追加を有料化する事も考えられますが、「どうせだし皆使ってくれ!」が根本にあるので皆が使えない機能入れてどうすんねんみたいな感じで今後も機能充実は無料版、有料版隔てなくやっていく予定です。

    売れた数は?


    マネタイズポイントは適当で戦略もなく適当ですけどランキング入ったんだからさぞかし売れたわけやん?みたいな?


    いいぜ、てめえがGoogle Playで有料(人気)の上位にランクインしたら儲かると思ってるなら、まずはそのふざけた幻想をぶち殺す。

    まぁアプリのページ行けば分かるんですけど、ダウンロード数が1000〜5000なんですよ。



    で値段は105円



    という事はどういう事か。そういう事ですね。


    収益は?


    まぁ1000〜5000DLで105円ですよ。しかも30%持ってかれるんですね。
    まぁぶっちゃけ2,486DLですよ。今日の時点で。

    つまり
    2486*105*0.7=182721円

    あれですよ、一日じゃないですよ。
    あれですよ、一週間じゃないですよ。
    あれですよ、一ヶ月じゃないですよ。
    あれですよ、全期間ですよ。
    2年近くでですよ。
    総売上ですよ。


    まとめ


    Androidアプリは儲からないのか?
    僕のこの実績においては正にそうですが、実際はそうでもないなと思っています。
    あんまり言うとアレなんで言わないけど色々やれる事はあります。
    色々分析のアレがあったが書くとアレだしもっと後で書こうと思ったので書かない。
    今は「あーランキング入ってもこんなもんなんだー」と思っていてもらえるといいんじゃないでしょうか。マジで。

    とりあえず儲かってないので誰か焼肉奢って下さい
    あと無料版でいいんで皆使ってね!→無料版 #ステマ



    2012年8月30日木曜日

    Haskellで三項演算子のような物を定義する

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    こう書きたい


    if-then-elseはなんだか嫌だなぁと。でもガードじゃなくてワンライナーで書きたいなぁと。
    出来るのかもしれないが、知らないので。教えて・・・。

    >[x>5?1:2 | x <- [1..10]]
    >[2,2,2,2,2,1,1,1,1,1]
    

    無理だ


    そもそも":"はリストを生成する為の演算子なので上のような書き方はシンタックスエラーで死ぬ。
    "1:2"は"1:2:[]"と書け!と怒られる。
    なので諦める。":"は諦める。

    似非三項演算子


    結果こうなった。
    x ? (a, b) = if x then a else b
    
    if-thenで返す値とelseで返す値をペアで取る事にした。
    使う時はこう
    >[(x>5)?(1,2) | x <- [1..10]]
    >[2,2,2,2,2,1,1,1,1,1]
    

    やったぜ!

    2012年8月23日木曜日

    [java] クイックソート 効率はよくない

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    クイックソート。

    [Java] ヒープソート 遅い

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    Javaでヒープソート。
    もう見なくてもヒープソートも実装できる様になったわー。
    この実装は再起を使っているので遅いらしい。
    次はHaskellでやる。

    2012年8月17日金曜日

    [Haskell] FizzBuzzをやる

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    メモ。すぐ忘れそうなので。
    cycleが名前長いのが気に入らない。
    take 100でリスト取り出してるだけなんで要件満たしてない気もする。
    bとcが空かどうかを評価する部分が気に入らない
    zip3使うのが何か負けた気がする
    無限リスト楽しい。

    take 100 [if b=="" && c=="" then show a else concat [b,c]|(a,b,c)<-(zip3 (cycle [1..]) (cycle["","","Fizz"]) (cycle ["","","","","Buzz"]))]
    

    2012年7月26日木曜日

    OS XのNetBeansの使用メモリ設定

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    はまったのでメモ

    netbeans.confの場所


    NetBeansの起動オプションはnetbeans.confに書くらしい、のは分かったがどこにあるのかわからない!
    Linux系だと「/etc/netbeans.conf」にあるらしいのですがOS Xはそうはいかない。

    ここだー
    /Applications/NetBeans/NetBeans\ 7.1.app/Contents/Resources/NetBeans/etc/netbeans.conf
    

    netbeans.confの設定


    netbeans_default_optionsに-J-Xmxオプションを追加するだけでよさげ

    netbeans_default_options="-J-client -J-Xss2m -J-Xms32m -J-Xmx1024m -J-XX:PermSize=128m -J-Dapple.laf.useScreenMenuBar=true -J-Dapple.awt.graphics.UseQuartz=true -J-Dsun.java2d.noddraw=true -J-Dsun.zip.disableMemoryMapping=true"
    

    あとは再起動。
    やったー

    2012年7月18日水曜日

    CentOS6.2でMySQL5.5.24をソースからビルドする

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク


    CentOS release 6.2 (Final)とかいうのがMySQL5.1.61だったので、5.5.24にしたろか、と思って。
    適宜sudoするところはsudoで。

    環境


    OS: CentOS release 6.2 (Final)
    MySQL: 5.1.61
    カーネル:2.6.32-220.13.1.el6.x86_64(これいる?)

    事前準備


    ビルドするのでツールを準備する必要がある。

    make
    cmake
    ncurses-devel
    bison
    gccの方々

    sudo yum install make
    sudo yum install cmake
    sudo yum install ncurses-devel
    sudo yum install bison
    sudo yum install gcc*
    

    MySQL5.5.24のソースをダウンロードする


    MySQL5.5.24のソースは多分以下にあるのでwget。
    なんとなく/usr/local/srcに落とす。

    cd /usr/local/src
    wget http://downloads.mysql.com/archives/mysql-5.5/mysql-5.5.24.zip
    unzip mysql-5.5.24.zip
    

    cmakeする


    解凍したmysqlのディレクトリ内に移動して、cmake。
    色々オプションがあるようだが知らん。
    ここに書かれているらしい。
    http://dev.mysql.com/doc/mysql-sourcebuild-excerpt/5.5/en/source-configuration-options.html
    今回はインストール場所の指定と、デフォルトのcharsetをutf8にしてビルド。

    cd mysql-5.5.24
    cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql5.5.24 -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci
    

    makeする


    cmakeで特にwarningやerrorが出ていなければ

    make
    


    インストールする


    エラーとかなければ
    make install
    


    設定する


    cmakeの-DCMAKE_INSTALL_PREFIXで指定した場所にMySQL5.5.24がインストールされます。
    /etc/init.d/mysqldを書き換えて5.5.24を起動する様にします。

    sudo vim /etc/init.d/mysqld
    

    19行目辺りのexecを書き換える
    exec="/usr/bin/mysqld_safe"
    ↓
    exec="/usr/local/mysql5.5.24/bin/mysqld_safe"
    
    大体こんな感じでいいんじゃまいか。
    あとは45行目辺りの
    get_mysql_option mysqld datadir "/var/lib/mysql"
    
    も書き換えていいけど5.1.61のデータをそのまま使えるっぽのでそのままでもいい気がする。

    102行目のbasedirを書き換える
    $exec   --datadir="$datadir" --socket="$socketfile" \
                    --pid-file="$mypidfile" \
                    --basedir=/usr --user=mysql >/dev/null 2>&1 &
    ↓
    $exec   --datadir="$datadir" --socket="$socketfile" \
                    --pid-file="$mypidfile" \
                    --basedir=/usr/local/mysql5.5.24 --user=mysql >/dev/null 2>&1 &
    

    これで多分おk。
    /etc/my.cnfも大分フォーマットが変わっているけどデフォのままなら動く。

    動いた


    sudo service mysqld start
    

    で動く。わーい

    2012年2月23日木曜日

    [Android] アクションバーを画面下側から表示させてみました。アプリ編

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    @adakodaさんが面白い事をしていらっしゃったのでインスパイア。
    [Android] アクションバーを画面下側から表示させてみました
    http://www.adakoda.com/adakoda/2012/02/android-47.html

    アプリからでも出来るのでは?


    アプリケーションではアプリを初期化する際にsetContentView()するんで、setContentView()で出来たView群が画面上の全てだと思いがちです。しかーし画面上に存在する以上タイトルバーやActionBarなども当然Viewの一種なわけです。ちゅーことはアプリケーションのトップレベルのViewを取れたらタイトルバーやActionBarも操作出来るのでは無いでしょうか!?

    そもそもまずトップレベルのViewをアプリケーション側から取得する事が出来るのか。出来ます。ActicvityからWindowを取り出してgetDecorView()するだけ。
    View root = activity.getWindow().getDecorView()
    

    ではこのViewどういう構造になっているか。Viewツリーをダンプしてみましょう。ViewツリーのダンプはViewの階層構造をダンプするスニペットを参考に。
    View v = getWindow().getDecorView();
    Util.dumpViewTree(v, "");
    

    吐かれたのがこれ。Android4.0.2のGalaxy Nexusで、プロジェクトはEclipse+ADTで生成して出来たものを全く触らずに利用。ターゲットは4.0です。

    Util  D  com.android.internal.policy.impl.PhoneWindow$DecorView
    Util  D   android.widget.LinearLayout
    Util  D    com.android.internal.widget.ActionBarContainer
    Util  D     com.android.internal.widget.ActionBarView
    Util  D      android.widget.LinearLayout
    Util  D       android.widget.ImageView
    Util  D       android.widget.LinearLayout
    Util  D        android.widget.TextView
    Util  D        android.widget.TextView
    Util  D      com.android.internal.widget.ActionBarView$HomeView
    Util  D       android.widget.ImageView
    Util  D       android.widget.ImageView
    Util  D     com.android.internal.widget.ActionBarContextView
    Util  D    android.widget.FrameLayout
    Util  D     android.widget.LinearLayout
    Util  D      android.widget.TextView
    Util  D    com.android.internal.widget.ActionBarContainer
    

    "com.android.internal.widget.ActionBarContainer"てのが居ますね。露骨ですね。なんで二個あるのかはちょっとわかりません。

    findViewsWithClass()を拡張


    じゃあ"com.android.internal.widget.ActionBarContainer"を取り出して操作したらいいんじゃまいか?どうやって取り出すの。ああ、何かそういうエントリを昨日見たよ。あった。List<T> findViewsWithClass(View v, Class<T>)。助かるわー。#ステマ

    しかしどうもList<T> findViewsWithClass(View v, Class<T>)ではClass<T>を使っています。com.android.internal.widget.ActionBarContainerは名前しか判らないのでちょっと拡張する必要がありそうですね。

    こうなった
    public static <T extends View> List<T> findViewsWithClass(View v, Class<T> clazz) {
      List<T> views = new ArrayList<T>();
      findViewsWithClass(v, clazz.getName(), views);
      return views;
     }
     public static List<View> findViewsWithClassName(View v, String className) {
      List<View> views = new ArrayList<View>();
      findViewsWithClass(v, className, views);
      return views;
     }
     private static <T extends View> void findViewsWithClass(View v, String clazz, List<T> views) {
      if (v.getClass().getName().equals(clazz)) {
       views.add((T) v);
      }
      if (v instanceof ViewGroup) {
       ViewGroup g = (ViewGroup) v;
       for (int i = 0; i < g.getChildCount(); i++) {
        findViewsWithClass(g.getChildAt(i), clazz, views);
       }
      }
     }
    

    これでcom.android.internal.widget.ActionBarContainerを簡単に取り出せます。

    View root = activity.getWindow().getDecorView();
    List<View> views = Util.findViewsWithClassName(root, "com.android.internal.widget.ActionBarContainer");
    



    試しにsetVisibility(View.GONE)してみる


    では試しに取り出したcom.android.internal.widget.ActionBarContainerをView.GONEで消してみましょう。

    View root = activity.getWindow().getDecorView();
    List<View>l views = Util.findViewsWithClassName(root, "com.android.internal.widget.ActionBarContainer");
    for(View v : views){
      v.setVisibility(View.GONE);
    }
    

    消えたー!操作出来る。当たり前だけど。


    actionBarUpsideDown(Activity activity)


    Viewツリーのダンプを見ると、事実上ルートのコンテナはcom.android.internal.policy.impl.PhoneWindow$DecorView直下のLinearLayout。多分Vertical。つまり、この人にいるcom.android.internal.widget.ActionBarContainerをremoveView()で消して、addView()で足すとケツに移動するのではないか!?
    という事でメソッドを作成。

    public final static void actionBarUpsideDown(Activity activity) {
      View root = activity.getWindow().getDecorView();
      View firstChild = ((ViewGroup) root).getChildAt(0);
      if (firstChild instanceof ViewGroup) {
       ViewGroup viewGroup = (ViewGroup) firstChild;
       List<View> views = Util.findViewsWithClassName(root, "com.android.internal.widget.ActionBarContainer");
       if (!views.isEmpty()) {
        for (View vv : views) {
         viewGroup.removeView(vv);
        }
        for (View vv : views) {
         viewGroup.addView(vv);
        }
       }
      }
      else{
       Log.e(TAG, "first child is not ViewGroup.");
      }
     }
    

    試す


    メソッドにしたので試すのは簡単ですね

    setContentView(R.layout.main);
    Util.actionBarUpsideDown(this); 
    

    キタワァ.*・゜゚・*:.。..。.:*・゜(n‘∀‘)η゚・*:.。. .。.:*・゜゚・*!!!!!☆



    まとめ


    ICS以外で呼ぶとどうなるか試してないので適宜何とかして下さい。
    ActionBarが上に居るのは個人的に良く思っていないので、これ使って下に表示させる様にしていきたい。



    Viewの階層構造をダンプするスニペット

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    まぁViewっていうかViewGroupですけど。

    コード


    こうです。あんまり階層が深いとスタックオーバーフローで死ぬと思います。
    public static void dumpViewTree(View v, String padding){
      Log.d(TAG, padding + v.getClass().getName());
      if(v instanceof ViewGroup){
       ViewGroup g = (ViewGroup)v;
       for(int i = 0; i < g.getChildCount(); i++){
        dumpViewTree(g.getChildAt(i), padding+" ");
       }
      }
     }
    

    実行

    試しにandroid.support.v4.app.ListFragmentのonCreateView()で自動的に生成されるViewをダンプしてみました。 こういう構造してたんですねー。
    27152 Util  D  android.widget.FrameLayout
     27152 Util  D   android.widget.LinearLayout
     27152 Util  D    android.widget.ProgressBar
     27152 Util  D   android.widget.FrameLayout
     27152 Util  D    android.widget.TextView
     27152 Util  D    android.widget.ListView
    

    まとめ


    デバッグとか動的にView書き換えてる場合とかAPIが勝手に生成するViewについて調べるのに使えるのではないでしょうかー。





    2012年2月22日水曜日

    List<T> findViewsWithClass(View v, Class<T>)

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    android.view.ViewといえばfindViewById(int)ですよねー。
    実はそれ以外にもfindViewWithTag (Object tag)やfindViewsWithText(ArrayList, CharSequence, int)てのがあるようです。
    使い所がよくわかりませんがなんか使えそうですね。

    個人的にはViewのクラス名で取り出したいなーと、例えばjavascriptのgetElementsByTagName(name)のノリだと言うと判ると思います。

    List<T> findViewsWithClass(View v, Class<T>)


    はい、完成。本当は v instanceof Tってやりたかったけど怒られました。仕方なくClassのフルネームで比較してます。なのでサブクラスとかまでは検出できません。

    public static <T extends View> List<T> findViewsWithClass(View v, Class<T> clazz){
      List<T> views = new ArrayList<T>();
      findViewsWithClass(v, clazz, views);
      return views;
     }
     
     private static <T extends View> void findViewsWithClass(View v, Class<T> clazz, List<T> views){
      if(v.getClass().getName().equals(clazz.getName())){
       views.add((T)v);
      }
      if(v instanceof ViewGroup){
       ViewGroup g = (ViewGroup)v;
       for(int i = 0; i < g.getChildCount(); i++){
        findViewsWithClass(g.getChildAt(i), clazz, views);
       }
      }
     }
    

    ※追記20120223
    @zaki50さんからツッコミが。ありがとうございます!!



    直した!。これの場合だとサブクラスまでがっつり取れる。

     private static <T extends View> void findViewsWithClass(View v, Class<T> clazz, List<T> views) {
      if (clazz.isAssignableFrom(v.getClass())){
       views.add(clazz.cast(v));
      }
      if (v instanceof ViewGroup) {
       ViewGroup g = (ViewGroup) v;
       for (int i = 0; i < g.getChildCount(); i++) {
        findViewsWithClass(g.getChildAt(i), clazz, views);
       }
      }
     }
    

    使う


    こう使うとよさげ。例えばこういうレイアウト

    <?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="fill_parent"
        android:orientation="vertical" >
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="mogeee" >
        </TextView>
        <ImageView
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_launcher" >
        </ImageView>
        <LinearLayout
            android:id="@+id/main_list"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
        </LinearLayout>
    </LinearLayout>
    

    こういうコードで要素を取り出せます。以下はTextViewを取り出してます。

    List<TextView> views = findViewsWithClass(v, TextView.class);
            if(views.isEmpty()){
             Log.d(TAG, "not found");
            }
            else{
             Log.d(TAG, "find:"+views.size());
            }
    

    2012年2月20日月曜日

    サポートパッケージのFragmentでstartActivityForResultを使う場合の注意点

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    ついにFragmentに入門したわけですが、早速ハマったのでメモしておきます。サポートパッケージェ・・・。
    ※本エントリはandroid.support.v4.app.Fragmentに関する動作について書いています。android.app.Fragmentでは以下に書いている問題は発生しません。

    FragmentでstartActivityForResultが使える


    Fragmentって便利ですねー。特にActivityにベッタリ書いていた様な実装をFragmentに分けて色々捗らせる事が出来る点がとても助かります。で、Activityに書いていた処理をFragmentに集約する時に問題になるのがstartActivityForResultによって処理を委譲している部分です。

    FragmentではstartActivityやstartActivityForResultやonActivityResultが用意されており、FragmentからActivityを起動したり、結果を受け取る事ができます。

    という事でまぁ問題なくねーと思いがちですがしかし、FragmentのstartActivityForResultには罠があるんですねーーーー!


    リクエストコードは下位16bitの範囲で


    まず以下のコードをFragment内で実行してみます。なんの変哲もない、アドレス帳を起動して選択結果を受け取るだけのIntentです。

    private final static int REQUEST_CODE = 0x11111;
    public void pick() {
     Intent intent = new Intent(Intent.ACTION_PICK, 
      ContactsContract.Contacts.CONTENT_URI);
     startActivityForResult(intent, REQUEST_CODE);
    }
    

    し、死んだー!

    Can only use lower 16 bits for requestCode


    なにーー!
    そうですFragmentではstartActivityForResultの第二引数に与えるリクエストコードを下位16bit以内に収める必要があります。
    つまり0x0000〜0xffffまでの間の値にしろ、という事ですね。上記コードは"0x11111"だったのでダメだったんですね。

    リクエストコードは下位16bitの範囲で。



    onActivityResultが呼ばれないパターン


    こういう罠にはまるのは僕くらいのものな気がしますが、サポートパッケージのFragmentのstartActivityForResultではonActivityResultが呼ばれないパターンがあります。
    親となるActivity側がonActivityResultをオーバーライドしている場合に起きるのですが・・・。
    コードを見てみます。問題無いようなあるような・・・。

    親となるActivity側
    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data){
      switch (requestCode) {
      case REQUEST_CODE:
       //なにもしない
       break;
      case MOGE:
       //~処理~
       break;
      }
     }
    
    Fragment側
    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
      switch (requestCode) {
      case REQUEST_CODE:
       if(resultCode == Activity.RESULT_OK){
        Log.d(TAG, "okkkk!");
       }
       else{
        Log.d(TAG, "ooooops!");
       }
       break;
      }
     }
    

    このコードの場合Fragment側のonActivityResultが呼ばれません。何故か?

    super.onActivityResultを忘れているから!


    そうです。親Activity側のsuper.onActivityResultを呼んでませんでした。
    サポートパッケージでFragmentを使う場合はActivityではなくFragmentActivityを継承する必要があります。
    このFragmentActivityのonActivityResultでFragmentに結果を渡しているのでsuper.onActivityResultを呼んであげないとFragment側にonActivityResultの呼び出しが行かないんですねー。

    正しくは
    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data){
      super.onActivityResult(requestCode, resultCode, data);
      switch (requestCode) {
      case REQUEST_CODE:
       //なにもしない
       break;
      case MOGE:
       //~処理~
       break;
      }
     }
    

    問答無用でsuper.onActivityResult。



    まとめ


    コンパーチビリティの為のサポートパッケージですがなかなかどうして、大変ですねー。


    2012年2月11日土曜日

    【Android】スムーズな開閉アニメーションを実現するExpandAnimatorというライブラリを作りました

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    デモ


    まずは動作を。




    この様に指定したViewの開閉を行う為のアニメーションライブラリを作りました。


    ダウンロード


    Androidのライブラリプロジェクトをgithubに公開しています。git cloneして下さい。

    ExpandAnimator
    https://github.com/sys1yagi/ExpandAnimator

    構成は以下の通りです。


    動画のデモはsampleの中に入っているサンプルプロジェクトを動作させたものです。


    サポートバージョン


    1.6〜


    使い方


    ExpandAnimatorの使い方は簡単です。

    ライブラリプロジェクトを参照する


    まずExpandAnimatorをライブラリプロジェクトとして参照します。



    トリガとコンテナをlayoutXMLに定義する


    開閉する対象となるViewと、開閉のトリガとなるボタンなどをlayoutXMLに書いておきます。
    ここではボタンがトリガで色のついたLinearLayout部分がコンテナです。


    <?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:orientation="vertical" >
    
        <Button
            android:id="@+id/trigger"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="5" />
    
        <LinearLayout
            android:id="@+id/container"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="10dp" >
    
            <View
                android:layout_width="fill_parent"
                android:layout_height="300dp"
                android:background="#ffccddff" >
            </View>
        </LinearLayout>
    
    </LinearLayout>
    


    ExpandAnimatorを使う


    開閉アニメーションを行う為にExpandAnimatorクラスを利用します。
    ExpandAnimatorは開閉対象となるViewを内部に持ってアニメーション制御を行います。

    下記コードの20行目でExpandAnimatorをnewしています。引数に開閉対象となるViewを渡しています。
    第二引数は開閉アニメーション時に呼び出されるリスナを指定します。

    41行目でアニメーションの時間を指定しています。
    42行目ではアニメーションの為のインタポレータをセットしています。DecelerateInterpolatorはだんだんアニメーションのスピードが減速していくインタポレータです。

    43行目でトリガとなるボタンにOnClickListnerをセットし、onClick(View)でExpandAnimatorのexpand(), unexpand()をそれぞれ呼び出しています。

    package test.test.test.test.test;
    
    import jp.dip.sys1.yagi.android.expandanimator.ExpandAnimator;
    import jp.dip.sys1.yagi.android.expandanimator.ExpandAnimator.OnAnimationListener;
    import android.app.Activity;
    import android.os.Bundle;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.view.animation.DecelerateInterpolator;
    import android.widget.Button;
    
    public class ExpandSampleActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            final Button trigger = (Button) findViewById(R.id.trigger);
            //ExpandAnimatorを初期化する
            final ExpandAnimator animator = new ExpandAnimator(findViewById(R.id.container), new OnAnimationListener() {
                @Override
                public void onUnexpanded(ExpandAnimator e) {
                    trigger.setText("onUnexpanded");
                }
    
                @Override
                public void onStartUnexpand(ExpandAnimator e) {
                    trigger.setText("onStartUnexpand");
                }
    
                @Override
                public void onStartExpand(ExpandAnimator e) {
                    trigger.setText("onStartExpand");
                }
    
                @Override
                public void onExpanded(ExpandAnimator e) {
                    trigger.setText("onExpanded");
                }
            });
            //アニメーション時間を指定
            animator.setDuration(1000);
            //インタポレータを指定
            animator.setInterpolator(new DecelerateInterpolator());
            
            //トリガを設定
            trigger.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (animator.isExpand()) {
                        //閉じる
                        animator.unexpand();
                    } else {
                        //開く
                        animator.expand();
                    }
                }
            });
        }
    }
    


    出来た


    こ、こいつ動くぞ・・・!



    使いどころ


    わかりません。


    ライセンス


    Apache License 2.0です。


    2012年1月31日火曜日

    android.app.Dialogを任意のサイズに設定する。 FILL_PARENT, WRAP_CONTENTもできるよ

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    さんざんハマったので。

    こうだ


    こうです。
    dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);

    android.app.DialogをnewしてレイアウトぶちこんだあとgetWindow()してsetLayoutでサイズ指定する感じです。
    この時FILL_PARENT, WRAP_CONTENTなども使えます。

    import android.widget.LinearLayout.LayoutParams;
    
    Dialog dialog = new Dialog(context);
    dialog.setContentView(R.layout.hoge);
    dialog.setTitle(R.string.moge);
    dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
    
    



    Dialogの横だけFILL_PARENTにする


    まぁこうですね。

    import android.widget.LinearLayout.LayoutParams;
    
    Dialog dialog = new Dialog(context);
    dialog.setContentView(R.layout.hoge);
    dialog.setTitle(R.string.moge);
    dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
    
    



    Dialogを300x300にしてみる


    まぁこうですね。

    import android.widget.LinearLayout.LayoutParams;
    
    Dialog dialog = new Dialog(context);
    dialog.setContentView(R.layout.hoge);
    dialog.setTitle(R.string.moge);
    dialog.getWindow().setLayout(300, 300);
    
    



    使ったレイアウトXML


    は、これ。android:textにリテラルぶっこむとLint先生に怒られるので良い子の皆様は必ずvaluesで定義してstringを下さい。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:padding="10dp" >
        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:text="(さよならだ)"
            android:textColor="#FFF" />
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal" >
            <Button 
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="OK"
            />    
        </LinearLayout>
    </LinearLayout>
    



    2012年1月6日金曜日

    Javascriptによるドラゴン曲線。辰年だし。

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

    辰年だしドラゴン曲線だよね。という事でjavascriptで。Canvasで。

    新年あけましてドラゴン曲線 - jsdo.it - share JavaScript, HTML5 and CSS


    ソース

    <html>
    <script>
    window.onload =function(){
     var $ = function(id){ return document.getElementById(id); }
     var canvas = $("canvas");
     var ctx = canvas.getContext('2d');
     var clear =function(){
      ctx.fillStyle = 'rgb(200, 200, 200)';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
     }
     var line = function(ctx, sx, sy, ex, ey){
      ctx.beginPath();
      ctx.moveTo(sx, sy);
      ctx.lineTo(ex, ey);
      ctx.closePath();
      ctx.stroke();
     }
     var dline = function(ctx, sx, sy, ex, ey, cx, cy){
      ctx.beginPath();
      ctx.moveTo(sx, sy);
      ctx.lineTo(cx, cy);
      ctx.moveTo(ex, ey);
      ctx.lineTo(cx, cy);
      ctx.closePath();
      ctx.stroke();
     };
     var dragon = function(ctx, depth, sx, sy, ex, ey, d){
      if(depth ==0){
       line(ctx,sx,sy,ex,ey);
       return;
      }
      var dx = (ex-sx)/2;
      var dy = (ey-sy)/2;
      var centerX = ex-dx;
      var centerY = ey-dy;
      var cx = centerX + (d?dy:-dy);
      var cy = centerY + (d?-dx:dx);
      if(depth == 1){
       dline(ctx,sx,sy,ex,ey,cx,cy);
       return;
      }
      else{
       dragon(ctx, depth-1, sx, sy, cx, cy, true);
       dragon(ctx, depth-1, cx, cy, ex, ey, false);
      }
     }
     var dragon_call = function(depth){
      return function(){
       clear();
       dragon(ctx, depth, 50, canvas.height/3, canvas.width-100, canvas.height/3,false);
       if(depth < 15){
        setTimeout(dragon_call(depth+1), 1000);
       }
      }
     };
     $("start").onclick = function(){
      dragon_call(0)();
     }
     dragon_call(15)();
    }
    </script>
    <body>
    
    <canvas id="canvas" width=400 height=400 ></canvas>
    <br/>
    <input type="button" id="start" value="start" ></input>
    </body>
    </html>
    

    まとめ


    今年も頑張る。