Xamarin.Froms を使ってパズドラのように駒を動かす

Xamrin.Forms のドラッグシリーズの続き
ひと駒だけドラッグができたので、今度は複数の駒を移動させてみます。いわゆるパズドラのように隣の駒を入れ替えながら動かすというやつですね。

やっていることは前の BoxView のドラッグと同じで移動時のイベントを ManipulationDelta に変換して PCL にコールバックします。

AbsoluteLayoutに配置する

位置がわかりやすくなるように、AbsoluteLayout を使っています。UWP の Canvas に似た動きをしますね。iOS/Android/UWP の3種類でどういう作り方をしているかは知らなくても大丈夫です。Xamarin.Froms の AbsoluteLayout タグに則って配置させます。

/// <summary>
/// 初期化ボタン
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private void clickInit(object sender, EventArgs e)
{
    var cols = new List<Color>()
    {
        Color.Red,
        Color.Navy,
        Color.Yellow,
        Color.Pink,
        Color.Purple,
    };
    var rnd = new Random();

    // キャンバスに Box を25個並べる
    boxes = new List<BoxViewEx>();
    for (int i = 0; i < 25; i++) {
        var box = new BoxViewEx();
        boxes.Add(box);
        canvas.Children.Add(box);
        int w = 60;
        int h = 60;

        int x = (i % 5) * (w + 10) + 30;
        int y = (i / 5) * (h + 10) + 30;

        var rect = new Rectangle(new Point(x, y), new Size(w, h));
        box.LayoutTo(rect);
        box.BackgroundColor = cols[ rnd.Next(cols.Count)];
        box.ManipulationDelta += Box_ManipulationDelta;
        box.ManipulationCompleted += Box_ManipulationCompleted;
        box.ManipulationStarted += Box_ManipulationStarted;
    }
}

BoxView のタップイベントは、BoxView 自身につけています。自前の ManipulationDelta のコールバックでは移動している BoxView のオブジェクトそのものを sender として渡すので、このコールバックを AbsoluteLayout に集約してしまっても大丈夫なはずです。

タップ操作イベント

BoxView を移動させて隣の駒の中心付近になったときに駒の入れ替えをします。このあたりは感覚的なものがあるので実地で調節するのと、素早く動かしたときに SingleOrDefault なところでエラーになるので注意が必要です。これは LayoutTo メソッドを使ってアニメーションで駒を移動させているからです。Layout を使ってパッと移動させても良いのですが、ひとまずゲームっぽい感じで。

/// <summary>
/// ボックスの移動開始
/// </summary>
/// <param name=&quot;arg1&quot;></param>
/// <param name=&quot;arg2&quot;></param>
private void Box_ManipulationStarted(object sender , EventArgs arg2)
{
    var box = sender as BoxViewEx;
    /// 移動開始位置を覚えておく
    boxBlank = box.Bounds;
}
private bool _flag = false;
/// <summary>
/// box の移動中
/// </summary>
/// <param name=&quot;sender&quot;></param>
/// <param name=&quot;e&quot;></param>
private void Box_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    // box の中心が他のboxに入ったときに入れ替える
    var box1 = sender as BoxViewEx;
    double x1 = box1.Bounds.Left + box1.Bounds.Width / 2;
    double y1 = box1.Bounds.Top + box1.Bounds.Height / 2;

    // 1.中心が他のboxに入っていないか調べる
    try {
        var box2 = boxes
            .Where(x => x != box1)
            .SingleOrDefault<BoxViewEx>(x => x.Bounds.Contains(x1, y1));
        if (box2 == null) return;

        if (_flag == true) return;
        _flag = true;
        // 2.中心に入っていれば、空位置へ移動する
        var b = boxBlank;
        boxBlank = box2.Bounds;
        box2.LayoutTo(b).ContinueWith((_) => { _flag = false; });
    }catch
    {
        return;
    }

}
/// <summary>
/// タップを離したとき
/// </summary>
/// <param name=&quot;arg1&quot;></param>
/// <param name=&quot;arg2&quot;></param>
private void Box_ManipulationCompleted(object sender, EventArgs arg2)
{
    // 移動中の box は空きに収まるように移動させる
    var box = sender as BoxViewEx;
    box.LayoutTo(boxBlank);
    boxBlank = new Rectangle(); // 空にしておく
}

そんな訳で次は、カードゲームでよくある強化カードを選ぶ、みたいなドラッグサンプルを。

サンプルコード

moonmile/PazzleDrag
https://github.com/moonmile/PazzleDrag

カテゴリー: C#, Xamarin | Xamarin.Froms を使ってパズドラのように駒を動かす はコメントを受け付けていません

追悼 デスマーチ

「デスマーチ」の著者 エドワード・ヨードン氏が亡くなったそうなので、追悼記念に書き下しておく。ちなみに、ツイッターでも書いたけど「デスマーチ」は「終わりなき行軍」を意味するので、現在の短納期(半年ぐらい)のプロジェクトは当てはまらないと思う。そういう場合は、すでに精神が「デス」か「Dead」してしまう場合が多いので別なラベリングが必要だろう。「お前は既に死んでいる」…でもいいけど、意味が違うから別なのを考えよう。

「デスマーチ」を手に取った頃

私が「デスマーチ」を知ったのは、まだ阿佐ヶ谷に住んでいたころだから一人暮らしで寝て起きて渋谷だったか新宿だったかに通っていたころだ。20年前になると思う。DoCoMo のプロジェクトで i-mode の前身を作っていたころだ。いまでこそ i モードを知っている人は少なくないけど(つーか、終わっているから知らない人のほうが多いかも)当時、なんだかわからない2,3年目の新人が、なんだかわからないプロジェクトのまま、なんだかわからない新宿の地下で毎日仕事をしているのは、いま考えればちょっと精神が病みそうな感じだったのだと思う。だが、会社員としてのプログラマをやりつつ、流行りだしたインターネット&ホームページ作りをやりつつということで、仕事と自分の将来を折り合いが付くか付かないかのぎりぎりのところだったと思う。

そういう頃に、プログラミング(当時は VB や C++ が主流)の技術のあれこれとともに、いつ果てるともない仕事の流れに漂っている感じ、朝会社に行く昼を食べる夕方に夜食を少し食べる10時頃に新宿を出る、という毎日を続けていた。合間にプログラミング&テスト&不具合直しを繰り返して、自分の担当の仕様をあれこれ考えてやっていても、多少プログラミングの腕に自信があるぐらいではどうしようもない量の仕事(プロジェクト自体にかかわっているのは、自社で30名弱、他社も入れれば200名ぐらいだろうか)があって、徒労感がひどい状態だった。でも、まあ、こういうのが「仕事」かもしれないと思ってやっていたころだ。

ふと、阿佐ヶ谷の本屋でいつもの技術書コーナーじゃなくて(当時はコンピューターの本はたくさん棚に並んでいたのだ)、なにかちょっと読みものっぽいコーナーを見た。「人月の神話」を買った前後だったか覚えていないのだが、「デスマーチ」という薄い本(日経BPのは分厚いけども、当時はもっと薄かった)があった。日経BPのほうでは挿絵を削られてしまっている(と思う)のだが、当時の本には、行軍のイラストが書いてあった。

ある意味、それだけで私は「デスマーチ」という本は十分だったのである。

見えない現象に名前を付けるということ

タイトルを失念したが、細野不二彦の漫画に「子供に見えない恐怖に名前を付けて克服できるように教える」というシーンがある。もともと現象なり恐怖なりはそこにあるものだけど、それを認識するためにはなんらかの「認識する」行為そのものが必要となる。それが「名前付け」であり、認知学の基本だったりする。現象学を紐解けば、シニフェ/シニフィアンという対応がとられているのがわかる。

いつ終わるともわからないプロジェクト、完成がいつかわからないプロジェクト、毎日忙しいけれどそれがいつ忙しくなくなるのかがわからないプロジェクトが2,3年続けば、それは「デスマーチ」プロジェクトと言ってよい。当時は、プロジェクトの成功率が 30%程度(日本の成功率が高いのは、「失敗したと認めない」からだ)だった。もっと厳密に「成功率」を考えれば、

  • 当初、計画した期間でプロジェクトを終わらせる
  • 当初、計画した予算(人員)でプロジェクトを終わらせる
  • 完成時に、当初の要望を満足できる形で製品が完成される

のが「成功」と言えるだろう。もちろん、もっとゆるゆるにして「ほぼ成功」とか「一部成功」なんてことも言えるけど、きちんと計画駆動(ウォーターフォール)するのだったら、成功の定義ぐらいきちんとして欲しいところだ。まあ、カウボーイプロセスも似た感じではあるけれど。

そういうものを曖昧にしたまま、「仕事」だからというスタイルでずるずるとソフトウェア開発をしていると、それは「デスマーチ」だ。目標もゴールも予算もなにもない。ただ「行軍」だけが目的化してしまった状態だ。

その渦中にいると、なにでこうなっているか分からないのだが、一度「デスマーチ」という名前付けをしてしまうと、一方で「デスマーチではない状態とは何か?」がわかってくる。いくつか「デスマーチではない状態」を列挙してみれば、

  • 納期が決まっている(それが不可能であっても、動かない納期がある)
  • 予算が決まっている。予算がなくなれば、プロジェクト自体が死ぬので、デスマーチも止まる。
  • タスクが決まっている。クリアすべきタスク/機能がわかっていれば、それさえ作ってしまえば、いつでもプロジェクトを終わらせられる。

という状態がある。なので、2000年頃からスタートした Web 開発の場合、短納期であることが多いので「デスマーチ」にならない。その代り「デス」=「即死」してしまうことはある。

ヨードンとデマルコ

実は、エドワード・ヨードン氏は数学者で(だったよね?)数学的なアプローチでソフトウェア開発プロセスを組み直そうとしている。構造化分析とかシステムダイナミクスとかそれだ。一方で、がちがちの管理主義者だったデマルコ氏だが一変して「ピープルウェア」を提唱している。このあたりは(今思えば)両者ともコンサルタントの立場から離れないという感じで、あまりプログラミングそのものには介在しないような感じなのではあるが、「ソフトウェア開発」というもの自体が「営利活動」である間は、経営/雇用/被雇用/外注などの普通の会社と変わらないものが出てくる。そこのあたりはドラッカー言うところの、何処に「顧客を創る」かだ。

おそらく、そのあたりで SAP などの BI(ビジネス・インテリジェンス)ブームに乗ってしまったと思うのだが、この二人がプロジェクト・ナビゲーターを作っていた頃の講演を聞きにいったことがある。

当時の私は、会場で手を挙げて質問をするのが趣味だったので、数学者としてのヨードン氏に質問をした。「TOC(制約理論)を応用した CCPM をどう思われるでしょうか?」。私のほうは TOC にハマっていた頃で、CCPM を自分の会社に応用できればいいかと思っていろいろ考えて、同時に XP をはじめとするアジャイル開発界隈にも顔を出していた頃だった。彼の答えの雰囲気としては、「TOC は非常に素晴らしい/興味ある理論だけども…」という感じだった。まあ、そりゃそうだ。一方で「プロジェクト・ナビゲーター」を売り出していこうとしたところで、TOC/CCPM の話をされたらあまり良い気はしなかったのだろう。

プロジェクト・ナビゲーターの最大の欠点は?

その講演があったのは10年前ぐらいだろうか。アジャイル開発が盛り上がっていた頃でもあって、管理側と開発側の対立が顕著になっていた頃だ。むろん、アジャイル開発をする場合には、スクラムプロセスのように管理と開発が一体にならないとダメなのだが、もともと管理側の人間からすれば「人を管理する」に徹するほうが分かりやすかったりする。少なくtも、管理側の人間からすれば、管理するのが楽なのだ。あたりまえだけどそういう視点がある。このあたりはワインバーグの「ライト、ついてますか」にもあって、「管理者になれ、被管理者になるな」ってのがある。

私が、BI が胡散臭いと思っているのは(今の BI はわからないが、当時の BI は非常に胡散臭かった)、現状だけを眺める、いわば工場のラインを上から眺めるだけの仕事になっているからだ。そのラインで働いている人のことを全然考えていない。というか、そのラインで働いている人を「バカ」だと思っているか、そういう制限された能力しかない(少なくとも、管理者を超える能力や、管理者が想像もできないような能力は思いもよらない)というのが前提になっている。だから、ラインで働いている人は、飛び抜けて出来ても困るし、もちろん飛び抜けてできなくても困る。そういう「ソフトウェア・ファクトリ」(昔の Microsoft のスローガンだ)の視点でしか見ない現象である。以前は、こういう会社を憎んでいた私でははあるが、いやいや今だだと旧態依然でやってくれたほうが相対的に自分に利益になることが分かったので放置である。だって「仲間」じゃないんだもん。

でもって、「ナビゲーター」の話に戻ると、それは株のデイトレーダーと同じように開発プロセスを見ることを目的としている(ヨードンは「デイトレード」という名称を使わなかったけど、絵柄はそっくりだ)。確かに、経営的な視点とかマクロ的な視点とかであればいいのだが、どうもそういう尻馬に乗っているスタイルは私は好きではない。いや、もっと積極的に言えば、尻馬に乗るぐらいだったら、馬自体の能力をもっと引き出したらどうだろうか?ってな具合だ。

「ナビゲーター」は現状を数値化して不都合のありそうなところをフォローするように働きかけることができる。いわば、マイナス部分を削っていけば、プラスだけが残る。少しずつ平均を上回るということだ。私も「マイナス削除」の方法をとることはあるけど、それはミクロ的なレベルだ。けれど、マクロ的にマイナスの削除をするということは、人を部品として切り捨てるということになる。あるいは、人を標準化するということになる。

もちろん、CCPM も含めて「部分最適化」よりも「全体最適化」のほうが効果が大きいことがわかっている。CCPM もプロジェクトナビゲーターも全体を俯瞰するというツールという点では同じだ。進捗状況を、Excel で管理したり、MS Project であれこれとやったり、バーンダウンチャートを作ったりするのも同じだ。全体の俯瞰するためのツールである。が、全体を俯瞰して弱点を見つけたあとに何をするかというのが「プロジェクトナビゲーター」にはない。いや、なかった。弱点を補強するのか、差し替えるのか、または全体をシミュレーションし直すのかという視点がナビゲーターにはない。それは、あくまでも俯瞰するための「図」でしかなくて、ナビゲーションしていない。そうそう、弱点を補強するという形では、ツェッペリが鉄球で馬の癖を大きくする、という方法もあるのだ。

そういう意味でもピープルウェアから外れている感じがするのだが、当時は CMMI ブームでもあったので仕方がないといったところだろう。

再びデスマーチへ?

過去のものが「経験」であるならば、未来に進むためには「予測」や「予知」が必要となる。私たちはソフトウェア界隈の多くの屍(主に大手企業の屍)を乗り越えここに至っている…ような気がするけど、実はそうでもない。豆蔵のボランティアのアレもどうかと思うけど、どちらかといえば、屍になっちゃった人は屍のままで、その臭いを避けて通った人が生き残っている気がする。

かつてデスマーチ撲滅メーリングリスト(でしたっけ、プロセス改善 ML だったっけ?)ってのがあったのだが、廃れてしまった。その第一の理由が、「デスマーチになるような会社を辞めて、転職/独立しちゃった」ので、デスマーチに巻き込まれなくなったんですよね。そうすると、自分のことではなくなってしまうので、あまり興味がなくなっちゃうわけですよ。緊急性が低くなる感じ。

という訳で、某銀行プロジェクトのような大規模デスマーチ予備軍に配属されない限り、あまりデスマーチに出会うことはない。むしろ、冒頭で書いたのだが「デス」の場合が多い。配属 → 即死フラグってのがあまりにもアレなんだけど、そこは要領良く無駄な作業を省くということだろう。超概算見積もりとか、三面図型 CCPM とか、まあ色々避ける手段はある。

[amazonjs asin=”B00F4QOMUO” locale=”JP” title=”デスマーチ 第2版 ソフトウエア開発プロジェクトはなぜ混乱するのか”]

[amazonjs asin=”B00I96CJWO” locale=”JP” title=”ピープルウエア 第3版”]

[amazonjs asin=”4320023684″ locale=”JP” title=”ライト、ついてますか―問題発見の人間学”]

 

カテゴリー: プロジェクト管理 | 追悼 デスマーチ はコメントを受け付けていません

Is TDD Dead ? のその後

今更 TDD なのか?それとも今から TDD なのか…という疑問が出たのは既に2年前なのですが、状況はあまり変わっていないような気がします。

TDD is dead. Long live testing. (DHH)
http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
テスト駆動開発(TDD)はもう終わっているのか? Part 1 | 開発手法・プロジェクト管理 | POSTD
http://postd.cc/is-tdd-dead-part1/
テスト駆動開発(TDD)はもう終わっているのか? Part 2 | 開発手法・プロジェクト管理 | POSTD
http://postd.cc/is-tdd-dead-part-2/
Is TDD dead? – YouTube
https://www.youtube.com/watch?v=z9quxZsLcfo

以前、マーチン・ファウラーが Japan XP User Group のために 2002年頃に来日して話した中に

  • TDD はプログラマに安心を与える。
  • MDA はプログラマを殺さないし、今後も大丈夫だ。

というのがありました。折しも「MDA(Model-Driven Architecture)」が発表されたころで、設計者 Model を作って UML のように繋げ、プログラミングの工数を大幅に減らす。そう、ソフトウェア開発の自動化によって大量にプログラマはいらなくなる(昨今の「ロボットに仕事が奪われる」ブームと同じ状態)がありました。当時から、大手ITのゼネコン体制が問題になっていたし、今でも問題な訳ですが(それは社員も含めて「社蓄」と言われているわけですが)そのあたりも含めての「今後も大丈夫だ」の発言です。ただし、ワタクシ的に言えばマクロ的には後退しているような気がしています(個人的には問題ないんですけどね、それは私の年齢的なものもあるので一般化できません)。

それから XP/TDD と言われて「アジャイル開発」という名前のブームも去りつつあり、そもそもソフトウェアプロセスについてのブームが去ってしまい、VRや人工知能、ロボット等の技術的な側面が全面に出てきています。これはスマートフォンやウェブサイトが一般に広まって零細/個人レベルでの売り出し(技術そのものを売る)ことができるようになったからでしょう。だから、内情がアジャイルであれ、ウォーターフォール式の委託であれ、カウボーイプロセスであれ、できあがった「製品」に対しての評価比がプロセス自体よりも高くなったということです。

そういうところで、Webサイトやスマートフォンのアプリを創るときに「TDD」だけにこだわるのはナンセンスです。

が、一方で業務アプリケーションのように十数年レベルで使われているもの(十数年使われていたもの)やインフラ開発のように障害発生時の損失が非常に大きい場合と、数年で消え去ってしまう Web サイトというか数か月でリリースしないと機会損失が発生してしまう Web サイトやスマートフォンのアプリとは別のアプローチが必要になってきます。それだけ TDD 自体の適用範囲が異なるということでしょう。

ただし、TDD にどのような価値があるかといえば「プログラマに安心を与える」のが最初になります。綿密な設計をし組み合わせを考え障害や例外を考慮したフローチャートを書いた後に、慎重なコーディングをした後に、このコードが期待通り動くかどうかを確認するには「ユーザーと同じような操作を画面から行う必要がある」というのは、心理的な負担が大きいのです。設計による試行錯誤、コードの書き換えによってどんな「変化」がおこってしまうかを、ずっと後の工程となる「テスト工程」ではなくて、その場でロジックとして即座に確認できる、というのが TDD の良いところです。この試行錯誤を手軽に扱える道具が TDD の UnitTest になります。逆に言えば、プログラマの安心のためにプログラマ中心の考え方が TDD にあります。

品質を向上するための手段はいろいろある

逆に言えば、プログラマを中心に据えないスタイルで、品質を向上する手段もあります。

  • プログラムコードを書かない設計書からの自動生成(富士通のアレ)
  • スクラム、CCPM をはじめとする、ソフトウェア開発プロセス/マネージメントからのアプローチ
  • 要求定義/開発というスタイルでの、要求の絞り込み (豆蔵のアレ)
  • ワインバーグの提唱する、要求定義工程からの品質の作りこみ
  • カバレッジやプロファイルツールを使い、テストの網羅率や複雑度からのアプローチ (富士通のアレ)
  • テスト工程での QA、不具合混入原因の調査、標準的な不具合発生率の導入
  • 関数型言語などの数学/証明的アプローチの活用
  • フレームワークを活用して、手動で作成する部分を減らす方法 (EJB, EF, Cake等)
  • UMLによるパターンやオブジェクト粒度などの経験的な手法の導入

TDD も含めて、品質を向上する(いわゆる製品の不具合率を下げる)方法はたくさんあります。どれを使ってもよいし、どのような組み合わせをしてもよいのです。ですが、残念ながら人はそれぞれの立場があり、その立場から得る利得のゆえに偏った「手段」を選ぶことがあります。それは TDD/UnitTest も同じ立場ではあるのですが、どこかに偏ってしまったときには、たいていの場合うまくいかない可能性が高まります。

逆に言えば、うまくいかない可能性を低めるのがこれらの品質マネージメントの目標になります。

品質を悪化させるものを削る

スマートフォンのアプリであれ、業務システムであれ、宇宙開発であれ、ロボット開発のためのソフトウェアであれ、要は「要求通り正しく動けばよい」という基準があります(品質工学の考え方で、管理図からはみ出さなければ良い、匠のように中央値に集約させなくてもよい、という考え方です)。特に素晴らしい品質/性能ではなくても、そこそこの品質を保てば平均以上になるということです(Intel プロセスのアレです)。

悪いものを削っていけば、だんだん良いものだけ残るだろうという発想のもとから、品質を悪化させるものを削っていくスタイルを取ってみます。そうすると、先の品質を向上するものが行き過ぎてしまったときに悪さを減らすことが可能です。

  • コーディングの手間がかかる部分で一定の法則があれば、コードを自動生成してしまう。
  • 要求の変更が少ない場合はウォーターフォールで、試行錯誤が必要であればスパイラルで、要求自体が時間軸で変わる可能性があればアジャイルプロセスを適用する。
  • 実装できない/実現の難しい要求を排除する。
  • 過去に実現できた要求を組み合わせて実現する。
  • TDD とカバレッジを組み合わせてテスト網羅率をほどよく観察する。観察した後は、TDD や設計にフィードバックする。
  • 似たプロジェクトあるいは以前のプロジェクトでの不具合混入率や原因をあらかじめ知っておく。
  • 部分的に強固なライブラリとして関数型言語を利用する。複数のプログラム言語を混在させる。
  • 部品が買えるのであれば、購入する。
  • 似たパターンを探し、パターンに沿って作成/ルール化する。ただし、20% の例外がある。

のように、悪化させる要因を減らしていきます。理論的なアプローチのほうが「美しい」と思われるでしょうが、そういう実現可能なものが「工学的なアプローチ」的に美しいものです。

Is Dead TDD のその後

その後、どうなったかといえば「Is Dead TDD」と検索してもさほど有用なデータは出てきません。使う人は使うし、使わない人は使わないという二極状態のような気がします。

おまけ、構造行列を使う方法

鴨澤さんの https://twitter.com/kamosawa/status/688208924764311557 順列組み合わせ絡みとコンポーネント化のところは、デザイン・ルールの「構造行列」という実例があります。この本はコンピュータのハードウェア設計を例にとって、設計時の情報伝達と手戻りのコストを行列として合わらわしたものです。訳本が 2004年で、原著は 2000年出版ですね。

以前、これをもとにして 30 コンポーネント/機能程度の構造行列を出して開発コスト&手戻りコストを計算したことがあります。あるコンポーネントが他のコンポーネントにどれだけ影響があるかを遷移表として調べて、関連を数え上げるだけです。この関連部分を少なくするように設計/UMLを書けば、各コンポーネントの独立度が高くなるため変更に強い(変更に相互影響が少ない)コンポーネント/モジュールが作れるという訳です。

image

[amazonjs asin=”4492521453″ locale=”JP” title=”デザイン・ルール―モジュール化パワー”]

カテゴリー: 開発, TDD | Is TDD Dead ? のその後 はコメントを受け付けていません

TDD はロストテクノロジー化しているか?

何故か、英語で CppUnit の解説を小一時間することになってしまったので、その後の展開的に残しておく。

使っている人は使っているらしいが

github のコードを見ると大抵のフレームワーク系のライブラリは、 test フォルダがあってテストプロジェクトがある。まあ、実際に自分でやってみると適当な xUnit を書かないと立ち行かない場合が多い(スタート時点の複雑さに追いつかない&後で混乱して修正コストが高くなる)のとで残しておくのと、ライブラリのマニュアル的なものとして残しておくと便利なのでちらっと書いておくとよい。

が、どこでどう間違ってしまったのか、様々な原因で身の回りに実務で TDD をやっている人は少ない…というかいない。これは主に業務アプリを作っている私だからなのか、最近はあまりであることが少なくなったのか分からないが、テストコードを書いている人は少ない。変わりに、従来通りの UI の打鍵テストをやったり、Excel で管理された単体テストや結合テストを繰り返している。

おそらく、XP のプラクティスと業務アプリ開発に取り入れるには「ペアプログラミングはコストが高い」、「UnitTest を継続的に使うにはコストが掛かる」があり、もうひとつ「単体テストをやると、単体テストのバグ票が出ない」かつ「単体テストの項目数が数えられない」というものがある。これは、2000年頃に XP/TDD を導入したときから言われてきたものだ。契約時に工数見積もりをするとき、「単体試験工程」を含ませてる必要があって、単体テストの項目数やバグ数という指標に引きずられてしまうというパターンである。まあ、そういう場合は

  • 単体テスト込みの製造工程/ガントチャートとする。
  • 単体テストの不具合票は出さない。

で押し切るのがベター。なんらかの QA 絡みで押し切られる場合には、不具合票自体をでっち上げるという手段をとる。

そんな訳で TDD は実質あまり広がらずに廃れてしまったものの、本質的に便利なところは便利だとわかっている人が使っている、という状態なのだろう。ちなみに言えば、UnitTest のフレームワークを使うか使わないかで、私的には工数的には倍以上異なる。MVVM パターンの解説のときにもよく言うのだが、

  • 内部ロジック(M-VM)部分をできるだけ多くして、UnitTest を使う。
  • UnitTest が適用しやすいクラス構造に徹する(できるだけモックを使わない)。
  • 適用外の場合は、通常の UI 打鍵で行う。

というスタイルが良い。TDD を適用しようとしたとき失敗するパターンが多いのが、すべての TDD を適用しようとするパターンである。どんな方法でも「適用可能な範囲」があるので、TDD で苦手な部分は多い。特に、ユーザーがいろいろなタイミングで打鍵するパターンは、TDD だけではうまくいかない場合がある(とはいえ、解析的に UnitTest を使うとうまくいくパターンも多い)。そのあたりを含めて、部分的に XP/TDD を取り入れればよいのである。

ちょっと変わった UnitTest フレームワーク

.NET Framework の場合は、Visual Studio で MS 謹製の UnitTest を使ったり、由緒正しい NUnit を使ったりする。Java の場合は JUnit だし、探せばたいていのプログラム言語に UnitTest フレームワークが用意されている。

UnitTest の基本は簡単で、

  1. テストコードを(できるだけ同じ言語で)プログラミングする。
  2. テストコードを何等かの形で実行する。
  3. テストの結果を自動判別する。

の 3 点があれば十分である。以前、Visual Test という GUI のテストを VB を使ってテストするフレームワークがあったがあまりはやらなかった。中身が C++/MFC で書かれていても、VB でテストしない駄目だったからだ。まあ、GUI の自動テストってのが難しいというのあるんだけど。

テストの実行環境は、できるだけ実環境と同じにあわせる。例えば、ガラケーの開発をしていたときには CPU の種類が異なるので、PC のテストとガラケー内のテストではずれが生じる可能性がある。ので、ガラケー内でテストが実行されるようにするのがベター、だがロジック的には PC でも十分だったりする。

結果を自動判別するのは、1,2,3 のサイクルを自動的に回すために必須である。昔 TDD 出る前に Excel で単体試験項目を書いて自動で動かす VBA を組んだことがあったが、結果は目検で行っていた。いま考えれば、結果を VBA で取り込んで Countif あたりで集計すればよかったのである。

そんなわけで、通常の PC あるいはサーバーで動く UnitTest はプログラム言語対応をそのまま使い、PC 以外の場合はちょっとした工夫が必要になる。

CppUnit

CppUnit – C++ port of JUnit – Browse /cppunit/1.12.1 at SourceForge.net
http://sourceforge.net/projects/cppunit/files/cppunit/1.12.1/

2008年で更新が止まっているが、これで十分。Visual C++, g++ で動くのでこれを使い続けている。一時期は更新結果が Green/Red になることが重要だったりしたが、実はそれは些末なところでしかない。本質は、All Success なのか Any Failure なのかにある。失敗したことが確認できて、どこでエラーになったか判別できればそれで十分である。

image

これは内部で FEM を計算するときの歪み関数をテストしているところだ。こういう科学計算の結果チェックに使える。

FsUnit

What is FsUnit?
http://fsprojects.github.io/FsUnit/

内部的には NUnit のラッパーなのだが、テストの書き方がちょっと異なる。たいていの xUnit テストは assertEqulas( 期待値, 実行値 ) という形でテストを書き連ねるのだが、FsUnit の場合は 実行値 |> should 期待値 というテストの書き方になる。期待値と実行値の語順が逆なのは関数型言語特融のカリー化/パイプを使っているため。この語順が便利かというと、実は結構混乱をしていて私の場合、CppUnit や NUnit の語順に慣れているのでよく間違う。

とはいえ、パイプを使うことで通常の assertEqulas 関数とは違った使い方ができる。特にパイプの間にログのフィルタを挟み込めるのは便利だ。バグ解析をするときに

実行値 |> dprint |> should be …

のように、適切な dprint を用意して挟み込めるようにするスタイルができる。

ArduinoUnit

先に書いた通り、組み込みシステムを作っているときは実機で動くような UnitTest を書くのがベストだ。Arduino の場合には Sketch と呼ばれるプログラム(実質、プログラムだ)を書いて動作させる。言語は C++ 風になっているので、ほぼ CppUnit が使える。ただし、 Arduino の sketch 自体が完全に C++ 準拠なのかどうかはわからないので、できれば Arduino 専用の UnitTest を使って、実行は Arduino 上でやったほうがいい。特に、GPIO をリアルタイムで制御するとか、センサーからの値を取得するとかは実機でしかできない(勿論、センサーからの値は毎回異なるので、assertEquals の対象としては使えない。センサーから読み込める/読み込めない、とか BLE 通信をするとか、そういう扱いになる)。

mmurdoch/arduinounit: ArduinoUnit is a unit testing framework for Arduino libraries
https://github.com/mmurdoch/arduinounit

まだ使ってみたことはないが、ひと通り AssertEquals が使えて結果がシリアル通信で送られてくるらしい。BLEやWiFi モジュールの通信系とかを作るときに便利そう。

Xamarin の UI Test App

Xamarin.UITest – Xamarin
https://developer.xamarin.com/guides/testcloud/uitest/
Xamarin.UITests クイック スタート | Xamarin : XLsoft エクセルソフト
http://www.xlsoft.com/jp/products/xamarin/uitest-quickstarts.html

Xamarin Test Cloud が必須っぽいのだが(テスト自体はローカルで実行できる?)、Android/iOS 上でテストをするツールである。これも試したことがないので記事を読む限り、昔の Visual Test に近いスタイルを取っている。UI コントロールに直接代入をする形になる。ただし、テストコード自体が C# であるのと、Xamarin 自体が C# で組む(F#/VB でも可能だけど)ので、テストフレームワークとして同じ言語を使える。スマートフォンのテストに特化しているので、ユーザーからの入力と画面キャプチャができるのがメリットだろう。

内部的には NUnit で動いているようなので、Xamarin.UITest.IApp と ITestServer を実装すればローカルで動かす限り Xamarin Test Cloud は必要ないのではないか?という作りになっている。勿論、Xamarin Test Cloud  自体は Android のいろいろな機種で動くというメリットがあるので、手元にある機種限定になるだろうけど。

Android Studio で UnitTest

AndroidStudioでUnitTestを行う | Anysense Blog
http://blog.anysense.co.jp/app/androidunittest/

こちらのほうは、由緒正しい JUnit を使った Android のテスト。こっちの UnitTest は Android Studio 上で動かすパターンなので UI を介在しない。ロジックなどをテストときに使う。

これも本来ならば、Android 実機上で動かしてログなどの形で結果が通知されるとよい。たぶん、テスト用の画面を作って「テスト実行」のボタンを押すと TestMain が起動されるという形にすればよいと思う。

Swift で UnitTest

Swift 2 + Xcode 7: Unit Testing Access Made Easy!!!!
https://www.natashatherobot.com/swift-2-xcode-7-unit-testing-access/

以前から、Xcode には test プロジェクトを作るか否かのスイッチはあるものの使ったことがない。まあ仕事がら執筆用のサンプルを作ることが多いので単体テストをやること自体が少ないのだが。

これも Android Studio と同じように Xcode 上でテストを実行するパターン。@testable というキーワードを使ってテストクラスを指定する(NUnit は属性を使ってテストクラスを指定する)。これも、実機の iPhone 上で動くことが望ましい。

参考文献

そんな訳で、実行環境が変わるとそれに追随して UnitTest のフレームワークも再作成される。が、もともと UnitTest のパターンは同じなので他言語で作ったテストパターン/知識は流用できる。つまり知識の蓄積ができる分野なのだ。

という訳で、ロスとテクノロジー化していると思われたら再入門し、知らなかったら「損」だよという形でお薦めする。

[amazonjs asin=”4894717115″ locale=”JP” title=”テスト駆動開発入門”]

ちなみに、私が最初に読んだ TDD の本はこれです。

[amazonjs asin=”4756136877″ locale=”JP” title=”Rubyを256倍使うための本 極道編”]

カテゴリー: 開発, TDD | TDD はロストテクノロジー化しているか? はコメントを受け付けていません

Xamarin.Forms でドラッグを実装しよう(UWP編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653
Xamarin.Forms でドラッグを実装しよう(Android編)
http://www.moonmile.net/blog/archives/7667
Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on Android編)
http://www.moonmile.net/blog/archives/7670
Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on iOS編)
http://www.moonmile.net/blog/archives/7723
Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on WinPhone編)
http://www.moonmile.net/blog/archives/7728

の続きです。

Xamarin.Forms は 2.0 からユニバーサルアプリ(UWP)を扱えるようになったハズなので、これを作ってみます。

Adding a Universal Windows Platform (UWP) App – Xamarin
https://developer.xamarin.com/guides/xamarin-forms/windows/getting-started/universal/

を参考にして、

– App.xaml.cs に Xamarin.Forms.Forms.Init(e); を追加
– MainPage.xaml に using:Xamarin.Forms.Platform.UWP を追加
– MainPage.xaml.cs に LoadApplication(new BoxDragXF.App()); を追加

すると動きます。

Android や iOS と同じようにドラッグさせたいので、Windows Phone 8.1 と同じように BoxExRenderer クラスを作ります。

[assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.UWP
{
    class BoxExRenderer : Xamarin.Forms.Platform.UWP.BoxViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
            this.ManipulationDelta += BoxExRenderer_ManipulationDelta1;
            this.ManipulationMode = Windows.UI.Xaml.Input.ManipulationModes.All;
        }

        private void BoxExRenderer_ManipulationDelta1(object sender, Windows.UI.Xaml.Input.ManipulationDeltaRoutedEventArgs e)
        {
            var el = this.Element as BoxViewEx;
            el.OnManipulationDelta(el, new BoxDragXF.ManipulationDeltaRoutedEventArgs(el, e.Delta.Translation.X, e.Delta.Translation.Y));
        }
    }
}

ほとんど、WinPhone 版と同じですね。
これが動くと、Windows IoT Core の UWP も同じように動くので、自作タブレットを作ったり、Surface を使った場合でも共通化できます。Android/iPhone/Surface の UI が Xamarin.Forms で一括で作ることができます。

動かしたときの動画はこちらです。

サンプルコード

https://github.com/moonmile/BoxDrag

にあります。

カテゴリー: 開発 | Xamarin.Forms でドラッグを実装しよう(UWP編) はコメントを受け付けていません

Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on WinPhone編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653
Xamarin.Forms でドラッグを実装しよう(Android編)
http://www.moonmile.net/blog/archives/7667
Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on Android編)
http://www.moonmile.net/blog/archives/7670
Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on iOS編)
http://www.moonmile.net/blog/archives/7723

の続きです。

最後は Windows Phone で動かします。
もともとの Xamarin.Forms のテンプレートを使っているので、Windows Phone 8.1 版になっていますが、おそらく Windows 10 用の UWP プロジェクトでも動くはずです。

Windows Phone のレンダラーを作る

BoxDragXF.WinPhone プロジェクトに BoxExRenderer クラスを追加します。Windows Phone/Mobile が一番簡単で(簡単なようにしたので)、そのまま ManipulationDelta メソッドを渡せば ok です。渡すときの引数は、疑似的なほうの ManipulationDeltaRoutedEventArgs オブジェクトなので、そこだけコンバートします。

 [assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.WinPhone
{
    class BoxExRenderer : Xamarin.Forms.Platform.WinPhone.BoxViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
            this.ManipulationDelta += BoxExRenderer_ManipulationDelta;
        }
        
        private void BoxExRenderer_ManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e)
        {
            var el = this.Element as BoxViewEx;
            el.OnManipulationDelta(el, new BoxDragXF.ManipulationDeltaRoutedEventArgs(el, e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y));
        }
    }
}

実行してみる

Hyper-V のエミュレーターで動かしてみます。これで、同じ Xamarin.Forms を使って、三機種(Android/iPhone/Windows Phone)で同じようにコントロールをドラッグできることがわかります。

カテゴリー: 開発 | Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on WinPhone編) はコメントを受け付けていません

Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on iOS編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653
Xamarin.Forms でドラッグを実装しよう(Android編)
http://www.moonmile.net/blog/archives/7667
Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on Android編)
http://www.moonmile.net/blog/archives/7670

の続きです。iOS 版は比較的簡単にできたので、Xamarin.iOS 版と Xamarin.Forms 版をいっぺんに作っていしまいます。

サンプルコード

https://github.com/moonmile/BoxDrag にあります。

Xamarin.iOS でドラッグを実装する

iOS のみで動くバージョンは、BoxDragApple.iOS というプロジェクトのほうです。以前は、ViewController にタップイベント(TouchesBegan等)を送っていたのですが、今回は Android と動きを合わせるために自前のコントロール BoxViewEx を作ります。

partial class BoxViewEx : UIView
{
    public BoxViewEx (IntPtr handle) : base (handle)
    {
    }
    public override void TouchesBegan(NSSet touches, UIEvent evt)
    {
        base.TouchesBegan(touches, evt);
        UITouch touch = touches.AnyObject as UITouch;
    }
    public override void TouchesMoved(NSSet touches, UIEvent evt)
    {
        base.TouchesMoved(touches, evt);
        UITouch touch = touches.AnyObject as UITouch;
        var newPoint = touch.LocationInView(this);
        var previousPoint = touch.PreviousLocationInView(this);
        nfloat offsetX = previousPoint.X - newPoint.X;
        nfloat offsetY = previousPoint.Y - newPoint.Y;
        this.Frame = new CoreGraphics.CGRect(
            this.Frame.X - offsetX,
            this.Frame.Y - offsetY,
            this.Frame.Width,
            this.Frame.Height);
    }
    public override void TouchesEnded(NSSet touches, UIEvent evt)
    {
        base.TouchesEnded(touches, evt);
    }
}

ちょっとはまりどころがあって、Xamarin.iOS でユーザーコントロールを作るときは、

  1. Storyboard で、BoxViewEx を記述してコントロールを作る。
  2. コードのほうで、partial 付きの BoxViewEx クラスが作られる。
  3. 生成された BoxViewEx.cs を使う

という手順が必要です。先に BoxViewEx クラスを作ったり、partial 無しで作ったのですがうまく動きません。おそらく Storyboard のデザイナで自前コントロールを作ったときに、何らかの ID が割り振られるのでしょう。

BoxViewEx クラスは、UIView を継承しただけなので普通に Storyboard で扱えます。

タッチイベントによるドラッグは、TouchesMoved メソッドをオーバーライドすれば ok です。移動したときの差分を取って Frame プロパティに位置を設定します。

Xamarin.Forms on iOS でドラッグを実装する

iOS オンリー版ができたので、Xamarin.Forms で動くようにします。
Forms のある PCL プロジェクトは Android で作ったときのままにしておいて、BoxDragXF.iOS プロジェクトに、
BoxExRenderer クラスを追加します。

using BoxDragXF;
using BoxDragXF.iOS;
using System;
using System.Collections.Generic;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using Foundation;
using UIKit;

 [assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.iOS
{
    class BoxExRenderer : BoxRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
        }
        public override void TouchesBegan(NSSet touches, UIEvent evt)
        {
            base.TouchesBegan(touches, evt);
            UITouch touch = touches.AnyObject as UITouch;
        }
        public override void TouchesMoved(NSSet touches, UIEvent evt)
        {
            base.TouchesMoved(touches, evt);
            UITouch touch = touches.AnyObject as UITouch;
            var newPoint = touch.LocationInView(this);
            var previousPoint = touch.PreviousLocationInView(this);
            nfloat dx = newPoint.X - previousPoint.X;
            nfloat dy = newPoint.Y - previousPoint.Y;
            var el = this.Element as BoxViewEx;
            el.OnManipulationDelta(el, new ManipulationDeltaRoutedEventArgs(el, dx, dy));
        }
        public override void TouchesEnded(NSSet touches, UIEvent evt)
        {
            base.TouchesEnded(touches, evt);
        }
    }
}

書き方は、Android の時と同じですね。iOS の場合は、移動距離の差分がすでに計算されているので、そのまま OnManipulationDelta メソッドを呼び出せば ok です。

実行してみる

これを動かしてみたのが、この動画です。

Renderer を切り替えるだけで、Android と iOS が同じように Xamarin.Forms で動くことがわかります。似たような感じで、独自のコントロールを作って、PCL 側で共通で動かすということができる訳です。

カテゴリー: C#, Xamarin | Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on iOS編) はコメントを受け付けていません

ふと GoF のデザインパターンを再考しておく

懐かしの パタンランゲージ を眺めつつ、ふと 再考: GoF デザインパターン が気になったのでさらっと流しておこう。

[amazonjs asin=”4306041719″ locale=”JP” title=”パタン・ランゲージ―環境設計の手引”]

そもそもが GoF 発祥自体が 90年頃で、日本語の書籍は Java が発生した頃と前後してしまっているので、オブジェクト思考ブーム、UML, パターンのブームよりも随分前からある。C# や Java よりも前になので、今さら再考する意味もないのではないか?と思われるのだが(そもそも Qiita の「再考~」の記事自体が 2014 年だ)、歴史経緯から考えるのと、GoF の反省(あるいはドツボ?)からできあがった現在の C#, Java の言語拡張をみていくと、これらのパターンが言語仕様やライブラリに含まれてしまっていることが分かる。逆に言えば、.NET ライブラリを使うときに「?」とか「何でこんなややこしい使い方になっとるんやッ!!!」というときには、ここの GoF を踏まえたライブラリづくりがあって現在があるという経緯をを知ると、なんとなく納得がいくと思う。良くも悪くも、最適化(準かもしれないけど)されつつあるということだ。

それは、アルゴリズムのテクニックとして、双方向リストやらソートやらが流行っていた時期があって、今だと、List<> や Sort() で十分な訳で、実用的にはそれでよいし、でも知識として知っておくとその延長がわかるという仕掛けが含まれている。

というわけで、GoF を現在の C#, .NET クラスライブラリと合わせてさらっていこうと思う。

Abstract Factory

Factory パターンの汎用版で、C# だとストリームを作るときに使ってる。UWP の File.Open あたりがそれ。

20160111_03

Builder

なんとか Builder が好きな人が多くて StringBuilder とかに出てくる。

20160111_04

Factory Method

いわゆるファクトリーパターンで、Java だと結構頻発する。C# だと TaskFactory.StartNew にみられる。new するときにパラメータが多くなるのを嫌がったパターンと、DOM で Parent を Document にするときに作るときに使われる。

Abstract Factory と比べると、Factory/Creator クラスが二重化されていることがわかる。

20160111_31

Prototype

見かけたことがない…というか、普通にインターフェース。

20160111_06

Singleton

static で十分やろ、ってな感じで使われている。

20160111_32

Adapter

継承を使ってクラスを拡張する代わりに、あらかじめ拡張するメソッドをインターフェースにしておく手法。メッセージングのときに、後付けで必要なインターフェースを追加するときに使う。データ自体と操作するメソッドを分離するときによこう使われる。が、最初からわかっていれば、Interface で十分な気も。

20160111_08

Bridge

今だと普通に委譲を使うのだが、GoF の発祥自体が Java, C# よりも前なのだ。C++ と同時期だと思えば、そういうパターンがあっても仕方がない。継承地獄を避けるためにつくられる。今となっては常識になったパターン…だが、.NET クラスライブラリには歴史的にか継承地獄が頻発する。

20160111_10

Composit

今でいえば、DOM そのもの。ツリー構造を使いたいときは、このパターンを使うか、DOM を使う。

20160111_11

Decorator

元クラスがぴよぴよクラスのときに、継承して拡張するか、メンバにして拡張するか、のパターン。

20160111_12

Facade

残念ながら .NET クラスライブラリにはこの発想はない。既定クラスからばらばらとメソッド&プロパティが公開されている。Android の ViewGroup へのキャストがそれ。あるいは、ごちゃごちゃになったクラスをクライアントから見れば整理するパターン。ユーティリティクラスがそれ。

20160111_13

Flyweight

データベースのコネクションプーリングで使われている。

20160111_14

Proxy

いわゆる、アスペクト指向のパターンなのだが、C# の UI コントロールのメソッドが常に override と event を持っているのが proxy の役割になっている。あらかじめ仕込んでおかないといけないのがミソ。

20160111_15

Chain of Responsibility

複数のオブジェクトを連ねていくパターン。クリップボードのチェーンや、昔の常駐プログラムのキーチェーンで使われていた。いわゆる協調パターン。

20160111_16

Command

MVVM パターンにも出て来るおなじみの、ICommand インターフェースになる

20160111_17

Interpreter

いわゆる、式木だが、普通の人は使わない。動的にスクリプトを使うとか一部の関数型言語マニアが使う。F# だと簡単に作れる。

20160111_18

Iterator

いわゆる C++ のイテレーターだし、C# の IEnumerator だ。今だと、LINQ を使ったほうが楽。

20160111_20

Mediator

リモートメッセージで使われる SOAP とか JSON とかがそれにあたる。DCOM, CORBA を知っていればシリアライズという形で現れる。

20160111_23

Memento

いわゆる、Undo/Redo に使われるパターンなのだが、これを使った実装は見たことがない。そもそも Undo/Redo 自体が難しくて、今だとメモリのある限り丸ごと保存してしまう。例えば、Visual Studio をコンテナとして Undo/Redo メソッドとして利用する、ような感じ。

20160111_24

Observer

これも MVVM パターンで使われる ObservableCollection<T> の元ネタになる。

20160111_25

State

switch や if で実装しないために State パターンを使う。インターフェースを作っておくと、無限種類でオブジェクトを作れるので、State パターンは便利なのですよ。5 個程度ならば swtich、それ以上であれば State パターンを使えばよい。

20160111_26

Strategy

かつて openssl で使っていたパターンで、暗号コードの構造体を切り替える。ちなみに openssl は C言語だ。部分的に切り替えるのが State で、ごっそり切り替えるのが Strategy になる。ただし、.NET クラスライブラリに Strategy という単語はでてこなくて、Factory あたりでごっそりクラス名を切り替えたりする。ストリームクラスあたりがそれ。

20160111_27

Template Method

interface だったり、abstract だったりするが、言語仕様として Interface は内部フィールドを持たず、Abstract は内部データを持てる、という違いがあったりする。言語の都合なのと、IDE の補完機能を使って自動メソッドを使うときに便利なパターンだ。

20160111_28

Visitor

このあたりは、ほとんど delegate と一緒だったり、Action<>, Func<> で大丈夫だったりするが、異なるスレッドの場合はディスパッチが発生するため、このパターンが使われる?たぶん、C# だと Invoke になると思う。

20160111_29

 

 

改めて見直すと、コンテナとオブジェクトのパターンが多い。「パターン」自体が、頻出されるモデルという意味合いなので、かつてのオブジェクト指向以前であれこれと悩んだ末に出てきた定型パターンと考えられえる。定型だからこそ、現在の言語仕様やライブラリではそれらのパターンを取り入れてしまって、外側に出ないようにする。そういう意味では、GoF のパターンは形骸化しているといってよい。が、新しいプログラム環境で作ろうと思ったり、もっと大きい範囲でパターンを考える(ロボット制御の ROS とか、スマートフォンや PC などのメッシュ的な通信とか)ときには、これらのパターンが有効に働くので、知識として覚えておくとよいだろう。

おまけ

astah で作った GoF の図を置いてきます。

参考先

[amazonjs asin=”B000SEIBB8″ locale=”JP” title=”Design Patterns: Elements of Reusable Object-Oriented Software (Adobe Reader)”]

 

カテゴリー: 開発 | ふと GoF のデザインパターンを再考しておく はコメントを受け付けていません

Xamarin.Forms でドラッグを実装しよう(Xamarin.Forms on Android編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653
Xamarin.Forms でドラッグを実装しよう(Android編)
http://www.moonmile.net/blog/archives/7667

の続きで、前回の Xamarin.Android を Xamarin.Forms で動くようにします。
Android だけの場合であれば Xamarin.Android で閉じてしまえばよいのですが、iOS も同時に動かしたいのと、View に XAML の構文を使いたい(最終的には MVVM パターンに落とし込みたい)ので、Xamarin.Forms を使います。
ただし、ここでは Android の Touch イベントを持ってくるだけなので iOS では動きません…が、インターフェースは揃えておきましょう。

本来ならば、

Xamarin.Formsでタッチイベントを処理するには?(iOS/Androidの各種ジェスチャー対応)
http://www.buildinsider.net/mobile/xamarintips/0035

にあるように GestureDetector.SimpleOnGestureListener のスタイルを取りたいところなのですが、なぜかタップ位置が消えてしまっているので自作をします。このリスナークラスには GestureDetector.IOnGestureListener というインターフェースが用意されているので、頑張ればこのスタイルに沿って自作できないこともないのですが…どうも、なんか回りくどいので、別な方式をとります。
Xamarin.Forms の場合、PCL にあるコントロールと、それぞれのプラットフォームにあるコントロールのレンダラーがワンセットになっているので、直接レンダラーから PCL のコントロールにコールバックしてしまいます。こうすると無駄な Listener クラスが無くなって構造が簡単になります。まあ、Listener パターンは汎用性を広げるために使っているので、場合によりけりということです。

サンプルコード

サンプルコードは、https://github.com/moonmile/BoxDrag にあります。
BoxDragXF フォルダが Xamarin.Forms で作ったテストアプリ一式になります。

PCL に MainPage.xaml を作る

共通化される PCL プロジェクトに MainPage.xaml を追加します。

<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot; ?>
<ContentPage xmlns=&quot;http://xamarin.com/schemas/2014/forms&quot;
             xmlns:x=&quot;http://schemas.microsoft.com/winfx/2009/xaml&quot;
             xmlns:local=&quot;clr-namespace:BoxDragXF;assembly:BoxDragXF&quot;
             x:Class=&quot;BoxDragXF.MainPage&quot;>
  <StackLayout>
    <Button Text=&quot;初期化&quot; x:Name=&quot;button1&quot; ></Button>
    <Grid>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width=&quot;1*&quot; />
        <ColumnDefinition Width=&quot;1*&quot; />
        <ColumnDefinition Width=&quot;1*&quot; />
        <ColumnDefinition Width=&quot;1*&quot; />
      </Grid.ColumnDefinitions>
      <Button Text=&quot;上&quot; x:Name=&quot;buttonUp&quot;></Button>
      <Button Text=&quot;下&quot; x:Name=&quot;buttonDown&quot; Grid.Column=&quot;1&quot;></Button>
      <Button Text=&quot;左&quot; x:Name=&quot;buttonLeft&quot; Grid.Column=&quot;2&quot;></Button>
      <Button Text=&quot;右&quot; x:Name=&quot;buttonRight&quot; Grid.Column=&quot;3&quot;></Button>
    </Grid>
    <AbsoluteLayout
      x:Name=&quot;layout&quot;
      BackgroundColor=&quot;Blue&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalOptions=&quot;FillAndExpand&quot;>
      <local:BoxViewEx
        BackgroundColor=&quot;Red&quot;
        x:Name=&quot;box1&quot; WidthRequest=&quot;60&quot; HeightRequest=&quot;60&quot; AbsoluteLayout.LayoutBounds=&quot;100,100,60,60&quot; />
    </AbsoluteLayout>
  </StackLayout>
</ContentPage>

ドラッグ対象のコントロールは BoxView を継承した BoxViewEx コントロールを使います。このカスタムコントロールを参照するために、”clr-namespace:BoxDragXF;assembly:BoxDragXF” でロードできるようにします。

最初の BoxViewEx クラスは BoxView を継承しただけの空っぽのクラスです。

public class BoxViewEx : BoxView
{
}

Android プロジェクトにレンダラーを追加する

BoxViewEx コントロールに対応するレンダラーを追加します。レンダラーは各プラットフォームの描画をすると同時に、各プラットフォーム特有の動作を記述できます。

最初の BoxExRenderer クラスは、こんな感じになります。


 [assembly: ExportRenderer(typeof(BoxViewEx), typeof(BoxExRenderer))]
namespace BoxDragXF.Droid
{
    class BoxExRenderer : BoxRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);
        }
    }
}

ExportRenderer 属性は、PCL 側にあるコントロールの本体とレンダラーを結び付ける属性で記述が必要です。
この時点で、一度ビルドと実行をして、エラーが無いようにしておきます。

  • XAML の local:BoxViewEx の記述
  • BoxViewEx クラスの記述
  • BoxExRenderer クラスの記述

の3つがワンセットになります。

レンダラーに、タッチイベントを追加する

Xamarin.Android で作成した Touch イベントをそのまま移植します。
レンダラーから、本体のコントロールクラスを参照するときは Element プロパティを参照します。ここでは BoxView クラスになっていますが、実体は BoxViewEx です。
Android で描画する View オブジェクトは、sender として渡されてくるので、これを Android.Views.View にキャストをします。もちろん、必要であれば対応する Android の View コントロールにキャストして大丈夫です。

class BoxExRenderer : BoxRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
    {
        base.OnElementChanged(e);
        this.Touch += BoxExRenderer_Touch;
    }

    float _gx, _gy; // 初期の相対値
    float _ox, _oy; // 前回の絶対位置
    /// <summary>
    /// タッチイベント
    /// </summary>
    /// <param name=&quot;sender&quot;></param>
    /// <param name=&quot;e&quot;></param>
    private void BoxExRenderer_Touch(object sender, TouchEventArgs e)
    {
        var box = sender as Android.Views.View;
        switch (e.Event.Action)
        {
            case MotionEventActions.Down:
                // 初期の相対値を保存
                _gx = e.Event.GetX();
                _gy = e.Event.GetY();
                break;
            case MotionEventActions.Move:
                // 移動距離を計算
                float dx = e.Event.RawX - _ox;
                float dy = e.Event.RawY - _oy;
                // コールバック呼び出し
                // TODO: delta 方式なのか誤差が大きい
                var el = this.Element as BoxViewEx;
                el.OnManipulationDelta(el, new ManipulationDeltaRoutedEventArgs(sender, dx, dy));
                break;
            case MotionEventActions.Up:
                break;
        }
        // 現在の絶対位置を保存
        _ox = e.Event.RawX;
        _oy = e.Event.RawY;
    }
}

先の Xamarin.Android と異なるのは、PCL 側にある BoxViewEx クラスの OnManipulationDelta メソッドを直接呼び出していることです。ここでリスナーパターンを使うこともできるのですが、面倒なので直接メソッドを呼び出します。C# の場合は event を使ってもよいでしょう。

引数として渡す ManipulationDeltaRoutedEventArgs クラスは共通で利用する PCL プロジェクトに記述します。このクラスはプラットフォームに依存しないように作ります。名前が UWP 風にしてあるのは、最終的に Windows ユニバーサルアプリ風に MVVM を作るためです。

PCL プロジェクト内の BoxViewEx クラスでドラッグ処理をする

疑似的な ManipulationDeltaRoutedEventArgs クラスを作っておいて、Android 側から通知される OnManipulationDelta メソッドを用意しておきます。

public class BoxViewEx : BoxView
{
    public virtual void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
    {
        var rc = this.Bounds;
        rc.X += e.Delta.Translation.X;
        rc.Y += e.Delta.Translation.Y;
        this.Layout(rc);
    }
}

public class ManipulationDeltaRoutedEventArgs
{
    public ManipulationDeltaRoutedEventArgs(object source, double deltaX, double deltaY)
    {
        this.OriginalSource = source;
        this.Delta = new Delta_()
        {
            Translation = new Delta_.Translation_()
            {
                X = deltaX,
                Y = deltaY
            }
        };
    }
    public Delta_ Delta { get; set; }
    public object OriginalSource { get; set; }
    public class Delta_
    {
        public Translation_ Translation { get; set; }
        public class Translation_
        {
            public double X { get; set; }
            public double Y { get; set; }
        }
    }
}

Xamarin.Forms 側でコントロールの位置設定は Layout メソッドを使えば ok です。移動距離が Delta.Translation.X,Y として渡されるのは UWP と同じです。というか、そういう風に作ります。

実行してみると

実行してみると、マウスの位置とコントロールがかなりズレていますがきちんとドラッグできていることがわかります。このずれは、Xamarin.Android の時と同じように差分移動をさせているための誤差です。

デバッグ出力をさせるとわかりますが、Touch イベントが Xamarin.Android の時よりも頻繁に入ります。このためなのか、Android 側の GetX,Y の値が float 型であるものの、Xamarin.Forms が double 型になっていること、最終的な描画(レンダラーのほう)は int 型になっていることなどに影響されていると思います。このあたり、誤差が出ない方式をあとで考えていきます。

ちなみに UWP(Windows のユニバーサルアプリ)で Delta.Translation を使ったときはずれが発生しません。できることならば、うまく調節していきたいところです。

カテゴリー: C#, Xamarin | 1件のコメント

Xamarin.Forms でドラッグを実装しよう(Android編)

Xamarin.Forms でドラッグを実装しよう(のうりん編)
http://www.moonmile.net/blog/archives/7653

の続きで「2.Xamarin.Android 上で Touch イベントを使って実装」のところをメモ書きします。一応、Xamarin.Forms on Android までは動いたので、あとは微調整(ドラッグしているときの位置に誤差が含まれているので)と iOS 対応版を作るところですね。

どうも Xamarin.Forms の場合はリスナーを使ってあれこれやるっぽいのですが、C# の場合はリスナーを使うよりもイベントやデリゲートを使う方が楽なので、そっちのほうに流れます。さらにいうと、Xamarin.Forms でネイティブのイベントハンドラを拾う(iOS/Android編) でネイティブのコントロールに直接バインドしてしまって、レンダラー無しのパターンでもよいかもしれません。こっちのほうは先行き実装ということで。

Xamarin.Android を Touch イベントでドラッグ

Xamarin.Forms を直接弄る前に、Xamarin.Android を使って C# でのドラッグを確認しておきます。コードとしては Java から C# へコンバートすれば良いので比較的簡単ではあるのですが、Xamarin.Forms で動かすことを考えて、Android/iOS/WinPhone のイベントを共通化するために、疑似的に ManipulationDelta を使って動かせるようにします。ストアアプリ/UWP の場合には、コントロールのドラッグイベントを ManipulationDelta を使って移動差分を利用するとコントロールの位置指定簡単になるのです。

画面的にこんな風になります。

  • 初期化ボタンで、最初の位置にジャンプさせる。
  • 上下左右ボタンで、20ドットずつ移動
  • 黄色のコントロールをドラッグする

初期化と上下左右ボタンは、Layout の位置チェックに使っています。最終的には移動先の座標を保存しておいて復元することも視野にいれるので、ドラッグ先の位置や整列の方法も調べておく必要があります。

TextViewで GetX,Y と RawX,Y を表示させていますが GetX,Y は正確な値ではありません。
ちなみに

  • GetX(), GetY() がコントロールに対する相対位置
  • RawX, RawY がタップの絶対位置(画面の左上が基準点)

という違いがあります。

ややこしいですが、こんな図の関係になります。box がドラッグするコントロールで、layout が box を載せているコンテナコントロールです。ここでは、RelativeLayout を使います。
タップした位置は RawX,Y で取れますが、ドラッグするコントロールは box.Left, box.Top のように layout の相対位置に直さねばいけません。また、タップした相対位置は、Touch イベントでとれるのですが、GetX,GetY はタップ対象コントロールの相対位置になるので、この場合は、box に対しての相対位置になります。

相対位置で計算するがベストなのですが、タップしたときの相対位置が、ドラッグさせて移動させてしまうコントロールそのものに対しての相対位置なので話がややこしくなります。このあたり本来ならば、コンテナのほううで Touch イベントを取るほうがよいのですが、Xamarin.Forms のレンダラーの制限のために、この方法になっています。まあ、ちょっとこのあたりはあとで調節ですね。

画面の axml

TextView コントロールをドラックさせるため、RelativeLayout の中に張り付けます。
コンテナに対しての相対位置は layout_marginTop と layout_marginLeft で指定しておきます。

<?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?>
<LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    android:orientation=&quot;vertical&quot;
    android:layout_width=&quot;fill_parent&quot;
    android:layout_height=&quot;fill_parent&quot;>
    <Button
        android:text=&quot;初期化&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/button1&quot; />
    <LinearLayout
        android:orientation=&quot;horizontal&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/linearLayout1&quot;>
        <Button
            android:text=&quot;上&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonUp&quot; />
        <Button
            android:text=&quot;下&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonDown&quot; />
        <Button
            android:text=&quot;左&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonLeft&quot; />
        <Button
            android:text=&quot;右&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;match_parent&quot;
            android:id=&quot;@+id/buttonRight&quot; />
    </LinearLayout>
    <TextView
        android:text=&quot;GetX,Y: 000,000&quot;
        android:textAppearance=&quot;?android:attr/textAppearanceMedium&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/textView1&quot; />
    <TextView
        android:text=&quot;RawX,Y: 000,000&quot;
        android:textAppearance=&quot;?android:attr/textAppearanceMedium&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;wrap_content&quot;
        android:id=&quot;@+id/textView2&quot; />
    <RelativeLayout
        android:minWidth=&quot;25px&quot;
        android:minHeight=&quot;25px&quot;
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;
        android:id=&quot;@+id/relativeLayout1&quot;>
        <TextView
            android:text=&quot;Box&quot;
            android:textAppearance=&quot;?android:attr/textAppearanceLarge&quot;
            android:layout_width=&quot;60dp&quot;
            android:layout_height=&quot;60dp&quot;
            android:id=&quot;@+id/box1&quot;
            android:layout_marginTop=&quot;100dp&quot;
            android:layout_marginLeft=&quot;100dp&quot;
            android:background=&quot;#ffbb33&quot; />
    </RelativeLayout>
</LinearLayout>

OnCreate で初期化する

OnCreate メソッドで、画面が作成されたときにボタンのイベントを追加しておきます。
RelativeLayout 内での座標の指定は、RelativeLayout.LayoutParams と SetMargins メソッドを使います。このあたり使い方がややこしいですよね。RelativeLayout.LayoutParams の部分は、コンテナで利用する layout によって変える必要があります。

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    // Set our view from the &quot;main&quot; layout resource
    SetContentView(Resource.Layout.Main);

    // Get our button from the layout resource,
    // and attach an event to it

    var text1 = this.FindViewById<TextView>(Resource.Id.textView1);
    var text2 = this.FindViewById<TextView>(Resource.Id.textView2);
    var box = this.FindViewById<View>(Resource.Id.box1);
    var btn1 = this.FindViewById<Button>(Resource.Id.button1);

    // 位置を初期化
    btn1.Click += (s, e) =>
    {
        setPos(50, 50);
        dispPos();
    };
    // 位置移動
    this.FindViewById<Button>(Resource.Id.buttonLeft).Click += delegate
    {
        setPos(box.Left-20, box.Top);
        dispPos();
    };
    this.FindViewById<Button>(Resource.Id.buttonRight).Click += delegate
    {
        setPos(box.Left + 20, box.Top);
        dispPos();
    };
    this.FindViewById<Button>(Resource.Id.buttonUp).Click += delegate
    {
        setPos(box.Left, box.Top - 20);
        dispPos();
    };
    this.FindViewById<Button>(Resource.Id.buttonDown).Click += delegate
    {
        setPos(box.Left, box.Top + 20);
        dispPos();
    };
    box.Touch += Box_Touch;
    // OnManipulationDelta を設定
    this.ManipulationDelta += OnManipulationDelta;
}
// 位置指定
void setPos( int x, int y )
{
    var box = this.FindViewById<View>(Resource.Id.box1);
    // var lp = new RelativeLayout.LayoutParams(box.Width, box.Height);
    if ( _box_w == 0 || _box_h == 0 )
    {
        _box_w = box.Width;
        _box_h = box.Height;
    }
    var lp = new RelativeLayout.LayoutParams(_box_w, _box_h);
    lp.SetMargins(x,y, 0, 0);
    box.LayoutParameters = lp;
}

ちなみに、box.Top, box.Left は get/set の両方があるので設定も可能なのですが、これに値を入れると Width/Height も自動的に変わってしまいます。Width/Height は get しかないので謎な仕様です。

タップは、Touch イベントに結び付けます。ManipulationDelta のほうはテスト用に作ったものです。

Touch イベント

ドラッグ時のタップイベントは、MotionEventActions.Down/Move/Up の処理をします。
box の新しい位置を決定するときに、直接、絶対座標の GetX,GetY を使って指定すれば良いのですが、コンテナやドラッグ対象のコントロールの位置指定が相対座標のためうまくいきません。仕方がないので、前回の絶対位置を保持しておいて差分を使ってコントロールの位置決めをします。

private void Box_Touch(object sender, View.TouchEventArgs e)
{
    var box = sender as View;
    switch( e.Event.Action )
    {
        case MotionEventActions.Down:
            // 初期の相対値を保存
            _gx = e.Event.GetX();
            _gy = e.Event.GetY();
            _box_w = box.Width;
            _box_h = box.Height;
            break;
        case MotionEventActions.Move:
            // 移動距離を計算
            float dx = e.Event.RawX - _ox;
            float dy = e.Event.RawY - _oy;
            // 移動
            // TODO: 誤差で少しずれるが実用上問題ない
            // setPos((int)box.Left + (int)dx, (int)box.Top + (int)dy);
            // OnManipulationDelta(sender, new ManipulationDeltaRoutedEventArgs(sender, (int)dx, (int)dy));
            // イベント呼び出し
            if ( ManipulationDelta != null )
            {
                ManipulationDelta(sender, new ManipulationDeltaRoutedEventArgs(sender, (int)dx, (int)dy));
            }

            break;
        case MotionEventActions.Up:
            break;
    }
    // 現在の絶対位置を保存
    _ox = e.Event.RawX;
    _oy = e.Event.RawY;
}

差分を使ったときの欠点は、誤差が蓄積されてしまうことです。絶対座標の GetX,GetY は float 型なのですが、box の Left/Top は int 型です。このあたりがずれているために、前回の位置(int型)+ 変化量(float型)が、位置(int型)に丸められてしまって、だんだんと誤差が溜まってしまいます。タップした位置に合わせるように誤差を補正する方法が必要でしょう。これはあとで考えます。

疑似的な ManipulationDelta

移動量を差分で計算する場合は、誤差が溜まるという欠点はあるのですが、設定する座標の計算が楽になります。単純に前回の位置に移動量を足せば、今回の位置になるからです。これが、Windowsストアアプリ/UWP で使われている方法で、ManipulationDelta を使って加算していきます。

どうせ、Xamarin.Forms で 3つのプラットフォームを共通化しなければいけない(WPFも合わせると4つのプラットフォームになる)ので、疑似的に ManipulationDelta を使えるようにしてしまいます。
名前だけ合わせるために、無理矢理 ManipulationDeltaRoutedEventArgs クラスを作り、OnManipulationDelta メソッドが扱えるようにします。どうせ C# なのだから、コーディングスタイルも UWP に合わせてしまうほうがいいですよね。

    public class ManipulationDeltaRoutedEventArgs
    {
        public ManipulationDeltaRoutedEventArgs( object source, int deltaX, int deltaY )
        {
            this.OriginalSource = source;
            this.Delta = new Delta_()
            {
                Translation = new Delta_.Translation_()
                {
                    X = deltaX,
                    Y = deltaY
                }
            };
        }
        public Delta_ Delta { get; set; }
        public object OriginalSource { get; set; }
        public class Delta_
        {
            public Translation_ Translation { get; set; }
            public class Translation_
            {
                public int X { get; set; }
                public int Y { get; set; }
            }
        }
    }
    public virtual void OnManipulationDelta( object sender, ManipulationDeltaRoutedEventArgs e )
    {
        var el = e.OriginalSource as View;
        int x = el.Left + e.Delta.Translation.X;
        int y = el.Top + e.Delta.Translation.Y;
        // left, top は同時に設定する必要あり
        // RelativeLayoutHelper.SetLeft(el, x);
        // RelativeLayoutHelper.SetTop(el, y);
        RelativeLayoutHelper.SetXY(el, x, y);
    }

    void setPos(int x, int y)
    {
        var box = this.FindViewById<View>(Resource.Id.box1);
        // var lp = new RelativeLayout.LayoutParams(box.Width, box.Height);
        if (_box_w == 0 || _box_h == 0)
        {
            _box_w = box.Width;
            _box_h = box.Height;
        }
        var lp = new RelativeLayout.LayoutParams(_box_w, _box_h);
        lp.SetMargins(x, y, 0, 0);
        box.LayoutParameters = lp;
    }
}
class RelativeLayoutHelper
{
    public static void SetLeft( View el, int left)
    {
        var lp = new RelativeLayout.LayoutParams(el.Width, el.Height);
        lp.SetMargins(left, el.Top, 0, 0);
        el.LayoutParameters = lp;
    }
    public static void SetTop(View el, int top)
    {
        var lp = new RelativeLayout.LayoutParams(el.Width, el.Height);
        lp.SetMargins(el.Left, top, 0, 0);
        el.LayoutParameters = lp;
    }
    public static void SetXY(View el, int left, int top)
    {
        var lp = new RelativeLayout.LayoutParams(el.Width, el.Height);
        lp.SetMargins(left, top, 0, 0);
        el.LayoutParameters = lp;
    }
}

ManipulationDelta 方式を使うときには、キャンバス(Canvas)を使って、Canvas.SetLeft と Canvas.SetTop が頻発するので同じようなものを作っておきます。RelativeLayoutHelper.SetXY とすることで、座標を指定できます。試しに SetLeft と SetTop を作ってみたのですが片方ずつ設定することができませんでした。たぶん、設定するタイミングと View で反映されて再計算されるタイミングが違うからですね。仕方がないので、両方設定するときは SetXY を使います。

サンプルコード

そんな訳で、Xamarin.Android を使って TextView をドラッグさせることができました。更に、疑似的に ManipulationDelta を使うようにしたので Xamarin.Forms で共通化できそうです。

https://github.com/moonmile/BoxDrag/tree/master/BoxDrag.Android

では、引き続き Xamarin.Forms のほうも。

カテゴリー: C#, Xamarin | Xamarin.Forms でドラッグを実装しよう(Android編) はコメントを受け付けていません