[NUI] Fix not to use setter of AnimatedImageView.URLs (#2303)
[platform/core/csapi/tizenfx.git] / test / Tizen.NUI.Samples / Tizen.NUI.Samples / Samples / FrameCallbackTest.cs
1 using System;
2 using System.Threading.Tasks;
3 using Tizen.NUI;
4 using Tizen.NUI.BaseComponents;
5 using System.Collections.Generic;
6 using System.Linq;
7
8 namespace Tizen.NUI.Samples
9 {
10   enum TOUCH_ANIMATION_STATE
11   {
12     NO_ANIMATION = 0,
13     ON_ANIMATION,
14     ON_FINISH_ANIMATION,
15     END_ANIMATION = NO_ANIMATION
16   };
17
18   public class FrameCallbackTest : IExample
19   {
20
21     private static string resourcePath = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
22     private static string[] BACKGROUND_IMAGE_PATH = {
23       resourcePath + "/images/FrameCallbackTest/launcher_bg_02_nor.png",
24       resourcePath + "/images/FrameCallbackTest/launcher_bg_03_nor.png",
25       resourcePath + "/images/FrameCallbackTest/launcher_bg_04_nor.png",
26       resourcePath + "/images/FrameCallbackTest/launcher_bg_05_nor.png",
27       resourcePath + "/images/FrameCallbackTest/launcher_bg_06_nor.png",
28       resourcePath + "/images/FrameCallbackTest/launcher_bg_apps_nor.png"
29     };
30
31     private static string[] APPS_IMAGE_PATH = {
32       resourcePath + "/images/FrameCallbackTest/launcher_ic_culinary_nor.png",
33       resourcePath + "/images/FrameCallbackTest/launcher_ic_family_nor.png",
34       resourcePath + "/images/FrameCallbackTest/launcher_ic_ent_nor.png",
35       resourcePath + "/images/FrameCallbackTest/launcher_ic_homecare_nor.png"
36     };
37
38     private static string[] APPS_ICON_NAME = {
39       "Culinary",
40       "Family",
41       "Entertainment",
42       "Homecare"
43     };
44
45     private static string[] CONTROL_IMAGE_PATH = {
46       resourcePath + "/images/FrameCallbackTest/launcher_ic_apps_nor.png",
47       resourcePath + "/images/FrameCallbackTest/launcher_ic_settings_nor.png",
48       resourcePath + "/images/FrameCallbackTest/launcher_ic_viewinside_nor.png",
49       resourcePath + "/images/FrameCallbackTest/launcher_ic_timer_nor.png",
50       resourcePath + "/images/FrameCallbackTest/launcher_ic_internet_nor.png"
51     };
52
53     private static string[] CONTROL_ICON_NAME = {
54       "Apps",
55       "Settings",
56       "ViewInside",
57       "Timer",
58       "Internet"
59     };
60
61     private const int FRAME_RATE = 60;
62     private const int OBJECT_DELAY = 30;
63     private const int OBJECT_SIZE = 150;
64     private const int INITIAL_POSITION = 46;
65     private const int DEFAULT_SPACE = 9;
66     private const int DEVIDE_BAR_SIZE = 4;
67
68     private TextLabel text;
69     public class FrameCallback : FrameCallbackInterface
70     {
71       private int mTimeInterval;
72
73       private uint mContainerId;
74       private List<uint> mViewId; ///< Container of Actor IDs.
75       private List<float> mViewPosition;
76
77       // Movement is position difference of Container from start position of this animation to current position.
78       // Time interval between each mMovement entry is 16 milliseconds(about 60 fps)
79       // For example.
80       // mMovement[i] + mContainerStartPosition is the position of container after i*16 milliseconds from this animation started.
81       private List<float> mMovement;
82
83       // mLatestMovement is actually current movement of container.
84       private float mLatestMovement;
85
86       // An icon of mTouchedViewIndex moves with container at the same time.
87       // Each icon of (mTouchedViewIndex -/+ i) moves as following container after i*OBJECT_DELAY milliseconds.
88       private int mTouchedViewIndex;
89
90       // If every icon between mLeftIndext and mRightIndex is stopped, this frame callback can be reset.
91       // Then mIsResetTouchedViewPossible becomes true.
92       private bool mIsResetTouchedViewPossible;
93       private int mLeftIndex;
94       private int mRightIndex;
95
96       // Total animation time from start to current.
97       private float mTotalAnimationTime;
98       // Total animation time from start to the time that last movement is added.
99       private float mPreviousTotalAnimationTime;
100
101       // Start position of container in this animation.
102       private float mContainerStartPosition;
103       // Position of container at the time that last movement is added.
104       private float mPreviousContainerPosition;
105
106       // This animation only need to save movement about (the number of view * OBJECT_DELAY) milliseconds.
107       // and this size is updated when new view is added.
108       // and, If the list of mMovement size become larger than this size, remove unnecessary entries.
109       private int mRequiredMovementSize;
110       private bool mNeedUpdateMovementSize;
111
112       // current velocity.
113       private float mVelocity;
114       // dirty flag.
115       private bool mDirty;
116
117       public FrameCallback()
118       {
119         mViewId = new List<uint>();
120         mMovement = new List<float>();
121         mRequiredMovementSize = 0;
122         mNeedUpdateMovementSize = false;
123         mIsResetTouchedViewPossible = false;
124       }
125
126       public void ResetAnimationData()
127       {
128         SetLatestMovement(0.0f);
129         mMovement.Clear();
130         mTotalAnimationTime = 0.0f;
131         mPreviousTotalAnimationTime = 0.0f;
132         mDirty = true;
133       }
134
135       public void SetTimeInterval(int timeInterval)
136       {
137         mNeedUpdateMovementSize = true;
138         mTimeInterval = timeInterval;
139       }
140
141       public void AddId(uint id)
142       {
143         mViewId.Add(id);
144         mNeedUpdateMovementSize = true;
145       }
146
147       public void SetContainerId(uint id)
148       {
149         mContainerId = id;
150       }
151
152       public void SetContainerStartPosition(float position)
153       {
154         mContainerStartPosition = position;
155       }
156
157       public void SetLatestMovement(float movement)
158       {
159         mLatestMovement = movement;
160       }
161
162       public void ResetViewPosition()
163       {
164         mViewPosition = Enumerable.Repeat(0.0f, mViewId.Count).ToList();
165       }
166
167       public void SetViewPosition(int index, float position)
168       {
169         mViewPosition[index] = position;
170       }
171
172       public void SetTouchedViewIndex(int controlIndex)
173       {
174         mTouchedViewIndex = controlIndex;
175       }
176
177       public void AddMovement(float movement)
178       {
179         mMovement.Add(movement);
180       }
181
182       public void SetLeftIndex(int leftIndex)
183       {
184         mLeftIndex = leftIndex;
185       }
186
187       public void SetRightIndex(int rightIndex)
188       {
189         mRightIndex = rightIndex;
190       }
191
192       public bool IsResetTouchedViewPossible()
193       {
194         return mIsResetTouchedViewPossible;
195       }
196       public void Dirty()
197       {
198         mDirty = true;
199       }
200
201       public bool IsDirty()
202       {
203         return mDirty;
204       }
205
206       public float GetVelocity()
207       {
208         return mVelocity;
209       }
210
211       private void ComputeNewPositions(int totalTime)
212       {
213         // save latestMovement to avoid interference between thread.
214         float latestMovement = mLatestMovement;
215         bool isResetTouchedViewPossible = true;
216         for (int i = 0; i < mViewId.Count; ++i)
217         {
218           if (i == mTouchedViewIndex)
219           {
220             continue;
221           }
222
223           // compute delay of view of i.
224           int totalDelay = Math.Abs(i - mTouchedViewIndex) * OBJECT_DELAY;
225           if (totalDelay > totalTime)
226           {
227             continue;
228           }
229
230           int actorTime = totalTime - totalDelay;
231           int movementIndex = actorTime / mTimeInterval;
232           float factor = (float)(actorTime - (movementIndex * mTimeInterval)) / (float)mTimeInterval;
233           float movement;
234           if (movementIndex >= mMovement.Count - 1)
235           {
236             // 1. delay is zero(every view moves with container at the same time)
237             // 2. after every icons are stopped and the finger is stopped to move, the movement is still not added more.
238             // than the view has latestMovement.
239             movement = latestMovement;
240           }
241           else if (movementIndex < 0)
242           {
243             // If this animation is just staarted and the view need to wait more.
244             // movement is 0.
245             movement = 0.0f;
246           }
247           else
248           {
249             // Get the movement of ith view by interpolating mMovement
250             movement = factor * mMovement[movementIndex + 1] + (1.0f - factor) * mMovement[movementIndex];
251           }
252
253           // Prevent to overlap of each views.
254           float currentSpace = Math.Abs((mViewPosition[i] + movement) - (mViewPosition[mTouchedViewIndex] + latestMovement));
255           float minimumSpace = (float)Math.Abs(mViewPosition[i] - mViewPosition[mTouchedViewIndex]);
256           if (currentSpace < minimumSpace)
257           {
258             movement = latestMovement;
259           }
260
261           // check views in screen are still moving or stopped.
262           float newPosition = mViewPosition[i] + movement - latestMovement;
263           if (i >= mLeftIndex && i <= mRightIndex)
264           {
265             Vector3 previousPosition = new Vector3();
266             GetPosition(mViewId[i], previousPosition);
267             if (Math.Abs(previousPosition.X - newPosition) >= 1.0f)
268             {
269               isResetTouchedViewPossible = false;
270             }
271           }
272           // update new position.
273           SetPosition(mViewId[i], new Vector3(newPosition, 0.0f, 0.0f));
274         }
275         mIsResetTouchedViewPossible = isResetTouchedViewPossible;
276       }
277
278       public override void OnUpdate(float elapsedSeconds)
279       {
280         // second -> millisecond
281         mTotalAnimationTime += elapsedSeconds * 1000.0f;
282
283         Vector3 currentPosition = new Vector3();
284         GetPosition(mContainerId, currentPosition);
285
286         // Add new Movement, if there is change in position.
287         // 1. if dirty(there is reserved event)
288         // 2. if container position is changed.
289         // 3. if every icons in screen is stopped
290         if (mDirty || currentPosition.X != mPreviousContainerPosition || mIsResetTouchedViewPossible)
291         {
292           mDirty = false;
293           if (mTotalAnimationTime >= mMovement.Count * mTimeInterval)
294           {
295             // If the passed time is larger than mTimeInterval, add new movements.
296             // If we need to add more than one, compute each movement by using interpolation.
297             while (mMovement.Count <= mTotalAnimationTime / mTimeInterval)
298             {
299               float factor = ((float)(mMovement.Count * mTimeInterval) - mPreviousTotalAnimationTime) / (mTotalAnimationTime - mPreviousTotalAnimationTime);
300               float movement = (float)(factor * currentPosition.X + (1.0f - factor) * mPreviousContainerPosition) - mContainerStartPosition;
301               AddMovement(movement);
302             }
303             // Compute velocity.
304             // We need to compute velocity here to get reasonable value.
305             mVelocity = (currentPosition.X - mPreviousContainerPosition) / (mTotalAnimationTime - mPreviousTotalAnimationTime);
306             mPreviousTotalAnimationTime = mTotalAnimationTime;
307             mPreviousContainerPosition = currentPosition.X;
308           }
309         }
310         float currentMovement = currentPosition.X - mContainerStartPosition;
311         SetLatestMovement(currentMovement);
312
313         // Compute positions of each icon
314         ComputeNewPositions((int)mTotalAnimationTime);
315
316         // compute mRequiredMovementSize
317         if (mRequiredMovementSize == 0 || mNeedUpdateMovementSize)
318         {
319           mNeedUpdateMovementSize = false;
320           mRequiredMovementSize = mViewId.Count * OBJECT_DELAY / mTimeInterval;
321         }
322
323         // Remove unnecessary movement for memory optimization.
324         if (mMovement.Count > mRequiredMovementSize * 2)
325         {
326           int movementNumberToRemove = mMovement.Count - mRequiredMovementSize;
327           mMovement.RemoveRange(0, movementNumberToRemove);
328           mTotalAnimationTime -= (float)(mTimeInterval * movementNumberToRemove);
329         }
330       }
331     }
332
333     private Window mWindow;
334
335     private FrameCallback mFrameCallback;        ///< An instance of our implementation of the FrameCallbackInterface.
336
337     // Views for launcher
338     private View mBaseView;
339     private View mControlView;
340     private View mLayoutView;
341
342     // Variables for animation
343     private float mPreviousTouchedPosition;
344     private int mTouchedViewIndex;
345     private TOUCH_ANIMATION_STATE mAnimationState;
346
347     private float mLeftDirectionLimit;
348     private float mRightDirectionLimit;
349
350
351     // Variables for Finish animation
352     // These variables are for deceleration curve.
353     // If we want to use another curve like bezier, uses different variables
354     private delegate float UserAlphaFunctionDelegate(float progress);
355     private UserAlphaFunctionDelegate customScrollAlphaFunction;
356     private float mAbsoluteVelocity = 0.0f;
357     private float mFinishAnimationDuration = 0.0f;
358     private float mFinishAnimationDelta = 0.0f;
359     private float mLogDeceleration = 0.0f;
360     private float mDecelerationRate = 0.99f;
361     private float mEasingThreshold = 0.1f;
362
363     private Animation mFinishAnimation;
364     private Timer mAnimationOffTimer;  // timer to end animation after the easing animation is finished
365
366     // Vies of contents
367     private View mContentsView;
368
369     public void Activate()
370     {
371       mFrameCallback = new FrameCallback();
372       Initialize();
373     }
374
375     public void Deactivate()
376     {
377     }
378
379     void Initialize()
380     {
381       // Set the stage background color and connect to the stage's key signal to allow Back and Escape to exit.
382       mWindow = Window.Instance;
383       mWindow.BackgroundColor = Color.White;
384
385       mRightDirectionLimit = INITIAL_POSITION;
386
387       // Contents
388
389       mContentsView = new View();
390       mContentsView.BackgroundColor = new Color(0.921568f, 0.9098039f, 0.890196f, 0.5f);
391       mContentsView.ParentOrigin = ParentOrigin.TopLeft;
392       mContentsView.PivotPoint = PivotPoint.TopLeft;
393       mContentsView.PositionUsesPivotPoint = true;
394       mContentsView.WidthResizePolicy = ResizePolicyType.FillToParent;
395       mContentsView.HeightResizePolicy = ResizePolicyType.FillToParent;
396       mWindow.GetDefaultLayer().Add(mContentsView);
397
398       // Launcher
399       mBaseView = new View();
400       mBaseView.ParentOrigin = ParentOrigin.BottomLeft;
401       mBaseView.PivotPoint = PivotPoint.BottomLeft;
402       mBaseView.PositionUsesPivotPoint = true;
403       mBaseView.Size = new Size(mWindow.Size.Width, 278);
404       mBaseView.Position = new Position(0, 0);
405       mWindow.GetDefaultLayer().Add(mBaseView);
406
407       View iconBackgroundView = new View();
408       iconBackgroundView.BackgroundColor = new Color(0.921568f, 0.9098039f, 0.890196f, 0.5f);
409       iconBackgroundView.ParentOrigin = ParentOrigin.BottomLeft;
410       iconBackgroundView.PivotPoint = PivotPoint.BottomLeft;
411       iconBackgroundView.PositionUsesPivotPoint = true;
412       iconBackgroundView.Size = new Size(mWindow.Size.Width, 278);
413       iconBackgroundView.Position = new Position(0, 0);
414       mBaseView.Add(iconBackgroundView);
415
416       mControlView = new View();
417       mControlView.ParentOrigin = ParentOrigin.CenterLeft;
418       mControlView.PivotPoint = PivotPoint.CenterLeft;
419       mControlView.PositionUsesPivotPoint = true;
420       mControlView.Position = new Position(mRightDirectionLimit, 0);
421       mBaseView.Add(mControlView);
422       mFrameCallback.SetContainerId(mControlView.ID);
423
424       mLayoutView = new View();
425       mLayoutView.ParentOrigin = ParentOrigin.CenterLeft;
426       mLayoutView.PivotPoint = PivotPoint.CenterLeft;
427       mLayoutView.PositionUsesPivotPoint = true;
428       mLayoutView.Layout = new LinearLayout()
429       {
430         LinearOrientation = LinearLayout.Orientation.Horizontal,
431         CellPadding = new Size2D(DEFAULT_SPACE, 0),
432       };
433       mLayoutView.Position = new Position(0, 0);
434       mControlView.Add(mLayoutView);
435
436       for (int i = 0; i < 4; ++i)
437       {
438         AddIcon(BACKGROUND_IMAGE_PATH[i], APPS_IMAGE_PATH[i], APPS_ICON_NAME[i], Color.White);
439       }
440
441       View divideBar = new View();
442       divideBar.BackgroundColor = new Color(0.0f, 0.0f, 0.0f, 0.1f);
443       divideBar.ParentOrigin = ParentOrigin.CenterLeft;
444       divideBar.PivotPoint = PivotPoint.CenterLeft;
445       divideBar.PositionUsesPivotPoint = true;
446       divideBar.Size = new Size(DEVIDE_BAR_SIZE, OBJECT_SIZE);
447       mLayoutView.Add(divideBar);
448       mFrameCallback.AddId(divideBar.ID);
449
450       int iconNumber = 8;
451       for (int i = 0; i < iconNumber; ++i)
452       {
453         AddIcon(BACKGROUND_IMAGE_PATH[5], CONTROL_IMAGE_PATH[i % 5], CONTROL_ICON_NAME[i % 5], new Color(0.0f, 0.0f, 0.0f, 0.5f));
454       }
455
456       mFrameCallback.ResetViewPosition();
457       mFrameCallback.SetTimeInterval(1000 / FRAME_RATE);
458
459       mAnimationState = TOUCH_ANIMATION_STATE.NO_ANIMATION;
460
461       mAnimationOffTimer = new Timer(16);
462       mAnimationOffTimer.Tick += OffAnimatable;
463
464       mBaseView.TouchEvent += OnTouch;
465
466       mFinishAnimation = new Animation();
467       mFinishAnimation.Finished += EasingAnimationFinishedCallback;
468       mLogDeceleration = (float)Math.Log(mDecelerationRate);
469     }
470
471     // Add icons
472
473     void AddIcon(string background, string icon, string text, Color textColor)
474     {
475       ImageView backgroundView = new ImageView();
476       backgroundView.ResourceUrl = background;
477       backgroundView.Size = new Size(OBJECT_SIZE, OBJECT_SIZE);
478       backgroundView.ParentOrigin = ParentOrigin.CenterLeft;
479       backgroundView.PivotPoint = PivotPoint.CenterLeft;
480       backgroundView.PositionUsesPivotPoint = true;
481       mLayoutView.Add(backgroundView);
482       mFrameCallback.AddId(backgroundView.ID);
483
484       ImageView iconView = new ImageView();
485       iconView.ResourceUrl = icon;
486       iconView.Position = new Position(0, -15);
487       iconView.ParentOrigin = ParentOrigin.Center;
488       iconView.PivotPoint = PivotPoint.Center;
489       iconView.PositionUsesPivotPoint = true;
490       backgroundView.Add(iconView);
491
492       TextLabel label = new TextLabel(text);
493       label.Position = new Position(0, 30);
494       label.HorizontalAlignment = HorizontalAlignment.Center;
495       label.TextColor = textColor;
496       label.FontFamily = "SamsungOneUI";
497       label.PointSize = 12;
498       label.ParentOrigin = ParentOrigin.Center;
499       label.PivotPoint = PivotPoint.Center;
500       label.PositionUsesPivotPoint = true;
501       backgroundView.Add(label);
502     }
503
504     // Set frame callback to start drag animation.
505     private void SetFrameCallback(float position)
506     {
507       // remove frame callback if it is already added.
508       mWindow.RemoveFrameCallback(mFrameCallback);
509
510       mFrameCallback.ResetAnimationData();
511       mFrameCallback.AddMovement(0.0f); // Add first movement.
512
513       // Set container start position and start positions of each icon(and vertical bar)
514       // And compute total container size.
515       float totalSize = 0.0f;
516       mFrameCallback.SetContainerStartPosition(mControlView.Position.X);
517       for (int i = 0; i < mLayoutView.ChildCount; ++i)
518       {
519         mFrameCallback.SetViewPosition(i, mLayoutView.Children[i].Position.X);
520         totalSize += (float)(mLayoutView.Children[i].Size.Width + DEFAULT_SPACE);
521       }
522       totalSize -= (float)DEFAULT_SPACE;
523
524       // Find touched icon
525       for (int i = (int)mLayoutView.ChildCount - 1; i >= 0; --i)
526       {
527         if (position >= mLayoutView.Children[i].Position.X + mControlView.Position.X)
528         {
529           mFrameCallback.SetTouchedViewIndex(i);
530           mTouchedViewIndex = i;
531           break;
532         }
533       }
534       if (position < mLayoutView.Children[0].Position.X + mControlView.Position.X)
535       {
536         mFrameCallback.SetTouchedViewIndex(0);
537         mTouchedViewIndex = 0;
538       }
539
540       mPreviousTouchedPosition = position;
541
542       // Add frame callback on window.
543       // OnUpdate callback of mFrameCallback will be called before every render frame.
544       mWindow.AddFrameCallback(mFrameCallback);
545
546       // compute limit position the container could go.
547       mLeftDirectionLimit = (float)mWindow.Size.Width - (totalSize + (float)(INITIAL_POSITION));
548
549       mWindow.RenderingBehavior = RenderingBehaviorType.Continuously; // make rendering be done for upto 60 fps even though there is no update in main thread.
550       mAnimationState = TOUCH_ANIMATION_STATE.ON_ANIMATION; // make rendering state on.
551     }
552
553     private bool OnTouch(object source, View.TouchEventArgs e)
554     {
555       Vector2 position = e.Touch.GetScreenPosition(0);
556
557       PointStateType state = e.Touch.GetState(0);
558       if (PointStateType.Down == state)
559       {
560         if (mAnimationState == TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION)
561         {
562           // re-birth current animation
563           // in case of touch during finish animation,
564           // quit easingAnimation and AnimationOffTimer because animation ownership is returned to the touch event again.
565           // AND, DO NOT RESET ALL PROPERTIES OF FRAMECALLBACK.
566           // because, for example, if touched icon index is changed, the movement is wrong and the animation can be not continous.
567           // This re-birthed animation is just for smooth moving during complex user interaction.
568           // during complex and fast interaction, this is not so noticeable.
569           // and reset of such properties will be done in the below Motion state
570           mFinishAnimation.Stop();
571           mAnimationOffTimer.Stop();
572
573           // Set Animation State to ON_ANIMATION again
574           mAnimationState = TOUCH_ANIMATION_STATE.ON_ANIMATION;
575           // Set previousTouchPosition
576           mPreviousTouchedPosition = position.X;
577         }
578         else
579         {
580           // in case of stable state
581           // just set new framecallback for this touched position.
582           SetFrameCallback(position.X);
583         }
584       }
585       else if (PointStateType.Motion == state)
586       {
587         // if framecallback can be reset, quit current frame callback and re-launch new frame callback.
588         // because, if current frame callback is re-birthed one, the animation is not totally re-created one.
589         // So, some properties like touched icon index can be wrong for the continuous animation.
590         // But, some case like that finger is stopped and restart to move, this could make weired feeling.
591         // We reset mFrameCallback as soon as possible we can. And the conditions are ...
592         // 1. icons in screen is stopped.
593         // 2. velocity of frame callback is 0.0 (this frame callback will not move again instantly)
594         // 3. frame callback is not dirty (there is no reserved action)
595         if (mFrameCallback.IsResetTouchedViewPossible() && mFrameCallback.GetVelocity() == 0.0f && !mFrameCallback.IsDirty())
596         {
597           SetFrameCallback(position.X);
598         }
599
600         // Set new controlView(container) position
601         // in here, we need to consider the container is not go outside of limits.
602         float containerPosition = mControlView.Position.X + (position.X - mPreviousTouchedPosition);
603         containerPosition = Math.Min(containerPosition, mRightDirectionLimit);
604         containerPosition = Math.Max(containerPosition, mLeftDirectionLimit);
605         float adjustedPosition = containerPosition - mControlView.Position.X + mPreviousTouchedPosition;
606         mPreviousTouchedPosition = adjustedPosition;
607         mControlView.Position.X = containerPosition;
608       }
609       else if ((PointStateType.Up == state || PointStateType.Leave == state || PointStateType.Interrupted == state) &&
610                mAnimationState == TOUCH_ANIMATION_STATE.ON_ANIMATION)
611       {
612         mAnimationState = TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION;
613
614         // To launch finish animation, we get latest velocty from frame callback
615         float velocity = mFrameCallback.GetVelocity();
616
617         /* TUNING */
618         // This is just for turning of finish animation.
619         // change the values if you want.
620         velocity = Math.Max(velocity, -3.5f);
621         velocity = Math.Min(velocity, 3.5f);
622         if (Math.Abs(velocity) < 0.0001f)
623         {
624           // If velocity is zero. just start animationOfftimer.
625           mAnimationOffTimer.Start();
626         }
627         else
628         {
629           // If velocity is not zero, make decelerating animation.
630           Decelerating(velocity);
631         }
632       }
633       // set currently visible icons for optimization
634       SetVisibleLimit();
635       // make frame callback dirty.
636       mFrameCallback.Dirty();
637       return true;
638     }
639
640     private void SetVisibleLimit()
641     {
642       int leftViewIndex = mTouchedViewIndex;
643       for (; leftViewIndex >= 0; --leftViewIndex)
644       {
645         float newPosition = mLayoutView.Children[leftViewIndex].Position.X + mControlView.Position.X;
646         if (newPosition + (float)mLayoutView.Children[leftViewIndex].Size.Width < 0.0f)
647         {
648           break;
649         }
650       }
651       leftViewIndex = Math.Max(leftViewIndex, 0);
652       int rightViewIndex = mTouchedViewIndex;
653       for (; rightViewIndex < mLayoutView.ChildCount; ++rightViewIndex)
654       {
655         float newPosition = mLayoutView.Children[rightViewIndex].Position.X + mControlView.Position.X;
656         if (newPosition > mWindow.Size.Width)
657         {
658           break;
659         }
660       }
661       rightViewIndex = Math.Min(rightViewIndex, (int)mLayoutView.ChildCount - 1);
662
663       mFrameCallback.SetLeftIndex(leftViewIndex);
664       mFrameCallback.SetRightIndex(rightViewIndex);
665     }
666
667     // set decelerating properties
668     // in this example, we used decelerate animation in "https://medium.com/@esskeetit/scrolling-mechanics-of-uiscrollview-142adee1142c"
669     // But, if this method is problematic or violate some patent of other company, change this other way.
670     // We didn't checked anything.
671     // Only thing we need to remember when we change this animation is to add "EasingAnimationFinishedCallback" for the new animation.
672     private void Decelerating(float velocity)
673     {
674       mAbsoluteVelocity = Math.Abs(velocity);
675       mFinishAnimationDelta = (mAbsoluteVelocity * mDecelerationRate) / (1 - mDecelerationRate);
676       float destination = (velocity > 0) ? mControlView.Position.X + mFinishAnimationDelta : mControlView.Position.X - mFinishAnimationDelta;
677
678       if (destination < mLeftDirectionLimit || destination > mRightDirectionLimit)
679       {
680         mFinishAnimationDelta = velocity > 0 ? (mRightDirectionLimit - mControlView.Position.X) : (mControlView.Position.X - mLeftDirectionLimit);
681         destination = velocity > 0 ? mRightDirectionLimit : mLeftDirectionLimit;
682         if (mFinishAnimationDelta == 0)
683         {
684           mFinishAnimationDuration = 0.0f;
685         }
686         else
687         {
688           mFinishAnimationDuration = (float)Math.Log((mFinishAnimationDelta * mLogDeceleration / mAbsoluteVelocity + 1), mDecelerationRate);
689         }
690       }
691       else
692       {
693         if (mFinishAnimationDelta == 0)
694         {
695           mFinishAnimationDuration = 0.0f;
696         }
697         else
698         {
699           mFinishAnimationDuration = (float)Math.Log(-mEasingThreshold * mLogDeceleration / mAbsoluteVelocity) / mLogDeceleration;
700         }
701       }
702
703       mFinishAnimation.Clear();
704       customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
705       mFinishAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
706       GC.KeepAlive(customScrollAlphaFunction);
707       mFinishAnimation.Duration = (int)mFinishAnimationDuration;
708       mFinishAnimation.AnimateTo(mControlView, "PositionX", destination);
709       mFinishAnimation.Play();
710     }
711
712     private float CustomScrollAlphaFunction(float progress)
713     {
714       if (mFinishAnimationDelta == 0)
715       {
716         return 1.0f;
717       }
718       else
719       {
720         float realDuration = progress * mFinishAnimationDuration;
721         float realDistance = mAbsoluteVelocity * ((float)Math.Pow(mDecelerationRate, realDuration) - 1) / mLogDeceleration;
722         float result = Math.Min(realDistance / Math.Abs(mFinishAnimationDelta), 1.0f);
723
724         return result;
725       }
726     }
727
728     private void EasingAnimationFinishedCallback(object sender, EventArgs e)
729     {
730       if (mAnimationState != TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION)
731       {
732         return;
733       }
734
735       // start Animation Off Timer
736       mFinishAnimation.Clear();
737       SetVisibleLimit();
738       mAnimationOffTimer.Start();
739     }
740
741     // Check each icons in screen is not moving.
742     // If it is, finish all animation and make animationstate End_animation(NO_ANIMATION)
743     private bool OffAnimatable(object target, Timer.TickEventArgs args)
744     {
745       if (mFrameCallback.IsResetTouchedViewPossible())
746       {
747         mWindow.RenderingBehavior = RenderingBehaviorType.IfRequired;
748         mWindow.RemoveFrameCallback(mFrameCallback);
749         mAnimationOffTimer.Stop();
750         mAnimationState = TOUCH_ANIMATION_STATE.END_ANIMATION;
751         return false;
752       }
753       SetVisibleLimit();
754       return true;
755     }
756   }
757 }