vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

【C#】Listを検索してList(またはIEnumerable)を返したい

はじめに

この記事は C# Advent Calendar 2017 の21日目の記事です。

qiita.com

すでに何度かネタにしている Effective C# から。

第四章の29項目で、 Collection を作って返すよりも Iterator メソッドを返した方が良い、というものがあります。

理由としては遅延評価であることや、呼び出す側で都合が良いように加工しやすい、といったところがあるようです。

ただそこで気になったのがパフォーマンス。

例えばデータを保持するクラスの List があったとして、
その中の1要素だけを取ってきて for や foreach で処理したい。

この時、下記の条件で実行すると実行速度や GC Alloc のデータ量にどの程度違いがあるのでしょうか。

  1. あらかじめ対象の要素だけを持った List を作成しておき、それを返す
  2. IEnumerable を使って Iterator メソッドとして返す
  3. IEnumerable を使って Iterator メソッドとして返す (Linq を使用)
  4. 対象の要素だけを持った List をメソッド内で生成し、それを返す
  5. 対象の要素だけを持った List をメソッド内で生成し、それを返す (Linq を使用)

  6. 1.について、 List を生成する時間は計測せず、 List を返すメソッドの実行時間のみを計測します。

感覚的には番号順通りに速い、という気がしますがどうでしょうか。

実行環境

  • Windows 10 Pro 64bit
  • Unity 2017.2.0f3

Unity を使うのは、計測に Profiler を使いたいのと、
重いデータとして 3D モデルを持たせるようにしたいためです。

データを格納するクラス

名前は特に気にしない方向で...。

Breadman.cs

using System.Collections.Generic;
using UnityEngine;

public class Breadman{
    public int Id { get; set; }
    public GameObject BreadmanModel { get; set; }
    public List SpecialMoves { get; set; }
}

こんな感じでデータを入れておきます。
また検証 1. のための List も作成します。

MainController.cs

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class MainController : MonoBehaviour {
    public GameObject BreadmanModel1;
    public GameObject BreadmanModel2;
    public GameObject BreadmanModel3;
    ~省略~
    private List breadmen;
    private List breadmanNames;
    
    private void Start () {

        breadmen = new List {
            new Breadman {
                Id = 0,
                BreadmanModel = BreadmanModel1,
                SpecialMoves = new List {
                    "パンチ",
                    "キック",
                }
            },
            new Breadman {
                Id = 1,
                BreadmanModel = BreadmanModel2,
                SpecialMoves = new List {
                    "空を飛ぶ",
                }
            },
            new Breadman {
                Id = 2,
                BreadmanModel = BreadmanModel3,
                SpecialMoves = new List {
                    "水に濡れる",
                }
            },
            new Breadman {
                Id = 3,
                BreadmanModel = BreadmanModel4,
                SpecialMoves = new List {
                    "力が出ない",
                }
            },
            new Breadman {
                Id = 4,
                BreadmanModel = BreadmanModel5,
                SpecialMoves = new List {
                    "新しいの",
                }
            }
        };
        // データを複製して増やす.
        var breadmen2 = new List(breadmen);
        breadmen.AddRange(breadmen2);
        var breadmen3 = new List(breadmen);
        breadmen.AddRange(breadmen3);
        var breadmen4 = new List(breadmen);
        breadmen.AddRange(breadmen4);
        var breadmen5 = new List(breadmen);
        breadmen.AddRange(breadmen5);

        breadmanNames = breadmen.Select(breadman => breadman.BreadmanModel.name).ToList();
        ~省略~
    }
    ~省略~
}

値を生成するメソッド

検証対象となる、値を生成するメソッドです。

前述の 1. ~ 5. に加えて、2. の Foreach ではなく For を使うもの(検証 2-1)、
さらにそれを逆転させたもの (検証 2-2) を追加しています。

MainController.cs

    // 検証1. あらかじめ対象の要素だけを持った List を作成しておき、それを返す.
    private List GetBreadmanNames_CreatedList() {
        return breadmanNames;
    }
    // 検証2. IEnumerable を使って Iterator メソッドとして返す.
    private IEnumerable GetBreadmanNames_IEnumerable() {
        foreach (var breadman in breadmen) {
            yield return breadman.BreadmanModel.name;
        }
    }
    // 検証3. IEnumerable を使って Iterator メソッドとして返す (Linq を使用).
    private IEnumerable GetBreadmanNames_IEnumerable_Linq() {
        return breadmen.Select(breadman => breadman.BreadmanModel.name);
    }
    // 検証4. 対象の要素だけを持った List をメソッド内で生成し、それを返す.
    private List GetBreadmanNames_Create() {
        var newBreadmanNames = new List();
        foreach (var breadman in breadmen) {
            newBreadmanNames.Add(breadman.BreadmanModel.name);
        }
        return newBreadmanNames;
    }
    // 検証5. 対象の要素だけを持った List をメソッド内で生成し、それを返す (Linq を使用).
    private List GetBreadmanNames_Create_Linq() {
        return breadmen.Select(breadman => breadman.BreadmanModel.name).ToList();
    }
    // 検証2-1. IEnumerable を使って Iterator メソッドとして返す.
    // ForeachではなくForを使って返してみる.
    private IEnumerable GetBreadmanNames_IEnumerable_For() {
        for(var i = 0; i < breadmen.Count; i++) {
            yield return breadmen[i].BreadmanModel.name;
        }
    }
    // 検証2-2. IEnumerable を使って Iterator メソッドとして返す.
    // 順番が逆になってしまうが、速度・GC Allocを計測してみる.
    private IEnumerable GetBreadmanNames_IEnumerable_For_Reverse() {
        for(var i = breadmen.Count - 1; i >= 0; i--) {
            yield return breadmen[i].BreadmanModel.name;
        }
    }

計測する"

ボタンのクリックイベントを利用して計測してみます。

MainController.cs

~省略~
public class MainController : MonoBehaviour {
    ~省略~
    public Button Sample1Button;
    public Button Sample2Button;
    public Button Sample3Button;
    public Button Sample4Button;
    public Button Sample5Button;
    public Button Sample6Button;
    public Button Sample7Button;
    ~省略~
    private void Start() {
        ~省略~
        // 検証1. あらかじめ対象の要素だけを持った List を作成しておき、それを返す.
        Sample1Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach(var breadmanName in GetBreadmanNames_CreatedList()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
        // 検証2. IEnumerable を使って Iterator メソッドとして返す.
        Sample2Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach (var breadmanName in GetBreadmanNames_IEnumerable()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
        // 検証3. IEnumerable を使って Iterator メソッドとして返す (Linq を使用).
        Sample3Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach (var breadmanName in GetBreadmanNames_IEnumerable_Linq()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
        // 検証4. 対象の要素だけを持った List をメソッド内で生成し、それを返す.
        Sample4Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach (var breadmanName in GetBreadmanNames_Create()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
        // 検証5. 対象の要素だけを持った List をメソッド内で生成し、それを返す (Linq を使用).
        Sample5Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach (var breadmanName in GetBreadmanNames_Create_Linq()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
        // 検証2-1. IEnumerable を使って Iterator メソッドとして返す(For使用).
        Sample6Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach (var breadmanName in GetBreadmanNames_IEnumerable_For()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
        // 検証2-2. IEnumerable を使って Iterator メソッドとして返す(Forを逆転させて使用).
        Sample7Button.onClick.AddListener(() => {
            Profiler.BeginSample("ProfileSampling");
            foreach (var breadmanName in GetBreadmanNames_IEnumerable_For_Reverse()) {
                Debug.Log("name " + breadmanName);
            }
            Profiler.EndSample();
        });
    }
    ~省略~

起動 -> ボタンを5回押す -> Profiler の結果を見る 、という内容で確認してみました。

結果

結果は下記の通り。

f:id:mslGt:20171224115042j:plain

やっぱりというか、あらかじめ作成済みの List を返すのが速いようです。

ただ、どのケースを見ても GC Alloc のデータ量は変わらなかったのは少し驚きました。
( List を生成する方がデータ量が多そうだったので)

また、 Foreach や For を使った場合と Linq を使う場合とを比較すると、
最初の一回は Foreach や For を使うほうが速く、それ以降は Linq の方が速い傾向がありました。

ただ速度に関しては、このあと何度か試すと必ずしも上記の通りとはならなかったので、
(検証1. も他と同じくらいの速度になったり)
どの方法を採るか、というのはもう少し検証が必要そうです。

おわりに

少なくとも今回の内容からすると、検証1. の List として持たせるのが速度を最優先するなら良さそうです。

ただ、それぞれを List に、とかし始めるとバグの温床にもなりそうですし、
そもそもデータを格納するためのクラスを作る意味もなくなってくるという。。。

またキャッシュしておくデータの量も増えてしまいますね。

メソッド内で生成する検証2. ~ 検証7. を見ると、ほとんど変わらないように思います。

ということで、検証2. か検証3. の方法で IEnumerable を返して、
呼び出し元で都合が良いように加工する、というのが良さそうです。

やっぱり実際測ってみると意外な結果が見えたりして面白いですね。

最後に、検証のネタに使った Breadman を載せておきます。

www.instagram.com

参照

2017年のお買い物

はじめに

早いものでもう年の瀬ですね。

Twitterなどで2017年に買ったものを紹介している方をちらほら見かけたので、
私も便乗してみます。

なおここで取り上げる基準は、2017年に購入し、そこそこ以上に良いと感じ、かつ私が覚えているものとなります。

良く言えば厳選された内容をお届けしますw

GRAPEVINE「ROADSIDE PROPHET」

GRAPEVINE 20周年となるアルバム。

相変わらず良いです。
突飛なことをするわけでもないけどマンネリ化してるわけでもなく。

今回はミドルテンポの曲が多く、何度も聴く内にじんわりとしみ込んできた感じです。

特に「楽園で遅い朝食」が大好きです。

www.grapevineonline.jp

なぜかディスコグラフィに「ROADSIDE PROPHET」が入ってないので公式ページのトップを貼っておきます。

PC用のバッテリー

ここ2年程はマウスコンピューターのラップトップを使っているのですが、
その交換用のバッテリーを購入しました。

大体充電なしで3, 4時間くらいは使用できるのですが、
長い勉強会などになると心元ない、ということで買い足したという経緯です。

もちろんノート等に書き残すのも良いのですが、
話をメモしつつキーワードを元に調べ物をしつつTweetしつつ、となると難しいので。。。

まだどのぐらいで交換するのがベストかなど試行錯誤しているところですが、
多少長い時間使う場合も、バッテリーを心配しなくて良い、というのはとても良いですね。

なおマウスコンピューターのバッテリー購入は、公式Webページのお問い合わせから。

www.mouse-jp.co.jp

JetBrains All Productsライセンス

これがあれば JetBrains 社の IDE 使い放題!!というやつですね。

C#IDE である Rider の正式版がリリースされたのを機に購入しました。

これと Visual Studioプラグインである ReSharper(C#, C++) 、Java や Kotlin の IntelliJ IDEA は、
すでに無いとコード書くのが厳しいくらい頼りにしています。

まぁあまり依存するのも良くないとは思いつつも、
強力な入力補完で自分の書きたい内容をポンポン書き進められるのは気持ちが良いです。

Effective C#

C# 6 対応版の第三版です。

play.google.com

まだ一度しか読んでいないため「理解した!」とは言い難いのですが、
繰り返し読んでものにできればなぁ、と。

現状では List を返すメソッドで、List をメソッド内で生成して返すのではなく IEnumerable を返して、
メソッド呼び出し元で Foreach で値を取り出す、というのが場合によっては便利だなと感じています。

あとこの本に限りませんが、 Google Play Books だと PC でもスマホでもログインさえすれば見られる、
というのがとても便利です。

家だと大きな画面で見られる方が良いので。

あと英語翻訳でいうと、単語だけでなく熟語も翻訳できるのはとても助かります。

いちいち文字を手打ちで翻訳にかけるのは結構大変なので(;'∀')

難点としては自分で電子書籍をアップロードするときの 200MB 制限がなければなぁ。。。と思うところです。
まぁ自分だけがそれを利用するわけでもないので、仕方がないとは思うのですが。

Office 365 Solo

年間サブスクリプションで購入しました。

もとは OneDrive のストレージ容量を 1TB に増やしたかったからなのですが、
Office 365 も含まれているということで、 PowerPoint でスライド資料を作ったりするのにも活躍してくれています。

やっぱり PC でもスマホでも自動で同期してくれて、最新のものが見られるというのは便利ですね(*'▽')

あと OneDrive は、保存するものがスマホで撮った写真や epub や pdf の電子書籍くらいなので、
ほとんどストレージのことを気にせずに良くなったのがありがたいです。

うかつに PC のローカルフォルダと全データを同期すると、
PC のストレージが死んでしまいかねないのは注意が必要ですが(;'∀')

iPhone X

買いました。

使い勝手はまぁ iPhone だなぁ、という感じで、
画面下から上へのスワイプでホーム画面に戻るなどの独自操作も少しずつ慣れてきている感じです。

特筆すべきは何といっても TrueDepth カメラ。

Unity に ARKit のプラグインがあり、それを使うとかなり簡単に顔のトラッキングができます。

https://www.assetstore.unity3d.com/jp/#!/content/92515

今のところは顔のところに何か三次元ででてきて面白い以上のことはできていないのですが、
例えば顔の向きに合わせてページを移動するとか、 操作 UI などで利用してみても面白そうです。

また、ランニングやサイクリングをトラッキングして Google Fit に送信するとか、
設定時間に近づくと事前に(当日のみ)オフにできる目覚まし機能などは少なくとも標準ではなさそうなので、
Xamarin の学習がてら作ってみても面白いなぁと思っています(実現できるとは言っていない)。

Windows 10 Proライセンス

まだ買ったばかりでほとんど使用できていませんが、
Docker や Hololens の Emulator に Hyper-V が必要、ということだったので Home からアップグレードしました。

今の調子だと今年は手が出せそうにありませんが、
来年頭くらいから色々触ってみたいなと思っています。

おわりに

ざっと思いつくところを挙げてみましたが、結構ありますね。

ソフトウェアやライセンスが多い感じ。

掛けた費用を回収するという意味でも、来年もバリバリ使って楽しんでいきたいと思います。

Xamarin.FormsでXAMLと仲良くしたい話 その2

はじめに

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 の15日目の記事です。

qiita.com

そろそろ終盤にさしかかってきました。
最初は無理かな~とも思っていましたが、ここまではちゃんと続いていてすごいです!

ここまできたら、できれば最後まで完走したいですねぇ。

さて今回は、前回の続きとして RelativeLayoutに触れてみたいと思います。

サンプルを見てみる

RelativeLayout は、 Android の ConstraintLayout や iOS の AutoLayout のように、
要素の表示位置やサイズに制約を付けてレイアウトを組むものです。

といっても分かりづらいので、公式のサンプルを見てみます。

https://developer.xamarin.com/guides/xamarin-forms/user-interface/layouts/relative-layout/

< RelativeLayout>
    < BoxView Color="Red" x:Name="redBox"
        RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
            Property=Height,Factor=.15,Constant=0}"
        RelativeLayout.WidthConstraint="{ConstraintExpression
            Type=RelativeToParent,Property=Width,Factor=1,Constant=0}"
        RelativeLayout.HeightConstraint="{ConstraintExpression
            Type=RelativeToParent,Property=Height,Factor=.8,Constant=0}" />
    < BoxView Color="Blue"
        RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=redBox,Property=Y,Factor=1,Constant=20}"
        RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=redBox,Property=X,Factor=1,Constant=20}"
        RelativeLayout.WidthConstraint="{ConstraintExpression
            Type=RelativeToParent,Property=Width,Factor=.5,Constant=0}"
        RelativeLayout.HeightConstraint="{ConstraintExpression
            Type=RelativeToParent,Property=Height,Factor=.5,Constant=0}" />
< /RelativeLayout>

ぎょえ~って感じがしますが、一つ一つ見てみましょう。

各 BoxView には大きく分けて4つの制約が付与されています。

  • XConstraint : X 軸方向の表示位置の制約
  • YConstraint : Y 軸方向の表示位置の制約
  • WidthConstraint : 幅の制約
  • HeightConstraint : 高さの制約

なおこれらの制約は順番を変えても問題ないようです。

また全ての制約を付与する必要はなく、いくつかだけを指定することも可能です。
(一つも指定しない場合は左上にデフォルトのサイズ(たぶん)で表示されました)

で、それぞれの制約の中を見てみると、同じような項目が設定されています。

  • ConstraintExpression
  • Type
  • ElementName (下のBoxViewのみ)
  • Property
  • Factor
  • Constant

ConstraintExpression は制約を付与する式を定義するよ~、というためのものだと思いますので、
それ以外の項目について見てみます。

Type

その要素の位置やサイズを、何をベースに決めるかを指定します。

指定できるのは下記の2種類です。

  • RelativeToParent : 親要素をベースにする
  • RelativeToView : 特定の要素をベースにする(要素の指定は ElementName で行う)

XConstraint、 YConstraint、 WidthConstraint、 HeightConstraint の四つをどちらかに統一する必要はなく、
それぞれ指定することができます。

ElementName

ちょっと順番をとばして ElementName です。

Type で書いた通り、Type を RelativeToView にしている場合に、ベースとなる要素を指定します。

ElementName が無い場合

その制約を指定しない場合と同じ状態で表示されます。

Type を RelativeToParent にして ElementName を指定した場合

特に変化はなさそうです。

存在しない要素名を ElementName に指定した場合

実行時に unhandled exception が発生します。

循環参照

2つの要素の Type を RelativeToView にして、ElementName にお互いの名前を指定した場合、
実行時に unhandled exception が発生します。

自分の名前を ElementName に指定した場合

実行時に unhandled exception が発生します。

ElementName の間違いによるエラーは、特に複雑なレイアウトの場合はやらかしがちだと思うので、
注意が必要ですね。。。

Property

制約のベースとなる親要素のプロパティを指定します。

例えば上の BoxView (RedBox) の子となる下の BoxView (BlueBox) を、
RedBox の右下に配置したい場合、下記のように書くことができます。

< RelativeLayout x:Name="Outline">
    < BoxView Color="Red" x:Name="RedBox"
        RelativeLayout.WidthConstraint="{ConstraintExpression
        Type=RelativeToParent,Property=Width,Factor=0.5,Constant=0}"
        RelativeLayout.HeightConstraint="{ConstraintExpression
        Type=RelativeToParent,Property=Height,Factor=0.5,Constant=0}" />

    < !-- X座標値をRedBoxのWidthに、Y座標値をRedBoxのHeightにする -->  
    < BoxView Color="Blue" x:Name="BlueBox"              
        RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
        ElementName=RedBox, Property=Height, Factor=1,Constant=0}"
        RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView,
        ElementName=RedBox, Property=Width, Factor=1,Constant=0}" />
< /RelativeLayout>

結果は以下の通りです。

f:id:mslGt:20171215012715j:plain

Factor

Property で指定した親要素の値 * Factor + Constant の値が、子要素の値として設定されます。

例えば XConstraint で、Type=RelativeToParent、 Property=Width、 Factor=0.5 とした場合、
親要素の Width の 0.5 倍の値が X 座標値として設定されます。

マイナス、1より大きい数を指定することもできます。
ただし、当然ながら親要素が0の場合は変わりません。

Constant

オフセット値です。

マイナスの値を指定することもできます。

Width、 Height では、 0より大きい値を指定すると要素は大きくなり、
0未満の値を入れると小さくなります。

レイアウトしてみる

なんとなく並べてみました。

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.View.MainPage">
    < RelativeLayout x:Name="Outline">
        < !--これをベースに他の要素を配置 -->
        < BoxView Color="Red" x:Name="RedBox"
                 RelativeLayout.WidthConstraint="{ConstraintExpression
            Type=RelativeToParent,Property=Width,Factor=0,Constant=100}"
                 RelativeLayout.HeightConstraint="{ConstraintExpression
            Type=RelativeToParent,Property=Height,Factor=0,Constant=100}" />

        < BoxView Color="Blue" x:Name="BlueBox"
                 RelativeLayout.WidthConstraint="{ConstraintExpression
            Type=RelativeToView,Property=Width,Factor=1,ElementName=RedBox}"
                 RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Height,Factor=1}"
                 RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Width,Factor=1}"
                 RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Height,Factor=1}" />

        < BoxView Color="Yellow" x:Name="YellowBox"
                 RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox, Property=Width,Factor=1}"
                 RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Height,Factor=1}"
                 RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Height,Factor=2}"
                 RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Width,Factor=2}" />
        
        < BoxView Color="Green" x:Name="GreenBox"
                 RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox, Property=Width,Factor=1}"
                 RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Height,Factor=1}"
                 RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Height,Factor=3}"
                 RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToView,
            ElementName=RedBox,Property=Width,Factor=3}" />
        
        < Button BackgroundColor="Aquamarine" BorderRadius="180" Opacity="0.5"
                Text="世界さんちーっす"
                RelativeLayout.WidthConstraint="{ConstraintExpression Type=RelativeToParent,
            Property=Width, Factor=0.3}"
                RelativeLayout.HeightConstraint="{ConstraintExpression Type=RelativeToParent,
            Property=Width, Factor=0.3}"
                RelativeLayout.XConstraint="{ConstraintExpression Type=RelativeToParent,
            Property=Width,Factor=0.4, Constant=0}"
                RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent,
            Property=Height,Factor=0.3, Constant=0}"/>
    < /RelativeLayout>
< /ContentPage>
  • ボタンでは BorderRadius で角を丸めることができ、180 にすると丸くすることができます。
    ただし上記の状態だと、ハイライト時は四角く表示されてしまいます。
  • Opacity を指定することで、透明度を設定することができます。
    値は 0.0 ~ 1.0 です。
  • ボタンの位置を画面中央にするのは、(画面サイズ / 2) - (ボタンサイズ / 2) の計算が必要なため、
    XAMLではなく、コードビハインドで指定する必要があるようです。
    (Constant に上記の書いたところ、エラーが発生しました)

f:id:mslGt:20171215013242j:plain

おわりに

親要素のサイズ・位置を決めてそれをベースに他の要素をレイアウトする RelativeLayout は、
特に多様なディスプレイサイズへの対応が必要となるであろう Xamarin.Forms で大活躍してくれそうです。

とはいえデザインが固まる前から制約を正しく付与していくのはなかなか大変そうだったり(慣れの問題かもですが)、
内容によっては前回試していた StackLayout や GridView の方がシンプルに書けることもあると思うので、
上手く使い分けていきたいところです。

さて、アドベントカレンダー、明日は yakumomo さんです。

よろしくお願いいたします~m(__)m。

参照

Xamarin.FormsでXAMLと仲良くしたい話 その1

はじめに

この記事は [初心者さん・学生さん大歓迎!] Xamarin その1 Advent Calendar 2017 の14日目の記事です。

qiita.com

とりあえず起動はするし、ボタンを押すとHello Worldと出力するとか、
簡単な動きくらいは作れる気がしてきました。

しかしながら、見た目がどうにもなのは悲しい。

ということで、今回は UI の部分について調べてみることにしました。

なお、 Xamarin.Forms ではボタンなどをコードで追加することもできますが、
今回は XAML を中心に進めてみます。

デフォルトで生成されるXAMLを見てみる

まず Blank App でプロジェクトを作成して、デフォルトの XAML を見てみます。

MainPage.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSample"
             x:Class="XamlSample.MainPage">

    < Label Text="Welcome to Xamarin.Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />

< /ContentPage>
  • 「xmlns:local="clr-namespace:XamlSample"」の行は、(少なくとも)デフォルトでは使用していないようで、
    削除してしまっても特にエラーは起きませんでした。
  • この記事を書く少し前のバージョンまで Label は ContentPage.Content と StackLayout の中に入れられていましたが、
    仕様の変更があったようです。( PCL から .Net Standard に変更したことには関係がなさそうなので)

ContentPage に対して コードの表示 を行うと、 コードビハインド (MainPage.xaml.cs) が開きます。

要は XAML ファイルのルート要素とコードビハインドのクラスとが紐づいているので( x:Class="XamlSample.MainPage" で指定)、
コードから XAML 要素にアクセスすることが可能になるのですね。

Xamarin.Forms で使用できる Layout は下記の通り。

ボタンなどのコントロール類は、 WPF などと(多分)ほぼ同じです。
自動で各プラットフォームらしい見た目にしてくれるのはすごいですね!

xmlnsとxmlns:xについて

「xmlns="http://xamarin.com/schemas/2014/forms"」で XAML の使用を宣言しています。

「xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"」で「x:Class」や「x:Name」などのプロパティが使用可能になります。

x は変更可能で、「xmlns:x= ~ 」を「xmlns:xx= ~ 」のように変更すると「xx:Class」と書くことができるようになります。

また xmlns:x では Name など XML でも定義されているプロパティがありますが、
これはそのプロパティをどの要素に対しても使用可能にするために定義されているようです。

ContentPage直下のLayout

適当にいじっていた時におや?と思ったところですが、 ContentPage 直下におけるLayoutは一つだけのようです。
そのため、下記のように書いてしまうと、上に書いた StackLayout の内容が表示されませんでした。

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.View.MainPage">
    < StackLayout>
        < Label Text="Welcome to Xamarin.Forms!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center" />
    < /StackLayout>
    < AbsoluteLayout>
            < BoxView WidthRequest="100" HeightRequest="100" BackgroundColor="YellowGreen"/>
    < /AbsoluteLayout>
< /ContentPage>

コードで要素を追加する

例えば Layout 内の要素は決まっていて、中のデータだけが変化する場合は DataBinding を使うのが良さそうです。

ただ、条件によって要素ごと差し替えたい場合だと難しそうです。

ということで、コードから要素を追加してみます。

追加対象の親要素を取得する

まずは要素を追加する対象となる、親の要素を取得してみます。

前述の通り、 XAML ファイルの要素とコードビハインドのクラスは紐付けられており、
コードビハインドで this.Content とすれば、 XAML ファイルの ContentPage.Content にアクセスできます。

ということで、あとは対象の要素を指定して、そこに子となる要素を追加するだけです。

MainPage.xaml

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.MainPage">
    < StackLayout x:Name="Outline">
        < Label Text="Welcome to Xamarin.Forms!"
            VerticalOptions="CenterAndExpand" 
            HorizontalOptions="CenterAndExpand" />
    < /StackLayout>
< /ContentPage>

※2017.12.14 21:30更新

親要素を取得するのに FindByName を使用していましたが、
XAML で 親要素に x:Name で名前を付けている場合、
そのまま x:Name でつけた名前でコードビハインドでもアクセスできるそうです。

処理も減ってシンプルになりますね(*'▽')

田淵さん、ありがとうございますm(__)m

Before

// 親要素となる StackLayout.
var layout = this.FindByName< StackLayout>("Outline");
layout.Children.Add(addContent);

After

// 親要素となる StackLayout.
Outline.Children.Add(addContent);

MainPage.xaml.cs

using Xamarin.Forms;

namespace XamlSample
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();

            // 追加する要素.
            var addContent = new Label
            {
                Text = "Label from code",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            };
            // 親要素となる StackLayoutに要素を追加.
            Outline.Children.Add(addContent);
        }
    }
}
  • これで「Welcome to Xamarin.Forms!」というラベルの下に、
    「Label from code」というラベルが追加されます。

レイアウトしてみる StackLayout

それでは、いくつか Layout を試してみます。

StackLayout

  • 縦方向または横方向に要素を並べます (Orientation で切り替え)。
  • 何も指定しない場合、縦方向に並べる場合は横幅いっぱい、横方向の場合は縦幅いっぱいに要素が表示されます。
  • StackLayout だけを使って縦横組み合わせたい場合は、 StackLayout を入れ子構造にします。
  • ただし縦方向に並べる StackLayout と横方向に並べる StackLayout を同階層に置いてしまうとエラーになります。

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.View.MainPage">
    < StackLayout BackgroundColor="Azure" Orientation="Vertical" VerticalOptions="Start">
        < StackLayout Orientation="Horizontal" Spacing="0">
            < BoxView BackgroundColor="Yellow" WidthRequest="100" />
            < StackLayout Orientation="Vertical" BackgroundColor="Brown" HorizontalOptions="FillAndExpand">
                < Label Text="世界さん" TextColor="White"/>
                < Label Text="ちーっす!" TextColor="White" />
            < /StackLayout>
        < /StackLayout>
        < StackLayout Orientation="Horizontal" Spacing="0">
            < BoxView BackgroundColor="Yellow" WidthRequest="100" />
            < StackLayout Orientation="Vertical" BackgroundColor="Brown" HorizontalOptions="FillAndExpand" Spacing="0">
                < Label Text="Hello" TextColor="White" Margin="3, 3, 3, 3"/>
                < Label Text="World!" TextColor="White" Margin="3" />
            < /StackLayout>
        < /StackLayout>
    < /StackLayout>
< /ContentPage>
  • 要素同士はデフォルトでスペースが含まれるので、なくしたい場合は Spacing を "0" にする必要があります。

結果はこんな感じになります。

f:id:mslGt:20171214000906j:plain

レイアウトしてみる GridView

次は格子状にレイアウトできる GridView を試してみます。

GridView では行数・列数とサイズを指定し、要素と配置する場所を指定します。

< ?xml version="1.0" encoding="utf-8" ?>
< ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSample.View.MainPage">
    < Grid VerticalOptions="FillAndExpand">
        
        < !-- 行の指定. 今回は一番下の行が30固定、残りが半分ずつ使うことになります -->
        < Grid.RowDefinitions >
            < RowDefinition Height="1*"/>
            < RowDefinition Height="Auto"/>
            < RowDefinition Height="30"/>
        < /Grid.RowDefinitions >
        < !-- 列の指定. Widthを指定していないので3分割 -->
        < Grid.ColumnDefinitions>
            < ColumnDefinition />
            < ColumnDefinition />
        < /Grid.ColumnDefinitions>
        < !-- StackLayoutを入れたり-->
        < StackLayout VerticalOptions="FillAndExpand"
                     HorizontalOptions="FillAndExpand"
                     BackgroundColor="Yellow">
            < Label Text="世界さんちーっす" />
        < /StackLayout>
        < !-- GridViewを入れたり-->
        < Grid VerticalOptions="FillAndExpand"
                  Grid.Row="0"
                  Grid.Column="1" >
            < Grid.RowDefinitions >
                < RowDefinition Height="Auto"/>
                < RowDefinition Height="Auto"/>
            < /Grid.RowDefinitions >
            < Grid.ColumnDefinitions>
                < ColumnDefinition />
                < ColumnDefinition />
            < /Grid.ColumnDefinitions>
            < BoxView Color="Aqua" 
                     Grid.Row="0"
                     Grid.Column="0" />
            < BoxView Color="Maroon" 
                     Grid.Row="1"
                     Grid.Column="0" />
            < BoxView Color="Navy" 
                     Grid.Row="1"
                     Grid.Column="1" />
            < BoxView Color="Silver" 
                     Grid.Row="0"
                     Grid.Column="1" />
        < /Grid>
        < BoxView Color="Maroon" 
                 Grid.Row="1"
                 Grid.Column="0" />
        < BoxView Color="Navy" 
                 Grid.Row="1"
                 Grid.Column="1" />
        < BoxView Color="Purple" 
                 Grid.Row="2"
                 Grid.Column="0" />
        < BoxView Color="Lime" 
                 Grid.Row="2"
                 Grid.Column="1" />
    < /Grid>  
< /ContentPage>
  • Grid.Row 、Grid.Column を指定しない場合、デフォルトで Grid.Row: 0 、Grid.Column: 0 が設定されるため、
    複数あると最初の要素が隠れてしまいます。

上記のコードを実行するとこのようになります。

f:id:mslGt:20171214001007j:plain

複雑なレイアウトを行う場合は、行番号・列番号の指定がややこしそうですね。。。

BoxViewについて

これまでも登場している BoxView ですが、これは四角形を表示するものです。

GridView などと同じように、中に Label などの要素を入れられるのかな?と思いきやエラーになります。

エラーになる

< BoxView Color="Silver">
    < Label Text="世界さん、ちーっす" />
< /BoxView>

なのであくまでも四角形を表示するためのもの、ということのようです。

装飾などに利用するのでしょうか。

おわりに

とりあえず StackLayout と GridView 、 BoxView に触れてみましたが、
まだ「なんでこんな大きさになるの。。。(´・ω・`)」となることも多いです。

まぁこういうのは慣れが重要だと思いますので、実際にアプリを作りながら慣れていきたいと思います。

次回は入りきらなかった RelativeLayout の予定。。。のはず。

参照

Xamarin.Forms

XAML

iPhoneでAndroid Wearと連携してみたメモ

はじめに

いきなりですが、メイン端末を iPhone にしました。

とはいってもまだ新しいものを買ったわけではなく、以前使っていた iPhone5s です。

iPhone にしようかなと思った理由は Google 先生が日本で Pixel2 が出てくれないし・・・というのもありますが、
iPhoneX で Prime sense の技術がとかいう話が、今更ながら気になってきたためです。

ただ、今まで Android 端末で過ごしてきただけに、
急に切り替えるとあれもできないこれもできない/(^o^)\とならないかと心配になったので、
とりあえず iPhone5s で試してみることにしました。

で、特に気になっていた Android Wear (Zenwatch2) が使えるのか問題を試してみたので、メモっておきます。

Zenwatch2との連携

Zenwatch2 には限りませんが、 Android Wear との連携は Android Wear アプリを使うことで可能です。

ただ、Android でやるように、ただ Bluetooth を On にすれば良いのではなく、
アプリを立ち上げて明示的に連携してやる必要があります。

Appleのアプリとの連携

それではまず iPhone に標準で入っているアプリとの連携について。

電話

電話がかかってきたことが表示され、受ける・または切ることができます。

SMS

メッセージをみることはできますが、返信をすることはできません。

Music、Podcast

曲名が表示され、再生・停止・スキップを行うことができます。

Clock

アラームが鳴っても通知されず、止めることもできません。

他のアプリとの連携

基本的にプッシュ通知は受けることができ、内容を見ることができます。

Hangout

テキストメッセージを見ることはできますが、画像のプレビューはできません。

Google Home(というか OK google)

反応しませんでした。

これができれば iOS アプリの Cortana と合わせて混とんとした感じになって面白かったのですが。。。
(どんな願望や。。)

Google Fit、Zenfit

iOS アプリがないので無理。。。かと思いきや、
実はこれらのアプリは Watch 単体で動作するため記録を取ることができます。
(GPS 情報は iPhone から取得)

iPhone は標準でトラッキングアプリが入っていないようだったのでどうしようかなと思っていたのですが、
これは助かりました。

おわりに

というわけで、絶望的かなと思われた Android Wear との連携ですが、
特に Android Wear の OS が 2.0 になったこともあって結構問題ありませんでした。

ただ他はともかくアラームが Watch でコントロールできないのは、
二度寝防止に朝大量にセットしている私としては結構痛い。。。

ま、今すぐ無いと困るってわけでもないので、 Xamarin の学習がてら自作するのも良いかもなぁ~などと思ってもいます。

あと、4年も前の端末が曲がりなりにもちゃんと使えるってのはすごいですねぇ。 開発者としてはさっさと替えて!って気もしなくもないですが苦笑。

コードを書く時間を確保したい話

はじめに

この記事は 子育てエンジニア・クリエイター Advent Calendar 2017 の11日目の記事です。

adventar.org

このブログに書き残している通り、プライベートの時間を使ってあれこれコードを書いてみたり試してみています。

が、子どもが生まれ、大きくなってくるにつれて時間を確保するのが難しくなってきました。
遊んで~と言ってくることももちろんですが、パソコン自体にも興味があるようで、 特に電源がついている状態だとキーボードを触りまくったりラップトップをパカパカ開いたり閉じたりし始めます/(^o^)\

そんな中で私がどのように時間確保しているかを書き残しておきます。

本当は最新のサービスとか、すごい技術とかで時間短縮してます!とかの方が良いのでしょうが、
いたって普通の方法に終止しています。期待した人残念でした。

子どもを寝かしつけたあとにする

前まではこの方法でした。

子どもが寝た後ならゆっくり時間もとれるし( ´∀`)bグッ!

と思っていたのですが、高確率で寝落ちしちゃうんですよね。。。orz

また、寝たばかりだとまだ眠りが浅く、結局起きてしまうことも。。。

ということで、(たまにはやりますが)基本的には夜遅くにやる、というのは諦めました。

早起きする

じゃあ逆に早起きすりゃいいんじゃね?というのが現在の方法です。

夜は子どもと一緒にさっさと寝る。

で、朝5時ごろに起きて、洗濯物を洗ったり出発の準備をしつつコードを書く、と。

最近は布団のやつがなかなか私を放してくれないので、6時ごろになってきてしまっていますが。。。

これだと出発の時間が決まっているので、ある程度集中してコードを書くことができる(気がする)
というメリットもあります。

大学卒業くらいまでは夜中まで起きてて昼まで寝てるみたいな生活していたのに、
変わるものだなぁ、と大学生当時の自分が見たら思うかもしれませんね。

その他

その他便利だなぁ~と思っているものとしては、電子書籍ですね。
それもスマホで見られるもの。

どうしても子どもと出かけるとなれば荷物は多いわけで、
そこに大きな本を追加するのはツラい。。。

紙の本の方が見やすかったりするメリットもわかるのですが。

電子書籍アプリの中でのお気に入りとしては、 Google Play Books です。

特に良いところが2つあって、1つ目が英語などを翻訳するとき、
単語だけではなく2,3単語組み合わせた熟語も翻訳ができることです。

そういうのは翻訳アプリに任せる、というのも手ですが、行ったり来たりしなくて済むならそちらの方が助かるので。

2つ目が、クレジットカードなどを登録しなくてもマルチプラットフォームに対応してくれているところです。
外ではスマホで良くても、家などではもっと大きな画面で見たい。

Google Play Books だと Web ブラウザでも見られるので助かります。

以上ステマでしたw

あとは勉強会に行く時間がとりづらいというところで、
Podcast や Channel 9 などの動画配信にも助けられています。

日本の勉強会でも、ストリーミング配信していたり、
VR 空間上や Twitter 上で行われるものがあったりしてすごいです。

直接会ったり聴いたりできる勉強会の良さはもちろんあるのですが、
地方在住だったり自由な時間が取りづらかったりする身としては大変助かります。

あと時間確保の上記以外の方法としては、
仕事帰りにもくもくする、というのがあります。

和歌山にも少ないながらもカフェがあります。

幸か不幸か残業代は見込み時間で支払われ、残業自体も多いので1時間くらいなら妻にバレることもありません(白目)

。。。嫌なオチになってしまった(´・ω・`)

おわりに

子どもがもう少し大きくなったら、あまり私とも遊んではくれなくなるんだろうなぁ、
と思うと今一緒に遊べる時間を大事にしたい。

とは思いつつ、やっぱり自分自身の興味に任せてあれこれ試したり学んだりする時間も欲しい。

ということで、両者を上手くバランス取れるようになれたらなぁ、と思っています。

妻との今年、そして来年の話

はじめに

この記事は 妻・夫を #愛してる ITエンジニア Advent Calendar 2017 - ADVENTAR の四日目の記事です。

adventar.org

妻とのこれまでや日常生活については去年も書いたので、これまでの話はしません。

mslgt.hatenablog.com

ここ一年から来年に向けての話をだらだらと書いてみたいと思います。

子どもとの生活

やっぱり良くも悪くも子どもが生活の中心になっちゃいますね。

休日など、どこへ行くにも3人。

もしくはたまには一人でゆっくり過ごしてねってことで、私と子どもの2人でとか。

私自身はそれでも良いかな、と思うのですが、妻としては私と2人でたまには、
とか思ってたりしないかな?と少し不安には思っています。

まぁもう少し子どもが大きくなってきたらそういう時間もとれるかな?とは思っているのですが。

正直子どもがわんぱくになってきたこともあり、
恋人同士というよりは戦友、といった意識になってきているところもありますw

恋愛感情云々はともかく、お互いを助け合って問題を乗り越えていく、
という点では共通項もあるのかなと。

おわりに

もう少しあれこれ書こうかな?と思っていたはずなのですが、
実際に書き始めるとあまり出てこなかったのでいったん締めます。
(後日書き足したりするかも)

最後に妻へ。

いつも帰りが遅くなってしまったりして、
大変な仕事を任せきりにしてしまってごめん。

来年は家族がもう一人増えてさらに大変になると思うけど、
僕もできるだけ時間を取るようにするので、一緒に頑張りましょう。

愛してます。