[NUI] Allow to call FrameUpdate callback return value
authorEunki, Hong <eunkiki.hong@samsung.com>
Mon, 8 Jan 2024 09:01:41 +0000 (18:01 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Tue, 16 Jan 2024 04:02:15 +0000 (13:02 +0900)
DALi's FrameUpdateCallback have interface whether we need to update scene or not.
Previously, we always return it as false.
It might make some render / update timing issue when App is paused.

To make ensure whether we need to update scene or not,
Let we override the new API that app can control the return value.

Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
src/Tizen.NUI/src/internal/Interop/Interop.FrameUpdateCallbackInterface.cs
src/Tizen.NUI/src/public/Common/FrameUpdateCallbackInterface.cs
test/Tizen.NUI.Samples/Tizen.NUI.Samples/Samples/FrameUpdateCallbackTest.cs

index 0382675..78dce87 100755 (executable)
@@ -24,8 +24,8 @@ namespace Tizen.NUI
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_new_FrameCallbackInterface")]
             public static extern global::System.IntPtr newFrameUpdateCallbackInterface();
 
-            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_FrameCallbackInterface_director_connect")]
-            public static extern void FrameUpdateCallbackInterfaceDirectorConnect(global::System.Runtime.InteropServices.HandleRef jarg1, Tizen.NUI.FrameUpdateCallbackInterface.DelegateFrameUpdateCallbackInterface delegate0);
+            [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_FrameCallbackInterface_director_connect_with_return")]
+            public static extern void FrameUpdateCallbackInterfaceDirectorConnectV1(global::System.Runtime.InteropServices.HandleRef jarg1, Tizen.NUI.FrameUpdateCallbackInterface.DelegateFrameUpdateCallbackInterfaceV1 delegate1);
 
             [global::System.Runtime.InteropServices.DllImport(NDalicPINVOKE.Lib, EntryPoint = "CSharp_Dali_FrameCallbackInterface_GetPosition")]
             [return: global::System.Runtime.InteropServices.MarshalAs(global::System.Runtime.InteropServices.UnmanagedType.U1)]
index f228adc..896ee51 100755 (executable)
@@ -25,43 +25,76 @@ namespace Tizen.NUI
     [EditorBrowsable(EditorBrowsableState.Never)]
     public class FrameUpdateCallbackInterface : Disposable
     {
+        private uint onUpdateCallbackVersion = 0u;
+
         /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
         [EditorBrowsable(EditorBrowsableState.Never)]
         internal FrameUpdateCallbackInterface(global::System.IntPtr cPtr, bool cMemoryOwn) : base(cPtr, cMemoryOwn)
         {
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
         /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
         [EditorBrowsable(EditorBrowsableState.Never)]
-        public FrameUpdateCallbackInterface() : this(Interop.FrameUpdateCallbackInterface.newFrameUpdateCallbackInterface(), true)
+        public FrameUpdateCallbackInterface() : this(0u)
         {
-            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
+        }
+
+        /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public FrameUpdateCallbackInterface(uint updateCallbackVersion) : this(Interop.FrameUpdateCallbackInterface.newFrameUpdateCallbackInterface(), true)
+        {
+            onUpdateCallbackVersion = updateCallbackVersion;
             DirectorConnect();
+            if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
         }
 
+        /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public uint UpdateCallbackVersion => onUpdateCallbackVersion;
+
         private void DirectorConnect()
         {
-            Delegate0 = new DelegateFrameUpdateCallbackInterface(DirectorOnUpdate);
-            Interop.FrameUpdateCallbackInterface.FrameUpdateCallbackInterfaceDirectorConnect(SwigCPtr, Delegate0);
+            Delegate1 = new DelegateFrameUpdateCallbackInterfaceV1(DirectorOnUpdate);
+            Interop.FrameUpdateCallbackInterface.FrameUpdateCallbackInterfaceDirectorConnectV1(SwigCPtr, Delegate1);
         }
 
-        private global::System.IntPtr proxyIntPtr;
-        private void DirectorOnUpdate(global::System.IntPtr proxy, float elapsedSeconds)
+        private global::System.IntPtr proxyIntPtr = global::System.IntPtr.Zero;
+
+        private bool DirectorOnUpdate(global::System.IntPtr proxy, float elapsedSeconds)
         {
+            bool ret = false;
+
             proxyIntPtr = proxy;
-            OnUpdate(elapsedSeconds);
-            return;
-        }
+            if (onUpdateCallbackVersion == 0u)
+            {
+                OnUpdate(elapsedSeconds);
+            }
+            else if (onUpdateCallbackVersion == 1u)
+            {
+                ret = OnUpdate(this, elapsedSeconds);
+            }
+            proxyIntPtr = global::System.IntPtr.Zero;
 
-        internal delegate void DelegateFrameUpdateCallbackInterface(global::System.IntPtr proxy, float elapsedSeconds);
-        private DelegateFrameUpdateCallbackInterface Delegate0;
+            return ret;
+        }
 
+        internal delegate bool DelegateFrameUpdateCallbackInterfaceV1(global::System.IntPtr proxy, float elapsedSeconds);
+        private DelegateFrameUpdateCallbackInterfaceV1 Delegate1;
 
         /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
         [EditorBrowsable(EditorBrowsableState.Never)]
         public virtual void OnUpdate(float elapsedSeconds)
         {
+        }
 
+        /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        public virtual bool OnUpdate(FrameUpdateCallbackInterface obj, float elapsedSeconds)
+        {
+            // Let we call Versoin 0 API. To keep backward capability.
+            OnUpdate(elapsedSeconds);
+            return false;
         }
 
         /// This will be public opened in next tizen after ACR done. Before ACR, need to be hidden as inhouse API.
index 80751c1..91d0706 100644 (file)
@@ -7,19 +7,19 @@ using System.Linq;
 
 namespace Tizen.NUI.Samples
 {
-  enum TOUCH_ANIMATION_STATE
-  {
-    NO_ANIMATION = 0,
-    ON_ANIMATION,
-    ON_FINISH_ANIMATION,
-    END_ANIMATION = NO_ANIMATION
-  };
-
-  public class FrameUpdateCallbackTest : IExample
-  {
-
-    private static string resourcePath = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
-    private static string[] BACKGROUND_IMAGE_PATH = {
+    enum TOUCH_ANIMATION_STATE
+    {
+        NO_ANIMATION = 0,
+        ON_ANIMATION,
+        ON_FINISH_ANIMATION,
+        END_ANIMATION = NO_ANIMATION
+    };
+
+    public class FrameUpdateCallbackTest : IExample
+    {
+
+        private static string resourcePath = Tizen.Applications.Application.Current.DirectoryInfo.Resource;
+        private static string[] BACKGROUND_IMAGE_PATH = {
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_bg_02_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_bg_03_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_bg_04_nor.png",
@@ -28,21 +28,21 @@ namespace Tizen.NUI.Samples
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_bg_apps_nor.png"
     };
 
-    private static string[] APPS_IMAGE_PATH = {
+        private static string[] APPS_IMAGE_PATH = {
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_culinary_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_family_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_ent_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_homecare_nor.png"
     };
 
-    private static string[] APPS_ICON_NAME = {
+        private static string[] APPS_ICON_NAME = {
       "Culinary",
       "Family",
       "Entertainment",
       "Homecare"
     };
 
-    private static string[] CONTROL_IMAGE_PATH = {
+        private static string[] CONTROL_IMAGE_PATH = {
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_apps_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_settings_nor.png",
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_viewinside_nor.png",
@@ -50,7 +50,7 @@ namespace Tizen.NUI.Samples
       resourcePath + "/images/FrameUpdateCallbackTest/launcher_ic_internet_nor.png"
     };
 
-    private static string[] CONTROL_ICON_NAME = {
+        private static string[] CONTROL_ICON_NAME = {
       "Apps",
       "Settings",
       "ViewInside",
@@ -58,700 +58,723 @@ namespace Tizen.NUI.Samples
       "Internet"
     };
 
-    private const int FRAME_RATE = 60;
-    private const int OBJECT_DELAY = 30;
-    private const int OBJECT_SIZE = 150;
-    private const int INITIAL_POSITION = 46;
-    private const int DEFAULT_SPACE = 9;
-    private const int DEVIDE_BAR_SIZE = 4;
+        private const int FRAME_RATE = 60;
+        private const int OBJECT_DELAY = 30;
+        private const int OBJECT_SIZE = 150;
+        private const int INITIAL_POSITION = 46;
+        private const int DEFAULT_SPACE = 9;
+        private const int DEVIDE_BAR_SIZE = 4;
 
-    public class FrameUpdateCallback : FrameUpdateCallbackInterface
-    {
-      private int timeInterval;
-
-      private uint containerId;
-      private List<uint> viewId; ///< View ID in the container.
-      private List<float> viewPosition;
-
-      // Movement is position difference of Container from start position of this animation to current position.
-      // Time interval between each containerMovement entry is 16 milliseconds(about 60 fps)
-      // For example.
-      // containerMovement[i] + containerStartPosition is the position of container after i*16 milliseconds from this animation started.
-      private List<float> containerMovement;
-
-      // latestMovement is actually current movement of container.
-      private float latestMovement;
-
-      // An icon of touchedViewIndex moves with container at the same time.
-      // Each icon of (touchedViewIndex -/+ i) moves as following container after i*OBJECT_DELAY milliseconds.
-      private int touchedViewIndex;
-
-      // If every icon between mLeftIndext and rightIndex is stopped, this frame callback can be reset.
-      // Then isResetTouchedViewPossible becomes true.
-      private bool isResetTouchedViewPossible;
-      private int leftIndex;
-      private int rightIndex;
-
-      // Total animation time from start to current.
-      private float totalAnimationTime;
-      // Total animation time from start to the time that last movement is added.
-      private float previousTotalAnimationTime;
-
-      // Start position of container in this animation.
-      private float containerStartPosition;
-      // Position of container at the time that last movement is added.
-      private float previousContainerPosition;
-
-      // This animation only need to save movement about (the number of view * OBJECT_DELAY) milliseconds.
-      // and this size is updated when new view is added.
-      // and, If the list of containerMovement size become larger than this size, remove unnecessary entries.
-      private int requiredMovementSize;
-      private bool needUpdateMovementSize;
-
-      // current velocity.
-      private float velocity;
-      // dirty flag.
-      private bool dirty;
-
-      public FrameUpdateCallback()
-      {
-        viewId = new List<uint>();
-        containerMovement = new List<float>();
-        requiredMovementSize = 0;
-        needUpdateMovementSize = false;
-        isResetTouchedViewPossible = false;
-      }
-
-      public void ResetAnimationData()
-      {
-        SetLatestMovement(0.0f);
-        containerMovement.Clear();
-        totalAnimationTime = 0.0f;
-        previousTotalAnimationTime = 0.0f;
-        dirty = true;
-      }
-
-      public void SetTimeInterval(int interval)
-      {
-        needUpdateMovementSize = true;
-        timeInterval = interval;
-      }
-
-      public void AddId(uint id)
-      {
-        viewId.Add(id);
-        needUpdateMovementSize = true;
-      }
-
-      public void SetContainerId(uint id)
-      {
-        containerId = id;
-      }
-
-      public void SetContainerStartPosition(float position)
-      {
-        containerStartPosition = position;
-      }
-
-      public void SetLatestMovement(float movement)
-      {
-        latestMovement = movement;
-      }
-
-      public void ResetViewPosition()
-      {
-        viewPosition = Enumerable.Repeat(0.0f, viewId.Count).ToList();
-      }
-
-      public void SetViewPosition(int index, float position)
-      {
-        viewPosition[index] = position;
-      }
-
-      public void SetTouchedViewIndex(int controlIndex)
-      {
-        touchedViewIndex = controlIndex;
-      }
-
-      public void AddMovement(float movement)
-      {
-        containerMovement.Add(movement);
-      }
-
-      public void SetLeftIndex(int Index)
-      {
-        leftIndex = Index;
-      }
-
-      public void SetRightIndex(int Index)
-      {
-        rightIndex = Index;
-      }
-
-      public bool IsResetTouchedViewPossible()
-      {
-        return isResetTouchedViewPossible;
-      }
-      public void Dirty()
-      {
-        dirty = true;
-      }
-
-      public bool IsDirty()
-      {
-        return dirty;
-      }
-
-      public float GetVelocity()
-      {
-        return velocity;
-      }
-
-      private void ComputeNewPositions(int totalTime)
-      {
-        // save latestMovement to avoid interference between thread.
-        float lastMovement = latestMovement;
-        bool isStillMoving = true;
-        for (int i = 0; i < viewId.Count; ++i)
-        {
-          if (i == touchedViewIndex)
-          {
-            continue;
-          }
-
-          // compute delay of view of i.
-          int totalDelay = Math.Abs(i - touchedViewIndex) * OBJECT_DELAY;
-          if (totalDelay > totalTime)
-          {
-            continue;
-          }
-
-          int actorTime = totalTime - totalDelay;
-          int movementIndex = actorTime / timeInterval;
-          float factor = (float)(actorTime - (movementIndex * timeInterval)) / (float)timeInterval;
-          float movement;
-          if (movementIndex >= containerMovement.Count - 1)
-          {
-            // 1. delay is zero(every view moves with container at the same time)
-            // 2. after every icons are stopped and the finger is stopped to move, the movement is still not added more.
-            // than the view has lastMovement.
-            movement = lastMovement;
-          }
-          else if (movementIndex < 0)
-          {
-            // If this animation is just staarted and the view need to wait more.
-            // movement is 0.
-            movement = 0.0f;
-          }
-          else
-          {
-            // Get the movement of ith view by interpolating containerMovement
-            movement = factor * containerMovement[movementIndex + 1] + (1.0f - factor) * containerMovement[movementIndex];
-          }
-
-          // Prevent to overlap of each views.
-          float currentSpace = Math.Abs((viewPosition[i] + movement) - (viewPosition[touchedViewIndex] + lastMovement));
-          float minimumSpace = (float)Math.Abs(viewPosition[i] - viewPosition[touchedViewIndex]);
-          if (currentSpace < minimumSpace)
-          {
-            movement = lastMovement;
-          }
-
-          // check views in screen are still moving or stopped.
-          float newPosition = viewPosition[i] + movement - lastMovement;
-          if (i >= leftIndex && i <= rightIndex)
-          {
-            Vector3 previousPosition = new Vector3();
-            GetPosition(viewId[i], previousPosition);
-            if (Math.Abs(previousPosition.X - newPosition) >= 1.0f)
-            {
-              isStillMoving = false;
-            }
-          }
-          // update new position.
-          SetPosition(viewId[i], new Vector3(newPosition, 0.0f, 0.0f));
-        }
-        isResetTouchedViewPossible = isStillMoving;
-      }
-
-      public override void OnUpdate(float elapsedSeconds)
-      {
-        // second -> millisecond
-        totalAnimationTime += elapsedSeconds * 1000.0f;
-
-        Vector3 currentPosition = new Vector3();
-        GetPosition(containerId, currentPosition);
-
-        // Add new Movement, if there is change in position.
-        // 1. if dirty(there is reserved event)
-        // 2. if container position is changed.
-        // 3. if every icons in screen is stopped
-        if (dirty || currentPosition.X != previousContainerPosition || isResetTouchedViewPossible)
+        private const uint FRAME_UPDATE_CALLBACK_VERSION = 0u;
+
+        public class FrameUpdateCallback : FrameUpdateCallbackInterface
         {
-          dirty = false;
-          if (totalAnimationTime >= containerMovement.Count * timeInterval)
-          {
-            // If the passed time is larger than timeInterval, add new movements.
-            // If we need to add more than one, compute each movement by using interpolation.
-            while (containerMovement.Count <= totalAnimationTime / timeInterval)
-            {
-              float factor = ((float)(containerMovement.Count * timeInterval) - previousTotalAnimationTime) / (totalAnimationTime - previousTotalAnimationTime);
-              float movement = (float)(factor * currentPosition.X + (1.0f - factor) * previousContainerPosition) - containerStartPosition;
-              AddMovement(movement);
-            }
-            // Compute velocity.
-            // We need to compute velocity here to get reasonable value.
-            velocity = (currentPosition.X - previousContainerPosition) / (totalAnimationTime - previousTotalAnimationTime);
-            previousTotalAnimationTime = totalAnimationTime;
-            previousContainerPosition = currentPosition.X;
-          }
-        }
-        float currentMovement = currentPosition.X - containerStartPosition;
-        SetLatestMovement(currentMovement);
+            private int timeInterval;
+
+            private uint containerId;
+            private List<uint> viewId; ///< View ID in the container.
+            private List<float> viewPosition;
+
+            // Movement is position difference of Container from start position of this animation to current position.
+            // Time interval between each containerMovement entry is 16 milliseconds(about 60 fps)
+            // For example.
+            // containerMovement[i] + containerStartPosition is the position of container after i*16 milliseconds from this animation started.
+            private List<float> containerMovement;
+
+            // latestMovement is actually current movement of container.
+            private float latestMovement;
+
+            // An icon of touchedViewIndex moves with container at the same time.
+            // Each icon of (touchedViewIndex -/+ i) moves as following container after i*OBJECT_DELAY milliseconds.
+            private int touchedViewIndex;
+
+            // If every icon between mLeftIndext and rightIndex is stopped, this frame callback can be reset.
+            // Then isResetTouchedViewPossible becomes true.
+            private bool isResetTouchedViewPossible;
+            private int leftIndex;
+            private int rightIndex;
+
+            // Total animation time from start to current.
+            private float totalAnimationTime;
+            // Total animation time from start to the time that last movement is added.
+            private float previousTotalAnimationTime;
+
+            // Start position of container in this animation.
+            private float containerStartPosition;
+            // Position of container at the time that last movement is added.
+            private float previousContainerPosition;
+
+            // This animation only need to save movement about (the number of view * OBJECT_DELAY) milliseconds.
+            // and this size is updated when new view is added.
+            // and, If the list of containerMovement size become larger than this size, remove unnecessary entries.
+            private int requiredMovementSize;
+            private bool needUpdateMovementSize;
+
+            // current velocity.
+            private float velocity;
+            // dirty flag.
+            private bool dirty;
+
+            // Check whether position changed at this frame, or not.
+            // If some position changed, let we keep rendering for next frame.
+            private bool positionChanged;
+
+            // Initialize base class that we will use OnUpdate with return.
+            public FrameUpdateCallback() : base(FRAME_UPDATE_CALLBACK_VERSION)
+            {
+                viewId = new List<uint>();
+                containerMovement = new List<float>();
+                requiredMovementSize = 0;
+                needUpdateMovementSize = false;
+                isResetTouchedViewPossible = false;
+                positionChanged = true;
+            }
 
-        // Compute positions of each icon
-        ComputeNewPositions((int)totalAnimationTime);
+            public void ResetAnimationData()
+            {
+                SetLatestMovement(0.0f);
+                containerMovement.Clear();
+                totalAnimationTime = 0.0f;
+                previousTotalAnimationTime = 0.0f;
+                dirty = true;
+            }
 
-        // compute requiredMovementSize
-        if (requiredMovementSize == 0 || needUpdateMovementSize)
-        {
-          needUpdateMovementSize = false;
-          requiredMovementSize = viewId.Count * OBJECT_DELAY / timeInterval;
-        }
+            public void SetTimeInterval(int interval)
+            {
+                needUpdateMovementSize = true;
+                timeInterval = interval;
+            }
 
-        // Remove unnecessary movement for memory optimization.
-        if (containerMovement.Count > requiredMovementSize * 2)
-        {
-          int movementNumberToRemove = containerMovement.Count - requiredMovementSize;
-          containerMovement.RemoveRange(0, movementNumberToRemove);
-          totalAnimationTime -= (float)(timeInterval * movementNumberToRemove);
-        }
-      }
-    }
+            public void AddId(uint id)
+            {
+                viewId.Add(id);
+                needUpdateMovementSize = true;
+                positionChanged = true;
+            }
 
-    private Window window;
+            public void SetContainerId(uint id)
+            {
+                containerId = id;
+            }
 
-    private FrameUpdateCallback frameUpdateCallback;        ///< An instance of our implementation of the FrameUpdateCallbackInterface.
+            public void SetContainerStartPosition(float position)
+            {
+                containerStartPosition = position;
+            }
 
-    // Views for launcher
-    private View baseView;
-    private View controlView;
-    private View layoutView;
+            public void SetLatestMovement(float movement)
+            {
+                latestMovement = movement;
+            }
 
-    // Variables for animation
-    private float previousTouchedPosition;
-    private int touchedViewIndex;
-    private TOUCH_ANIMATION_STATE animationState;
+            public void ResetViewPosition()
+            {
+                viewPosition = Enumerable.Repeat(0.0f, viewId.Count).ToList();
+            }
 
-    private float leftDirectionLimit;
-    private float rightDirectionLimit;
+            public void SetViewPosition(int index, float position)
+            {
+                viewPosition[index] = position;
+            }
 
+            public void SetTouchedViewIndex(int controlIndex)
+            {
+                touchedViewIndex = controlIndex;
+            }
 
-    // Variables for Finish animation
-    // These variables are for deceleration curve.
-    // If we want to use another curve like bezier, uses different variables
-    private delegate float UserAlphaFunctionDelegate(float progress);
-    private UserAlphaFunctionDelegate customScrollAlphaFunction;
-    private float absoluteVelocity = 0.0f;
-    private float finishAnimationDuration = 0.0f;
-    private float finishAnimationDelta = 0.0f;
-    private float logDeceleration = 0.0f;
-    private float decelerationRate = 0.99f;
-    private float easingThreshold = 0.1f;
+            public void AddMovement(float movement)
+            {
+                containerMovement.Add(movement);
+            }
 
-    private Animation finishAnimation;
-    private Timer animationOffTimer;  // timer to end animation after the easing animation is finished
+            public void SetLeftIndex(int Index)
+            {
+                leftIndex = Index;
+            }
 
-    // Vies of contents
-    private View contentsView;
+            public void SetRightIndex(int Index)
+            {
+                rightIndex = Index;
+            }
 
-    public void Activate()
-    {
-      frameUpdateCallback = new FrameUpdateCallback();
-      Initialize();
-    }
+            public bool IsResetTouchedViewPossible()
+            {
+                return isResetTouchedViewPossible;
+            }
+            public void Dirty()
+            {
+                dirty = true;
+            }
 
-    public void Deactivate()
-    {
-    }
+            public bool IsDirty()
+            {
+                return dirty;
+            }
 
-    void Initialize()
-    {
-      // Set the stage background color and connect to the stage's key signal to allow Back and Escape to exit.
-      window = Window.Instance;
-      window.BackgroundColor = Color.White;
-
-      rightDirectionLimit = INITIAL_POSITION;
-
-      // Contents
-
-      contentsView = new View();
-      contentsView.BackgroundColor = new Color(0.921568f, 0.9098039f, 0.890196f, 0.5f);
-      contentsView.ParentOrigin = ParentOrigin.TopLeft;
-      contentsView.PivotPoint = PivotPoint.TopLeft;
-      contentsView.PositionUsesPivotPoint = true;
-      contentsView.WidthResizePolicy = ResizePolicyType.FillToParent;
-      contentsView.HeightResizePolicy = ResizePolicyType.FillToParent;
-      window.GetDefaultLayer().Add(contentsView);
-
-      // Launcher
-      baseView = new View();
-      baseView.ParentOrigin = ParentOrigin.BottomLeft;
-      baseView.PivotPoint = PivotPoint.BottomLeft;
-      baseView.PositionUsesPivotPoint = true;
-      baseView.Size = new Size(window.Size.Width, 278);
-      baseView.Position = new Position(0, 0);
-      window.GetDefaultLayer().Add(baseView);
-
-      View iconBackgroundView = new View();
-      iconBackgroundView.BackgroundColor = new Color(0.921568f, 0.9098039f, 0.890196f, 0.5f);
-      iconBackgroundView.ParentOrigin = ParentOrigin.BottomLeft;
-      iconBackgroundView.PivotPoint = PivotPoint.BottomLeft;
-      iconBackgroundView.PositionUsesPivotPoint = true;
-      iconBackgroundView.Size = new Size(window.Size.Width, 278);
-      iconBackgroundView.Position = new Position(0, 0);
-      baseView.Add(iconBackgroundView);
-
-      controlView = new View();
-      controlView.ParentOrigin = ParentOrigin.CenterLeft;
-      controlView.PivotPoint = PivotPoint.CenterLeft;
-      controlView.PositionUsesPivotPoint = true;
-      controlView.Position = new Position(rightDirectionLimit, 0);
-      baseView.Add(controlView);
-      frameUpdateCallback.SetContainerId(controlView.ID);
-
-      layoutView = new View();
-      layoutView.ParentOrigin = ParentOrigin.CenterLeft;
-      layoutView.PivotPoint = PivotPoint.CenterLeft;
-      layoutView.PositionUsesPivotPoint = true;
-      layoutView.Layout = new LinearLayout()
-      {
-        LinearOrientation = LinearLayout.Orientation.Horizontal,
-        CellPadding = new Size2D(DEFAULT_SPACE, 0),
-      };
-      layoutView.Position = new Position(0, 0);
-      controlView.Add(layoutView);
-
-      for (int i = 0; i < 4; ++i)
-      {
-        AddIcon(BACKGROUND_IMAGE_PATH[i], APPS_IMAGE_PATH[i], APPS_ICON_NAME[i], Color.White);
-      }
-
-      View divideBar = new View();
-      divideBar.BackgroundColor = new Color(0.0f, 0.0f, 0.0f, 0.1f);
-      divideBar.ParentOrigin = ParentOrigin.CenterLeft;
-      divideBar.PivotPoint = PivotPoint.CenterLeft;
-      divideBar.PositionUsesPivotPoint = true;
-      divideBar.Size = new Size(DEVIDE_BAR_SIZE, OBJECT_SIZE);
-      layoutView.Add(divideBar);
-      frameUpdateCallback.AddId(divideBar.ID);
-
-      int iconNumber = 8;
-      for (int i = 0; i < iconNumber; ++i)
-      {
-        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));
-      }
-
-      frameUpdateCallback.ResetViewPosition();
-      frameUpdateCallback.SetTimeInterval(1000 / FRAME_RATE);
-
-      animationState = TOUCH_ANIMATION_STATE.NO_ANIMATION;
-
-      animationOffTimer = new Timer(16);
-      animationOffTimer.Tick += OffAnimatable;
-
-      baseView.TouchEvent += OnTouch;
-
-      finishAnimation = new Animation();
-      finishAnimation.Finished += EasingAnimationFinishedCallback;
-      logDeceleration = (float)Math.Log(decelerationRate);
-    }
+            public float GetVelocity()
+            {
+                return velocity;
+            }
 
-    // Add icons
+            private void ComputeNewPositions(int totalTime)
+            {
+                // save latestMovement to avoid interference between thread.
+                float lastMovement = latestMovement;
+                bool isStillMoving = true;
+                for (int i = 0; i < viewId.Count; ++i)
+                {
+                    if (i == touchedViewIndex)
+                    {
+                        continue;
+                    }
+
+                    // compute delay of view of i.
+                    int totalDelay = Math.Abs(i - touchedViewIndex) * OBJECT_DELAY;
+                    if (totalDelay > totalTime)
+                    {
+                        continue;
+                    }
+
+                    int actorTime = totalTime - totalDelay;
+                    int movementIndex = actorTime / timeInterval;
+                    float factor = (float)(actorTime - (movementIndex * timeInterval)) / (float)timeInterval;
+                    float movement;
+                    if (movementIndex >= containerMovement.Count - 1)
+                    {
+                        // 1. delay is zero(every view moves with container at the same time)
+                        // 2. after every icons are stopped and the finger is stopped to move, the movement is still not added more.
+                        // than the view has lastMovement.
+                        movement = lastMovement;
+                    }
+                    else if (movementIndex < 0)
+                    {
+                        // If this animation is just staarted and the view need to wait more.
+                        // movement is 0.
+                        movement = 0.0f;
+                    }
+                    else
+                    {
+                        // Get the movement of ith view by interpolating containerMovement
+                        movement = factor * containerMovement[movementIndex + 1] + (1.0f - factor) * containerMovement[movementIndex];
+                    }
+
+                    // Prevent to overlap of each views.
+                    float currentSpace = Math.Abs((viewPosition[i] + movement) - (viewPosition[touchedViewIndex] + lastMovement));
+                    float minimumSpace = (float)Math.Abs(viewPosition[i] - viewPosition[touchedViewIndex]);
+                    if (currentSpace < minimumSpace)
+                    {
+                        movement = lastMovement;
+                    }
+
+                    // check views in screen are still moving or stopped.
+                    float newPosition = viewPosition[i] + movement - lastMovement;
+                    if (i >= leftIndex && i <= rightIndex)
+                    {
+                        Vector3 previousPosition = new Vector3();
+                        GetPosition(viewId[i], previousPosition);
+                        if (Math.Abs(previousPosition.X - newPosition) >= 1.0f)
+                        {
+                            isStillMoving = false;
+                        }
+                    }
+                    // update new position.
+                    SetPosition(viewId[i], new Vector3(newPosition, 0.0f, 0.0f));
+                    positionChanged = true;
+                }
+                isResetTouchedViewPossible = isStillMoving;
+            }
 
-    void AddIcon(string background, string icon, string text, Color textColor)
-    {
-      ImageView backgroundView = new ImageView();
-      backgroundView.ResourceUrl = background;
-      backgroundView.Size = new Size(OBJECT_SIZE, OBJECT_SIZE);
-      backgroundView.ParentOrigin = ParentOrigin.CenterLeft;
-      backgroundView.PivotPoint = PivotPoint.CenterLeft;
-      backgroundView.PositionUsesPivotPoint = true;
-      layoutView.Add(backgroundView);
-      frameUpdateCallback.AddId(backgroundView.ID);
-
-      ImageView iconView = new ImageView();
-      iconView.ResourceUrl = icon;
-      iconView.Position = new Position(0, -15);
-      iconView.ParentOrigin = ParentOrigin.Center;
-      iconView.PivotPoint = PivotPoint.Center;
-      iconView.PositionUsesPivotPoint = true;
-      backgroundView.Add(iconView);
-
-      TextLabel label = new TextLabel(text);
-      label.Position = new Position(0, 30);
-      label.HorizontalAlignment = HorizontalAlignment.Center;
-      label.TextColor = textColor;
-      label.FontFamily = "SamsungOneUI";
-      label.PointSize = 12;
-      label.ParentOrigin = ParentOrigin.Center;
-      label.PivotPoint = PivotPoint.Center;
-      label.PositionUsesPivotPoint = true;
-      backgroundView.Add(label);
-    }
+            public override void OnUpdate(float elapsedSeconds)
+            {
+                // FRAME_UPDATE_CALLBACK_VERSION == 0u
+                OnUpdate(this, elapsedSeconds);
+            }
 
-    // Set frame callback to start drag animation.
-    private void SetFrameUpdateCallback(float position)
-    {
-      // remove frame callback if it is already added.
-      window.RemoveFrameUpdateCallback(frameUpdateCallback);
-
-      frameUpdateCallback.ResetAnimationData();
-      frameUpdateCallback.AddMovement(0.0f); // Add first movement.
-
-      // Set container start position and start positions of each icon(and vertical bar)
-      // And compute total container size.
-      float totalSize = 0.0f;
-      frameUpdateCallback.SetContainerStartPosition(controlView.Position.X);
-      for (int i = 0; i < layoutView.ChildCount; ++i)
-      {
-        frameUpdateCallback.SetViewPosition(i, layoutView.Children[i].Position.X);
-        totalSize += (float)(layoutView.Children[i].Size.Width + DEFAULT_SPACE);
-      }
-      totalSize -= (float)DEFAULT_SPACE;
-
-      // Find touched icon
-      for (int i = (int)layoutView.ChildCount - 1; i >= 0; --i)
-      {
-        if (position >= layoutView.Children[i].Position.X + controlView.Position.X)
-        {
-          frameUpdateCallback.SetTouchedViewIndex(i);
-          touchedViewIndex = i;
-          break;
+            public override bool OnUpdate(FrameUpdateCallbackInterface obj /* Not using */, float elapsedSeconds)
+            {
+                // FRAME_UPDATE_CALLBACK_VERSION == 1u
+
+                // second -> millisecond
+                totalAnimationTime += elapsedSeconds * 1000.0f;
+
+                Vector3 currentPosition = new Vector3();
+                GetPosition(containerId, currentPosition);
+
+                // Add new Movement, if there is change in position.
+                // 1. if dirty(there is reserved event)
+                // 2. if container position is changed.
+                // 3. if every icons in screen is stopped
+                if (dirty || currentPosition.X != previousContainerPosition || isResetTouchedViewPossible)
+                {
+                    dirty = false;
+                    if (totalAnimationTime >= containerMovement.Count * timeInterval)
+                    {
+                        // If the passed time is larger than timeInterval, add new movements.
+                        // If we need to add more than one, compute each movement by using interpolation.
+                        while (containerMovement.Count <= totalAnimationTime / timeInterval)
+                        {
+                            float factor = ((float)(containerMovement.Count * timeInterval) - previousTotalAnimationTime) / (totalAnimationTime - previousTotalAnimationTime);
+                            float movement = (float)(factor * currentPosition.X + (1.0f - factor) * previousContainerPosition) - containerStartPosition;
+                            AddMovement(movement);
+                        }
+                        // Compute velocity.
+                        // We need to compute velocity here to get reasonable value.
+                        velocity = (currentPosition.X - previousContainerPosition) / (totalAnimationTime - previousTotalAnimationTime);
+                        previousTotalAnimationTime = totalAnimationTime;
+                        previousContainerPosition = currentPosition.X;
+                    }
+                }
+                float currentMovement = currentPosition.X - containerStartPosition;
+                SetLatestMovement(currentMovement);
+
+                // Compute positions of each icon
+                ComputeNewPositions((int)totalAnimationTime);
+
+                // compute requiredMovementSize
+                if (requiredMovementSize == 0 || needUpdateMovementSize)
+                {
+                    needUpdateMovementSize = false;
+                    requiredMovementSize = viewId.Count * OBJECT_DELAY / timeInterval;
+                }
+
+                // Remove unnecessary movement for memory optimization.
+                if (containerMovement.Count > requiredMovementSize * 2)
+                {
+                    int movementNumberToRemove = containerMovement.Count - requiredMovementSize;
+                    containerMovement.RemoveRange(0, movementNumberToRemove);
+                    totalAnimationTime -= (float)(timeInterval * movementNumberToRemove);
+                }
+
+                // Reset flag
+                bool retValue = positionChanged;
+                positionChanged = false;
+                return retValue;
+            }
         }
-      }
-      if (position < layoutView.Children[0].Position.X + controlView.Position.X)
-      {
-        frameUpdateCallback.SetTouchedViewIndex(0);
-        touchedViewIndex = 0;
-      }
 
-      previousTouchedPosition = position;
+        private Window window;
 
-      // Add frame callback on window.
-      // OnUpdate callback of frameUpdateCallback will be called before every render frame.
-      // We can set root view what given frameUpdateCallback used
-      window.AddFrameUpdateCallback(frameUpdateCallback, controlView);
+        private FrameUpdateCallback frameUpdateCallback; // An instance of our implementation of the FrameUpdateCallbackInterface.
 
-      // compute limit position the container could go.
-      leftDirectionLimit = (float)window.Size.Width - (totalSize + (float)(INITIAL_POSITION));
+        // Views for launcher
+        private View baseView;
+        private View controlView;
+        private View layoutView;
 
-      window.RenderingBehavior = RenderingBehaviorType.Continuously; // make rendering be done for upto 60 fps even though there is no update in main thread.
-      animationState = TOUCH_ANIMATION_STATE.ON_ANIMATION; // make rendering state on.
-    }
+        // Variables for animation
+        private float previousTouchedPosition;
+        private int touchedViewIndex;
+        private TOUCH_ANIMATION_STATE animationState;
 
-    private bool OnTouch(object source, View.TouchEventArgs e)
-    {
-      Vector2 position = e.Touch.GetScreenPosition(0);
+        private float leftDirectionLimit;
+        private float rightDirectionLimit;
+
+
+        // Variables for Finish animation
+        // These variables are for deceleration curve.
+        // If we want to use another curve like bezier, uses different variables
+        private delegate float UserAlphaFunctionDelegate(float progress);
+        private UserAlphaFunctionDelegate customScrollAlphaFunction;
+        private float absoluteVelocity = 0.0f;
+        private float finishAnimationDuration = 0.0f;
+        private float finishAnimationDelta = 0.0f;
+        private float logDeceleration = 0.0f;
+        private float decelerationRate = 0.99f;
+        private float easingThreshold = 0.1f;
 
-      PointStateType state = e.Touch.GetState(0);
-      if (PointStateType.Down == state)
-      {
-        if (animationState == TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION)
+        private Animation finishAnimation;
+        private Timer animationOffTimer;  // timer to end animation after the easing animation is finished
+
+        // Vies of contents
+        private View contentsView;
+
+        public void Activate()
         {
-          // re-birth current animation
-          // in case of touch during finish animation,
-          // quit easingAnimation and AnimationOffTimer because animation ownership is returned to the touch event again.
-          // AND, DO NOT RESET ALL PROPERTIES OF FRAMECALLBACK.
-          // because, for example, if touched icon index is changed, the movement is wrong and the animation can be not continous.
-          // This re-birthed animation is just for smooth moving during complex user interaction.
-          // during complex and fast interaction, this is not so noticeable.
-          // and reset of such properties will be done in the below Motion state
-          finishAnimation.Stop();
-          animationOffTimer.Stop();
-
-          // Set Animation State to ON_ANIMATION again
-          animationState = TOUCH_ANIMATION_STATE.ON_ANIMATION;
-          // Set previousTouchPosition
-          previousTouchedPosition = position.X;
+            frameUpdateCallback = new FrameUpdateCallback();
+            Initialize();
         }
-        else
+
+        public void Deactivate()
         {
-          // in case of stable state
-          // just set new framecallback for this touched position.
-          SetFrameUpdateCallback(position.X);
         }
-      }
-      else if (PointStateType.Motion == state)
-      {
-        // if framecallback can be reset, quit current frame callback and re-launch new frame callback.
-        // because, if current frame callback is re-birthed one, the animation is not totally re-created one.
-        // So, some properties like touched icon index can be wrong for the continuous animation.
-        // But, some case like that finger is stopped and restart to move, this could make weired feeling.
-        // We reset frameUpdateCallback as soon as possible we can. And the conditions are ...
-        // 1. icons in screen is stopped.
-        // 2. velocity of frame callback is 0.0 (this frame callback will not move again instantly)
-        // 3. frame callback is not dirty (there is no reserved action)
-        if (frameUpdateCallback.IsResetTouchedViewPossible() && frameUpdateCallback.GetVelocity() == 0.0f && !frameUpdateCallback.IsDirty())
+
+        void Initialize()
         {
-          SetFrameUpdateCallback(position.X);
+            // Set the stage background color and connect to the stage's key signal to allow Back and Escape to exit.
+            window = Window.Instance;
+            window.BackgroundColor = Color.White;
+
+            rightDirectionLimit = INITIAL_POSITION;
+
+            // Contents
+
+            contentsView = new View();
+            contentsView.BackgroundColor = new Color(0.921568f, 0.9098039f, 0.890196f, 0.5f);
+            contentsView.ParentOrigin = ParentOrigin.TopLeft;
+            contentsView.PivotPoint = PivotPoint.TopLeft;
+            contentsView.PositionUsesPivotPoint = true;
+            contentsView.WidthResizePolicy = ResizePolicyType.FillToParent;
+            contentsView.HeightResizePolicy = ResizePolicyType.FillToParent;
+            window.GetDefaultLayer().Add(contentsView);
+
+            // Launcher
+            baseView = new View();
+            baseView.ParentOrigin = ParentOrigin.BottomLeft;
+            baseView.PivotPoint = PivotPoint.BottomLeft;
+            baseView.PositionUsesPivotPoint = true;
+            baseView.Size = new Size(window.Size.Width, 278);
+            baseView.Position = new Position(0, 0);
+            window.GetDefaultLayer().Add(baseView);
+
+            View iconBackgroundView = new View();
+            iconBackgroundView.BackgroundColor = new Color(0.921568f, 0.9098039f, 0.890196f, 0.5f);
+            iconBackgroundView.ParentOrigin = ParentOrigin.BottomLeft;
+            iconBackgroundView.PivotPoint = PivotPoint.BottomLeft;
+            iconBackgroundView.PositionUsesPivotPoint = true;
+            iconBackgroundView.Size = new Size(window.Size.Width, 278);
+            iconBackgroundView.Position = new Position(0, 0);
+            baseView.Add(iconBackgroundView);
+
+            controlView = new View();
+            controlView.ParentOrigin = ParentOrigin.CenterLeft;
+            controlView.PivotPoint = PivotPoint.CenterLeft;
+            controlView.PositionUsesPivotPoint = true;
+            controlView.Position = new Position(rightDirectionLimit, 0);
+            baseView.Add(controlView);
+            frameUpdateCallback.SetContainerId(controlView.ID);
+
+            layoutView = new View();
+            layoutView.ParentOrigin = ParentOrigin.CenterLeft;
+            layoutView.PivotPoint = PivotPoint.CenterLeft;
+            layoutView.PositionUsesPivotPoint = true;
+            layoutView.Layout = new LinearLayout()
+            {
+                LinearOrientation = LinearLayout.Orientation.Horizontal,
+                CellPadding = new Size2D(DEFAULT_SPACE, 0),
+            };
+            layoutView.Position = new Position(0, 0);
+            controlView.Add(layoutView);
+
+            for (int i = 0; i < 4; ++i)
+            {
+                AddIcon(BACKGROUND_IMAGE_PATH[i], APPS_IMAGE_PATH[i], APPS_ICON_NAME[i], Color.White);
+            }
+
+            View divideBar = new View();
+            divideBar.BackgroundColor = new Color(0.0f, 0.0f, 0.0f, 0.1f);
+            divideBar.ParentOrigin = ParentOrigin.CenterLeft;
+            divideBar.PivotPoint = PivotPoint.CenterLeft;
+            divideBar.PositionUsesPivotPoint = true;
+            divideBar.Size = new Size(DEVIDE_BAR_SIZE, OBJECT_SIZE);
+            layoutView.Add(divideBar);
+            frameUpdateCallback.AddId(divideBar.ID);
+
+            int iconNumber = 8;
+            for (int i = 0; i < iconNumber; ++i)
+            {
+                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));
+            }
+
+            frameUpdateCallback.ResetViewPosition();
+            frameUpdateCallback.SetTimeInterval(1000 / FRAME_RATE);
+
+            animationState = TOUCH_ANIMATION_STATE.NO_ANIMATION;
+
+            animationOffTimer = new Timer(16);
+            animationOffTimer.Tick += OffAnimatable;
+
+            baseView.TouchEvent += OnTouch;
+
+            finishAnimation = new Animation();
+            finishAnimation.Finished += EasingAnimationFinishedCallback;
+            logDeceleration = (float)Math.Log(decelerationRate);
         }
 
-        // Set new controlView(container) position
-        // in here, we need to consider the container is not go outside of limits.
-        float containerPosition = controlView.Position.X + (position.X - previousTouchedPosition);
-        containerPosition = Math.Min(containerPosition, rightDirectionLimit);
-        containerPosition = Math.Max(containerPosition, leftDirectionLimit);
-        float adjustedPosition = containerPosition - controlView.Position.X + previousTouchedPosition;
-        previousTouchedPosition = adjustedPosition;
-        controlView.Position.X = containerPosition;
-      }
-      else if ((PointStateType.Up == state || PointStateType.Leave == state || PointStateType.Interrupted == state) &&
-               animationState == TOUCH_ANIMATION_STATE.ON_ANIMATION)
-      {
-        animationState = TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION;
-
-        // To launch finish animation, we get latest velocty from frame callback
-        float lastVelocity = frameUpdateCallback.GetVelocity();
-
-        /* TUNING */
-        // This is just for turning of finish animation.
-        // change the values if you want.
-        lastVelocity = Math.Max(lastVelocity, -3.5f);
-        lastVelocity = Math.Min(lastVelocity, 3.5f);
-        if (Math.Abs(lastVelocity) < 0.0001f)
+        // Add icons
+
+        void AddIcon(string background, string icon, string text, Color textColor)
         {
-          // If velocity is zero. just start animationOfftimer.
-          animationOffTimer.Start();
+            ImageView backgroundView = new ImageView();
+            backgroundView.ResourceUrl = background;
+            backgroundView.Size = new Size(OBJECT_SIZE, OBJECT_SIZE);
+            backgroundView.ParentOrigin = ParentOrigin.CenterLeft;
+            backgroundView.PivotPoint = PivotPoint.CenterLeft;
+            backgroundView.PositionUsesPivotPoint = true;
+            layoutView.Add(backgroundView);
+            frameUpdateCallback.AddId(backgroundView.ID);
+
+            ImageView iconView = new ImageView();
+            iconView.ResourceUrl = icon;
+            iconView.Position = new Position(0, -15);
+            iconView.ParentOrigin = ParentOrigin.Center;
+            iconView.PivotPoint = PivotPoint.Center;
+            iconView.PositionUsesPivotPoint = true;
+            backgroundView.Add(iconView);
+
+            TextLabel label = new TextLabel(text);
+            label.Position = new Position(0, 30);
+            label.HorizontalAlignment = HorizontalAlignment.Center;
+            label.TextColor = textColor;
+            label.FontFamily = "SamsungOneUI";
+            label.PointSize = 12;
+            label.ParentOrigin = ParentOrigin.Center;
+            label.PivotPoint = PivotPoint.Center;
+            label.PositionUsesPivotPoint = true;
+            backgroundView.Add(label);
         }
-        else
+
+        // Set frame callback to start drag animation.
+        private void SetFrameUpdateCallback(float position)
         {
-          // If velocity is not zero, make decelerating animation.
-          Decelerating(lastVelocity);
+            // remove frame callback if it is already added.
+            window.RemoveFrameUpdateCallback(frameUpdateCallback);
+
+            frameUpdateCallback.ResetAnimationData();
+            frameUpdateCallback.AddMovement(0.0f); // Add first movement.
+
+            // Set container start position and start positions of each icon(and vertical bar)
+            // And compute total container size.
+            float totalSize = 0.0f;
+            frameUpdateCallback.SetContainerStartPosition(controlView.Position.X);
+            for (int i = 0; i < layoutView.ChildCount; ++i)
+            {
+                frameUpdateCallback.SetViewPosition(i, layoutView.Children[i].Position.X);
+                totalSize += (float)(layoutView.Children[i].Size.Width + DEFAULT_SPACE);
+            }
+            totalSize -= (float)DEFAULT_SPACE;
+
+            // Find touched icon
+            for (int i = (int)layoutView.ChildCount - 1; i >= 0; --i)
+            {
+                if (position >= layoutView.Children[i].Position.X + controlView.Position.X)
+                {
+                    frameUpdateCallback.SetTouchedViewIndex(i);
+                    touchedViewIndex = i;
+                    break;
+                }
+            }
+            if (position < layoutView.Children[0].Position.X + controlView.Position.X)
+            {
+                frameUpdateCallback.SetTouchedViewIndex(0);
+                touchedViewIndex = 0;
+            }
+
+            previousTouchedPosition = position;
+
+            // Add frame callback on window.
+            // OnUpdate callback of frameUpdateCallback will be called before every render frame.
+            // We can set root view what given frameUpdateCallback used
+            window.AddFrameUpdateCallback(frameUpdateCallback, controlView);
+
+            // compute limit position the container could go.
+            leftDirectionLimit = (float)window.Size.Width - (totalSize + (float)(INITIAL_POSITION));
+
+            window.RenderingBehavior = RenderingBehaviorType.Continuously; // make rendering be done for upto 60 fps even though there is no update in main thread.
+            animationState = TOUCH_ANIMATION_STATE.ON_ANIMATION; // make rendering state on.
         }
-      }
-      // set currently visible icons for optimization
-      SetVisibleLimit();
-      // make frame callback dirty.
-      frameUpdateCallback.Dirty();
-      return true;
-    }
 
-    private void SetVisibleLimit()
-    {
-      int leftViewIndex = touchedViewIndex;
-      for (; leftViewIndex >= 0; --leftViewIndex)
-      {
-        float newPosition = layoutView.Children[leftViewIndex].Position.X + controlView.Position.X;
-        if (newPosition + (float)layoutView.Children[leftViewIndex].Size.Width < 0.0f)
+        private bool OnTouch(object source, View.TouchEventArgs e)
         {
-          break;
+            Vector2 position = e.Touch.GetScreenPosition(0);
+
+            PointStateType state = e.Touch.GetState(0);
+            if (PointStateType.Down == state)
+            {
+                if (animationState == TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION)
+                {
+                    // re-birth current animation
+                    // in case of touch during finish animation,
+                    // quit easingAnimation and AnimationOffTimer because animation ownership is returned to the touch event again.
+                    // AND, DO NOT RESET ALL PROPERTIES OF FRAMECALLBACK.
+                    // because, for example, if touched icon index is changed, the movement is wrong and the animation can be not continous.
+                    // This re-birthed animation is just for smooth moving during complex user interaction.
+                    // during complex and fast interaction, this is not so noticeable.
+                    // and reset of such properties will be done in the below Motion state
+                    finishAnimation.Stop();
+                    animationOffTimer.Stop();
+
+                    // Set Animation State to ON_ANIMATION again
+                    animationState = TOUCH_ANIMATION_STATE.ON_ANIMATION;
+                    // Set previousTouchPosition
+                    previousTouchedPosition = position.X;
+                }
+                else
+                {
+                    // in case of stable state
+                    // just set new framecallback for this touched position.
+                    SetFrameUpdateCallback(position.X);
+                }
+            }
+            else if (PointStateType.Motion == state)
+            {
+                // if framecallback can be reset, quit current frame callback and re-launch new frame callback.
+                // because, if current frame callback is re-birthed one, the animation is not totally re-created one.
+                // So, some properties like touched icon index can be wrong for the continuous animation.
+                // But, some case like that finger is stopped and restart to move, this could make weired feeling.
+                // We reset frameUpdateCallback as soon as possible we can. And the conditions are ...
+                // 1. icons in screen is stopped.
+                // 2. velocity of frame callback is 0.0 (this frame callback will not move again instantly)
+                // 3. frame callback is not dirty (there is no reserved action)
+                if (frameUpdateCallback.IsResetTouchedViewPossible() && frameUpdateCallback.GetVelocity() == 0.0f && !frameUpdateCallback.IsDirty())
+                {
+                    SetFrameUpdateCallback(position.X);
+                }
+
+                // Set new controlView(container) position
+                // in here, we need to consider the container is not go outside of limits.
+                float containerPosition = controlView.Position.X + (position.X - previousTouchedPosition);
+                containerPosition = Math.Min(containerPosition, rightDirectionLimit);
+                containerPosition = Math.Max(containerPosition, leftDirectionLimit);
+                float adjustedPosition = containerPosition - controlView.Position.X + previousTouchedPosition;
+                previousTouchedPosition = adjustedPosition;
+                controlView.Position.X = containerPosition;
+            }
+            else if ((PointStateType.Up == state || PointStateType.Leave == state || PointStateType.Interrupted == state) &&
+                     animationState == TOUCH_ANIMATION_STATE.ON_ANIMATION)
+            {
+                animationState = TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION;
+
+                // To launch finish animation, we get latest velocty from frame callback
+                float lastVelocity = frameUpdateCallback.GetVelocity();
+
+                /* TUNING */
+                // This is just for turning of finish animation.
+                // change the values if you want.
+                lastVelocity = Math.Max(lastVelocity, -3.5f);
+                lastVelocity = Math.Min(lastVelocity, 3.5f);
+                if (Math.Abs(lastVelocity) < 0.0001f)
+                {
+                    // If velocity is zero. just start animationOfftimer.
+                    animationOffTimer.Start();
+                }
+                else
+                {
+                    // If velocity is not zero, make decelerating animation.
+                    Decelerating(lastVelocity);
+                }
+            }
+            // set currently visible icons for optimization
+            SetVisibleLimit();
+            // make frame callback dirty.
+            frameUpdateCallback.Dirty();
+            return true;
         }
-      }
-      leftViewIndex = Math.Max(leftViewIndex, 0);
-      int rightViewIndex = touchedViewIndex;
-      for (; rightViewIndex < layoutView.ChildCount; ++rightViewIndex)
-      {
-        float newPosition = layoutView.Children[rightViewIndex].Position.X + controlView.Position.X;
-        if (newPosition > window.Size.Width)
+
+        private void SetVisibleLimit()
         {
-          break;
-        }
-      }
-      rightViewIndex = Math.Min(rightViewIndex, (int)layoutView.ChildCount - 1);
+            int leftViewIndex = touchedViewIndex;
+            for (; leftViewIndex >= 0; --leftViewIndex)
+            {
+                float newPosition = layoutView.Children[leftViewIndex].Position.X + controlView.Position.X;
+                if (newPosition + (float)layoutView.Children[leftViewIndex].Size.Width < 0.0f)
+                {
+                    break;
+                }
+            }
+            leftViewIndex = Math.Max(leftViewIndex, 0);
+            int rightViewIndex = touchedViewIndex;
+            for (; rightViewIndex < layoutView.ChildCount; ++rightViewIndex)
+            {
+                float newPosition = layoutView.Children[rightViewIndex].Position.X + controlView.Position.X;
+                if (newPosition > window.Size.Width)
+                {
+                    break;
+                }
+            }
+            rightViewIndex = Math.Min(rightViewIndex, (int)layoutView.ChildCount - 1);
 
-      frameUpdateCallback.SetLeftIndex(leftViewIndex);
-      frameUpdateCallback.SetRightIndex(rightViewIndex);
-    }
+            frameUpdateCallback.SetLeftIndex(leftViewIndex);
+            frameUpdateCallback.SetRightIndex(rightViewIndex);
+        }
 
-    // set decelerating properties
-    // in this example, we used decelerate animation in "https://medium.com/@esskeetit/scrolling-mechanics-of-uiscrollview-142adee1142c"
-    // But, if this method is problematic or violate some patent of other company, change this other way.
-    // We didn't checked anything.
-    // Only thing we need to remember when we change this animation is to add "EasingAnimationFinishedCallback" for the new animation.
-    private void Decelerating(float lastVelocity)
-    {
-      absoluteVelocity = Math.Abs(lastVelocity);
-      finishAnimationDelta = (absoluteVelocity * decelerationRate) / (1 - decelerationRate);
-      float destination = (lastVelocity > 0) ? controlView.Position.X + finishAnimationDelta : controlView.Position.X - finishAnimationDelta;
-
-      if (destination < leftDirectionLimit || destination > rightDirectionLimit)
-      {
-        finishAnimationDelta = lastVelocity > 0 ? (rightDirectionLimit - controlView.Position.X) : (controlView.Position.X - leftDirectionLimit);
-        destination = lastVelocity > 0 ? rightDirectionLimit : leftDirectionLimit;
-        if (finishAnimationDelta == 0)
+        // set decelerating properties
+        // in this example, we used decelerate animation in "https://medium.com/@esskeetit/scrolling-mechanics-of-uiscrollview-142adee1142c"
+        // But, if this method is problematic or violate some patent of other company, change this other way.
+        // We didn't checked anything.
+        // Only thing we need to remember when we change this animation is to add "EasingAnimationFinishedCallback" for the new animation.
+        private void Decelerating(float lastVelocity)
         {
-          finishAnimationDuration = 0.0f;
+            absoluteVelocity = Math.Abs(lastVelocity);
+            finishAnimationDelta = (absoluteVelocity * decelerationRate) / (1 - decelerationRate);
+            float destination = (lastVelocity > 0) ? controlView.Position.X + finishAnimationDelta : controlView.Position.X - finishAnimationDelta;
+
+            if (destination < leftDirectionLimit || destination > rightDirectionLimit)
+            {
+                finishAnimationDelta = lastVelocity > 0 ? (rightDirectionLimit - controlView.Position.X) : (controlView.Position.X - leftDirectionLimit);
+                destination = lastVelocity > 0 ? rightDirectionLimit : leftDirectionLimit;
+                if (finishAnimationDelta == 0)
+                {
+                    finishAnimationDuration = 0.0f;
+                }
+                else
+                {
+                    finishAnimationDuration = (float)Math.Log((finishAnimationDelta * logDeceleration / absoluteVelocity + 1), decelerationRate);
+                }
+            }
+            else
+            {
+                if (finishAnimationDelta == 0)
+                {
+                    finishAnimationDuration = 0.0f;
+                }
+                else
+                {
+                    finishAnimationDuration = (float)Math.Log(-easingThreshold * logDeceleration / absoluteVelocity) / logDeceleration;
+                }
+            }
+
+            finishAnimation.Clear();
+            customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
+            finishAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
+            GC.KeepAlive(customScrollAlphaFunction);
+            finishAnimation.Duration = (int)finishAnimationDuration;
+            finishAnimation.AnimateTo(controlView, "PositionX", destination);
+            finishAnimation.Play();
         }
-        else
+
+        private float CustomScrollAlphaFunction(float progress)
         {
-          finishAnimationDuration = (float)Math.Log((finishAnimationDelta * logDeceleration / absoluteVelocity + 1), decelerationRate);
+            if (finishAnimationDelta == 0)
+            {
+                return 1.0f;
+            }
+            else
+            {
+                float realDuration = progress * finishAnimationDuration;
+                float realDistance = absoluteVelocity * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logDeceleration;
+                float result = Math.Min(realDistance / Math.Abs(finishAnimationDelta), 1.0f);
+
+                return result;
+            }
         }
-      }
-      else
-      {
-        if (finishAnimationDelta == 0)
+
+        private void EasingAnimationFinishedCallback(object sender, EventArgs e)
         {
-          finishAnimationDuration = 0.0f;
+            if (animationState != TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION)
+            {
+                return;
+            }
+
+            // start Animation Off Timer
+            finishAnimation.Clear();
+            SetVisibleLimit();
+            animationOffTimer.Start();
         }
-        else
+
+        // Check each icons in screen is not moving.
+        // If it is, finish all animation and make animationstate End_animation(NO_ANIMATION)
+        private bool OffAnimatable(object target, Timer.TickEventArgs args)
         {
-          finishAnimationDuration = (float)Math.Log(-easingThreshold * logDeceleration / absoluteVelocity) / logDeceleration;
+            if (frameUpdateCallback.IsResetTouchedViewPossible())
+            {
+                window.RenderingBehavior = RenderingBehaviorType.IfRequired;
+                window.RemoveFrameUpdateCallback(frameUpdateCallback);
+                animationOffTimer.Stop();
+                animationState = TOUCH_ANIMATION_STATE.END_ANIMATION;
+                return false;
+            }
+            SetVisibleLimit();
+            return true;
         }
-      }
-
-      finishAnimation.Clear();
-      customScrollAlphaFunction = new UserAlphaFunctionDelegate(CustomScrollAlphaFunction);
-      finishAnimation.DefaultAlphaFunction = new AlphaFunction(customScrollAlphaFunction);
-      GC.KeepAlive(customScrollAlphaFunction);
-      finishAnimation.Duration = (int)finishAnimationDuration;
-      finishAnimation.AnimateTo(controlView, "PositionX", destination);
-      finishAnimation.Play();
-    }
-
-    private float CustomScrollAlphaFunction(float progress)
-    {
-      if (finishAnimationDelta == 0)
-      {
-        return 1.0f;
-      }
-      else
-      {
-        float realDuration = progress * finishAnimationDuration;
-        float realDistance = absoluteVelocity * ((float)Math.Pow(decelerationRate, realDuration) - 1) / logDeceleration;
-        float result = Math.Min(realDistance / Math.Abs(finishAnimationDelta), 1.0f);
-
-        return result;
-      }
-    }
-
-    private void EasingAnimationFinishedCallback(object sender, EventArgs e)
-    {
-      if (animationState != TOUCH_ANIMATION_STATE.ON_FINISH_ANIMATION)
-      {
-        return;
-      }
-
-      // start Animation Off Timer
-      finishAnimation.Clear();
-      SetVisibleLimit();
-      animationOffTimer.Start();
-    }
-
-    // Check each icons in screen is not moving.
-    // If it is, finish all animation and make animationstate End_animation(NO_ANIMATION)
-    private bool OffAnimatable(object target, Timer.TickEventArgs args)
-    {
-      if (frameUpdateCallback.IsResetTouchedViewPossible())
-      {
-        window.RenderingBehavior = RenderingBehaviorType.IfRequired;
-        window.RemoveFrameUpdateCallback(frameUpdateCallback);
-        animationOffTimer.Stop();
-        animationState = TOUCH_ANIMATION_STATE.END_ANIMATION;
-        return false;
-      }
-      SetVisibleLimit();
-      return true;
     }
-  }
 }
\ No newline at end of file