Core Animation: Xamarin.iOSと .MacでTickerを作ってみる


- Core Animationはタイマーで移動処理を書かなくていいので、理解すれば便利ではあるが結構難しいような

# iOS

- UIView.Animate(UIView.AnimateAsync)があるので簡単

## ViewDidLoad

~~~csharp

	public partial class Vision_TouchViewController : UIViewController
	{
	
		public override void ViewDidLoad ()
		{
			base.ViewDidLoad ();
			
			// Perform any additional setup after loading the view, typically from a nib.
			Prepare ();
			RunTicker ();
		}	
~~~
	

## Prepare: ラベルを作る

~~~csharp

		UILabel _label = null;


		void Prepare()
		{
			if (_label == null) {

				_label = new UILabel (
				    // 仮サイズで作る
					new System.Drawing.RectangleF (0, 200, 2000, 50)
				);
				
				// 赤地に白い文字でスクロールさせる
				_label.BackgroundColor = UIColor.Red;
				_label.TextColor = UIColor.White;
				
				// 文字はセンタリング
				_label.TextAlignment = UITextAlignment.Center;

				this.View.AddSubview (_label);
				this.View.LayoutSubviews ();

				MoveTo (true);        // スタートポジションに移動
			}
		}
~~~	


## MoveTo: ラベルを移動

~~~csharp

		void MoveTo(bool origin=false)
		{
		    // 現在時刻設定
			_label.Text = "Now " + DateTime.Now.ToLongDateString () + " "
				+ DateTime.Now.ToLongTimeString ();
		
		    // 文字領域取得
			var size = _label.StringSize (_label.Text, _label.Font);

            // ラベルリサイズ & 移動
			_label.Frame = new RectangleF (
				origin ? 0: -1.0f * (size.Width + View.Frame.Width),
				_label.Frame.Y,
				size.Width + 2 * View.Frame.Width , 
				size.Height
			);
		}
~~~

## RunTicker: Animationでスクロールさせる

~~~csharp

		public void RunTicker()
		{
			UIView.Animate (10.0,
				() => {
					MoveTo();           // 行き先へ移動(アニメーションあり)
				},
				() => {
					MoveTo(true);       // 開始地点に移動(アニメーション無し)
					RunTicker();        // 終わったらもう一回
				});
		}
~~~		

# Mac

## WindowDidLoad

- Xib で TickerLabelをNSTextFieldでOutletさせておいてる
- Windowのリサイズへの対応とかもろもろはTODO

~~~csharp

	public partial class MainWindowController : MonoMac.AppKit.NSWindowController
	{
	    //...
	    
		public override void WindowDidLoad ()
		{
			base.WindowDidLoad ();

			Prepare ();        // 準備
			RunTicker ();      // ラベルをスクロールさせる

		}

~~~

## Prepare

~~~csharp
		void Prepare()
		{
		    // 同じく赤地に白い文字
			TickerLabel.BackgroundColor = NSColor.Red;
			TickerLabel.TextColor = NSColor.White;
			TickerLabel.Alignment = NSTextAlignment.Center;

            // 現在時刻
			var now = DateTime.Now;
			TickerLabel.StringValue = "Now " + now.ToLongDateString () + " " +
				now.ToLongTimeString ();

            // 文字列幅
			var size = TickerLabel.AttributedStringValue.Size;

            // 初期値
			TickerLabel.Frame = 
				new System.Drawing.RectangleF (
					0.0f,
					TickerLabel.Frame.Y,
					size.Width + 2 * Window.Frame.Width, 
					size.Height * 2);
		}
~~~

## ラベルのロケーション

~~~csharp
		PointF StartLocation()
		{
			return	new PointF (
					0, TickerLabel.Frame.Y);
		}
			
		PointF StopLocation()
		{
			return	new PointF (
				-1 * (Window.Frame.Width + TickerLabel.AttributedStringValue.Size.Width), 
				TickerLabel.Frame.Y);
		}
~~~

## Tickerをスクロールさせる

~~~csharp
		void RunTicker()
		{
		    // 開始位置へ移動
			TickerLabel.SetFrameOrigin (StartLocation ());
		
		    // アニメーションを作成
			var animation = CreateTickerAnimation (
				                 StartLocation (), StopLocation ());
			
			// ラベルにアニメーション設定
			var key = new NSString (@"frameOrigin");
			var animations = NSDictionary.FromObjectAndKey (animation, key);
			TickerLabel.Animations = animations;

            // アニメーションが終わったら...
			animation.AnimationStopped += (object sender, CAAnimationStateEventArgs e) => {
				RunTicker ();        // もう一回！
			};
			
			// 開始
			((NSTextField)TickerLabel.Animator).SetFrameOrigin (StopLocation ());

		}
~~~				

### 移動のアニメーション

- CoreAnimationは難しい

~~~csharp
		CAKeyFrameAnimation CreateTickerAnimation(
			PointF fromp, PointF top, float duration = 6.0f, int rate = 10 )
		{
		    // パス作成
			var path = new CGPath ();

            //開始位置へ移動
			path.MoveToPoint(fromp);

            // rateの数だけ移動
			for (int index = 0; index <= rate; index++)
			{
				path.CGPathAddLineToPoint(
					fromp.X + (top.X - fromp.X ) / rate * index,
					top.Y);
			}
            //目的地へ移動
			path.MoveToPoint (top);
			
			// パス保存
			path.CloseSubpath();
		
		    // アニメーション作って返す
			return new CAKeyFrameAnimation () {
				Duration =duration,
				Path = path,
				RemovedOnCompletion = true,
			};
		}
~~~