まめ畑

ゆるゆると書いていきます

ArrayCollectionのソート

動的プロパティを使う際に注意すること - まめ畑でArrayCollectionを使っていたのですが、中のアイテムを指定したフィールドを基準にソートする方法。

private var _data:ArrayCollection = new ArrayCollection([
	{"piyo":"piyopiyo", "fuga":"fugafuga"},
	{"piyo":"hogehoge", "fuga":"puyopuyo"}
]);

の様に書く事が多かったため、

private var _data:ArrayCollection = new ArrayCollection([
	new fugaData("fuga", "piyo"),
	new fugaData("hoge", "puyo")
]);

の様にアイテムにインスタンスを渡した場合はどうしたらいいのか少し悩んだけど、ソートの方法は変わらないみたい。

//_dataがArrayCollection
//ソートオブジェクトを作る
var sortItem:Sort = new Sort();
//複数のフィールドを指定する時は sortItem.fields = [new SortField("piyo"), new SortField("fuga")]の様に追加するだけ
sortItem.fields = [new SortField("piyo", false, true, false)];
//アイテムを追加
_data.addItem(new fugaData("fuga", "piyo"));

//ソート方法を指定
_data.sort = sortItem;
//refresh()を呼ばないとソートされない
_data.refresh();

何も変わらなかったです。

ちなみに、SortFieldの引数は

第1引数 name:String (default = null) ― このフィールドが比較に使用するプロパティの名前です。 オブジェクトが単純型の場合、null を渡します。
第2引数 caseInsensitive:Boolean (default = false) ― ストリングをソートする場合は、値の大文字小文字を無視するかどうかをコンパレータに指示します。
第3引数 descending:Boolean (default = false) ― アイテムを降順に配置するかどうかをコンパレータに指示します。
第4引数 numeric:Object (default = null) ― ソートアイテムをアルファベット順ではなく数として比較するかどうかをコンパレータに指示します。


あと、勉強不足で知らなかったのですが、第4回Flex勉強会 - Fores Labs

レンダラーのインスタンスは、メモリの効率化のために実際に表示されている範囲の分だけしか作成されない。
そのため、スクロールすると以前に表示されていた部分のレンダラーのインスタンスが使い回されることになる。
レンダラーでスタイルを変更する場合は、必ず条件に合わない場合に元のスタイルに戻す処理も併せて記述しておかないと、関係のないデータまでスタイルが変更されてしまうという予期せぬ動作になることが多いので要注意。

と書かれていました。
いつも、何気なくスタイルを戻すようにはしていたのですが、こういう事だったのかと勉強になりました。

動的プロパティを使う際に注意すること

動的プロパティをdataProviderにセットする時にwarningが出て、少し悩んでしまったのでメモ。
動的プロパティについての理解不足だった。


以下のようなコードを書いたらwarningが出る。

//piyopiyoなどの値は変数。(動的に変わる)
[Bindable]
private var _data:ArrayCollection = new ArrayCollection([
	{"piyo":piyopiyo, "fuga":fugafuga},
	{"piyo":hogehoge, "fuga":puyopuyo}
]);

//このデータを
//<mx:DataGrid id="piyo" dataProvider="{_data}" >
//みたいに感じでdataProviderにセット
//itemRendererの中で「data.piyo」の様にアクセス出来る

以下のようなwarningが出る

warning: unable to bind to property 'piyo' on class 'Object' (class is not an IEventDispatcher)
warning: unable to bind to property 'fuga' on class 'Object' (class is not an IEventDispatcher)

原因は、動的プロパティは明示的にBindableにする必要があるみたい。
ようは、Objectのプロパティを自分でBindableとして定義しなさいということ。

面倒でも、格納するプロパティ達をBindableに設定したクラスを作成して、そのインスタンスを格納することでwarningは出なくなった。

package
{
    public class fugaData
    {
        [Bindable]
        public var piyo:String;
        
        [Bindable]
        public var fuga:String;
        
        public function statusData(piyo:String="", fuga:String="")
        {
            this.piyo = piyo;
            this.fuga = fuga;
        }

    }
}

これを使って

private var _data:ArrayCollection = new ArrayCollection([
	new fugaData("fuga", "piyo"),
	new fugaData("hoge", "puyo")
]);

のように書き換えて解決。

TwitterのStreaming APIを使ってみた

少し前から、TwitterAPIにStreaming APIというものが実験的に追加されました。
これは、今までのAPIではXMLを取得する度に接続して切断してという処理を行っていましたが、このAPIはPUSH型で接続した後は接続がクローズされるまで情報が連続的に送られてきます。
公式Wiki: Sign in with your Twitter account | Twitter Developers
認証はBASIC認証のみに対応しており、HTTPでアクセスします。

詳細はWikiを参照した方が新しい情報を取得できます。
アクセスする際には、レスポンスを順次扱えるライブラリなどが必要なのですが、FlexのHTTPServiceでは逐次情報が取れなかったので、Socketを使って簡単に書いてみました。
Streaming API用の簡単なライブラリを作ったほうが便利と思ったので、今度つくろうと思います(ライブラリで既に出来るかもしれないですが・・・)


ランダムにPublic TLを取得するsampleAPIを使用して作ってみました。

とりあえず流してみました。


socketを使用してStreaming APIにアクセスするコード(XMLの処理がごり押しですが・・・)

private var sock:Socket;

private const regex:RegExp = /<text>(.+)<\/text>/;

        
private function init():void{
    sock = new Socket();
    sock.addEventListener(Event.CONNECT, connectHandler);
    sock.addEventListener(IOErrorEvent.IO_ERROR, function():void{trace("ERROR");});
    sock.addEventListener(ProgressEvent.SOCKET_DATA, handleData);
    sock.connect("stream.twitter.com", 80);

}

private function connectHandler(event:Event):void{
    sock.writeUTFBytes("GET /1/statuses/sample.xml HTTP/1.1\r\n");   //APIのアドレス
    sock.writeUTFBytes("Host: stream.twitter.com\r\n");
    sock.writeUTFBytes("Authorization: Basic BASICのあれ\r\n");
    sock.writeUTFBytes("\r\n");
    sock.flush();
}

private function handleData(event:ProgressEvent):void{
    var text:Array = (sock.readUTFBytes(sock.bytesAvailable)).match(regex);
    if(text == null || text[1] == null) return;
    trace(text[1]);   //ここにPOSTが入ってるはず
}

private function closeConnection():void{
    sock.close();
}

他のAPIはPOSTでパラメータを送る必要があるので俺々ライブラリ作った方が楽ですね。

NicoCanvas(α)を晒してみます

結構、期間が開いてしまいましたがNicoCanvas(α)を晒してみたいと思います。
といってもα版かつ自分用に作った物なので、まだまだコメントの衝突判定などに不具合があったり、ActionScriptFlexの練習に作った物なのでコードが汚かったりします・・・。
随時、改善・機能追加をしていきたいと思います。
よろしければ動画なりに重ねてお使い下さい。

9/7 コメントのデフォルト表示時間を変更しました
DLはhttp://mame-lab.com/files/nico_canvas/nico_canvas.zipから(NIS2009でウィルスチェック済)
NicoCanvasの概要はNicoCanvas製作中 - まめ畑

ちょっとしたこと

  • コメントサイズは画面サイズが「512*384」で使用時用にサイズを設定しています(サイズの変更可能)
  • コメントは4秒で画面を駆け抜けます(設定可能)

簡単な使い方

  • DLしたファイルを解凍すると「nicoCanvas.swc」が入っていますのでプロジェクトの「libディレクトリ」にコピーします
  • mxmlファイルを以下の様に変更します
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:nc="com.con_mame.nicoCanvas.*" layout="absolute" creationComplete="hoge()" width="幅" height="高さ">

「xmlns:nc="com.con_mame.nicoCanvas.*"」を追加して下さい

  • mxmlファイル内で使用する時は
<nc:NicoCanvas id="commentsLayer" x="x位置" y="y位置" width="512" height="384"/>

幅と高さは変更可能ですが、コメントサイズの変更が必要です。idはなんでも構いません。

  • 実際にコメントを流すには
//インポート
import com.con_mame.nicoCanvas.Comment;
import com.con_mame.nicoCanvas.ParseCommands;

//メソッドなどで
/**
 * コメントを表示して流す
 * 
 * commentLayerがNicoCanvas
 *
 * @param command コメントに関するコマンド
 * @param comment コメント文字列
 * 
 */	
private function showComments(comment:String, commands:String):void{
   //コマンドをパースして
   var parsedCommands:Array = ParseCommands.Parse(commands);
   //コメントを生成して
   var com:Comment = new Comment(comment, parsedCommands);

   //NicoCanvasに追加
   commentLayer.addComment(com);
}

この様にします
コマンドはスペースで区切りです

サイズの変更など

  • コメントサイズを変更するには
ParseCommands.commentSmallSize = 15;
ParseCommands.commentNormalSize = 24;
ParseCommands.commentBigSize = 39;

の様にコンストラクタなどで各コメントサイズを指定して下さい

  • コメントのスピードは
var com:Comment = new Comment(comment, parsedCommands);
com.speed = 10;   //コメントの早さを設定

とすると変更出来ます

対応コマンド

  • サイズ
    • small
    • big
  • コメント位置
    • ue
    • shita
  • コメント色
    • ニコニコ動画で使用出来る色全て(プレミアムカラー含)
    • 「#ff0000」の様にカラーコード対応

その他

まだまだ不具合やエラー処理周りを改善する余地があります。
遊び用にお使いください。

NicoCanvasのその後

NicoCanvas製作中 - まめ畑で製作中と書いていたNicoCanvasのその後です。
物凄く短いコードなのですが、中々時間を研究というものにとられてしまい時間がかかっていますが、そこそこ順調に進んでいます。
今は、コメントの衝突判定周りの改善と使用する時のメソッド関連を修正したりしています。
ほぼ完成しているので、もうすぐ公開出来そうな感じがします。


以下のような使用方法を現在は考えています。
コメントを表示するメソッドは

/**
 * コメントを表示して流す
 * 
 * commentLayerがNicoCanvas
 *
 * @param command コメントに関するコマンド
 * @param comment コメント文字列
 * 
 */	
private function showComments(command:String, comment:String):void{
   //コマンドをパースして
   var parsedCommands:Array = ParseCommands.Parse(command);
   //コメントを生成して
   var com:Comment = new Comment(comment, parsedCommands);

   //NicoCanvasに追加
   commentLayer.addComment(com);
}

現在のところこんな感じです。
基本的にはこの3行で大丈夫なのですが、コメントのスピードやサイズ(small, normal, big)変えられるようなプロパティも作ってあります。


mxml上でNicoCanvasを他のコンポーネントと同じように配置するだけですぐに使えます。
色々重ねる時も簡単ですね。

NicoCanvas製作中

Chumbyニコニコ動画が見れるようにしたいと以前エントリで書きましたが、コメントが重ならないように流すところを考えていたら、モジュールにでもして重ねるだけでどこでもコメント流れる物を作ろうかなぁと思い立って作ってます。
最近Flexの勉強をしようと思っていたので、Flexで作成しています。
しかし、NicoCanvasはActionScriptコンポーネントですが・・・。


VideoDisplayなりの上に重ねてCanvasにコメントを追加するだけでニコ動みたいに動画やLiveストリーミングの動画の上にコメントが流れます。
現状ではコメントの衝突判定と上・下コメントの実装はできているのですが、稀にコメントが重なってしまう事があるので改良中です。
ニコニコ動画の様にシークする事でコメントを戻す機能や、後から受信したコメントを挿入するために位置の再設定などの機能は実装していないので、生放送のようにリアルタイムでコメントを流していくアプリ向けかと思います。


テスト中の画面はこんなかんじです。
コメントの入力部分は用意していただく必要があります。


弾幕作ってたらキャプチャーが遅れてしまいました・・・。
真ん中あたりのあいてる場所は高速コメントが流れていった後です。


NicoCanvasパッケージには、ニコ動で使用しているコマンド(位置・コメントサイズ・色)をパースするものも含まれているので、コマンドを渡すだけでカラーコードなどを返してくれます。
NicoCanvas上に流すコメントモジュールも含まれているので、「コマンド・コメント受信→パースして必要な情報だけをコメントに設定→NicoCanvasに追加」で流れてくれます。
コメント色はニコ動で使用されている全てをサポートしています。後、カラーコードでの指定も可能です。(プレミアムカラーも使用可能)
現在使用しているコメントのサイズはニコ動と同じサイズです。


公開は現状ではしていませんが、もう少しコメントの衝突判定と自由に設定できる項目を作りこんだら公開したいと思います。



コメントを流す前と流れ終わり始めるところでコメントがはみ出してしまっていたのですがマスク処理で解決しました。

/**
 * UIComponentのcreateChildrenメソッドをoverride
 * 
 * NicoCanvasがChildに設定されたタイミングでマスクを設定し
 * Canvasをはみ出したコメントが見えないようにする
 * コメント入場時と退場時にNicoCanvasの領域外
 * 
 */		
protected override function createChildren():void{
	super.createChildren();
	var mask:Sprite = new Sprite();
	mask.graphics.clear();
	mask.graphics.lineStyle(1);
	mask.graphics.beginFill(0xffffff);
	mask.graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
	mask.graphics.endFill();
	addChild(mask);
	this.mask = mask;			
}

これで、NicoCanvasがaddChildされたタイミングでマスクが設定されます。

Flexで常にTextAreaの最終行を表示する時のメモ

Flexでアプリを作っていてログなどをTextAreaに表示する際、常に最後に追加された文字を表示させようとして少し困ったのでメモ。


最初、このような感じでログを追加する関数を書いた一番下の文字の1つ前辺りに設定されて困った

//logはTextAreaのid
private function addStatusLog(message:String):void{
   log.text += message + "\n";
   log.verticalScrollPosition = log.maxVerticalScrollPosition;
}


どうやら、設定した直後では上手く値が取得出来ない様で次の描画のタイミングで取得しないといけない模様。
なので、以下の様に修正して解決。

private function addStatusLog(message:String):void{
   log.text += message + "\n";
   callLater(function():void{
             log.verticalScrollPosition = log.maxVerticalScrollPosition;
   });
}


callLaterで次の描画単位まで実行を遅らせて、所望の順序で実行させる事が多いようです。
初期化後や、何かを追加した後に処理を行いたい場合はcallLaterを使用して処理の順番を指定するようです。
描画されるまで値が正常に取得出来ないという考え方でいいのかな。