private void SendFakeEvent(MotionEvent e, MotionEventActions forcedAction)
        {
            var fake_event = MotionEvent.ObtainNoHistory(e);

            fake_event.Action = forcedAction;
            ContentView.ForceHandleTouchEvent(fake_event);
            fake_event.Recycle();
        }
        private bool HandleUpEvent(MotionEvent e)
        {
            last_touch_y = -1;

            if (refresh_state == PullToRefresharpRefreshState.ReleaseToRefresh)
            {
                UpdateRefreshState(PullToRefresharpRefreshState.Refreshing);
            }

            if (current_scroll_y < 0)
            {
                Post(StartSnapback);
                ContentView.IgnoreTouchEvents = false;
            }

            if (did_steal_event_stream && current_scroll_y >= 0)
            {
                did_steal_event_stream = false;
                ContentView.ForceHandleTouchEvent(e);
            }

            if (should_cancel_before_up)
            {
                // This means we revealed the ptr header and should cancel the event
                // so the content view doesn't register a click on up.
                should_cancel_before_up = false;
                SendFakeEvent(e, MotionEventActions.Cancel);
            }
            else if (should_send_down_before_up)
            {
                should_send_down_before_up = false;
                SendFakeEvent(e, MotionEventActions.Down);
            }

            return(false);
        }
        public override bool OnTouchEvent(MotionEvent e)
        {
            if (!IsPullEnabled)
            {
                return(base.OnTouchEvent(e));
            }

            if (null == ContentView)
            {
                return(base.OnTouchEvent(e));
            }

            if (ContentView is AbsListView && ((AbsListView)ContentView).FastScrollEnabled)
            {
                // An adimittedly crude way to determine if touch is in fast scroll, but
                // should accomplish the goal of not displaying ptr header.
                // This is crude because there is not a definitive way to determine
                // a) if the fast scroller is visible or
                // b) the width of the scroller
                if (Resources.DisplayMetrics.WidthPixels - e.RawX < fastscroll_thumb_width)
                {
                    return(false); // let the list view handle this
                }
            }

            switch (e.ActionMasked)
            {
            case MotionEventActions.Down:
                last_touch_y = ContentView.IsAtTop ? (int)e.RawY : -1;
                if (!ContentView.IsAtTop)
                {
                    send_down_event = false;     // because this event will reach the ContentView
                }
                else
                {
                    should_send_down_before_up = true;
                }
                return(ContentView.IsAtTop);

            case MotionEventActions.Move:
                if (did_steal_event_stream && current_scroll_y >= 0)
                {
                    if (send_down_event)
                    {
                        did_steal_event_stream        = false;
                        send_down_event               = false;
                        ContentView.IgnoreTouchEvents = false;
                        SendFakeEvent(e, MotionEventActions.Down);
                    }
                    return(true);
                }

                if (last_touch_y == -1)
                {
                    last_touch_y = (int)e.RawY;
                    return(true);
                }

                var y_delta = last_touch_y - (int)e.RawY;
                last_touch_y = (int)e.RawY;

                bool isMovingUp = y_delta > 0;
                if (isMovingUp && current_scroll_y >= 0 || !ContentView.IsAtTop)
                {
                    should_send_down_before_up = false;
                    ContentView.ForceHandleTouchEvent(e);
                    return(true);
                }

                if (ContentView.IsAtTop)
                {
                    var new_scroll_to = isMovingUp ? (int)y_delta : (int)(y_delta * PullDownTensionFactor);
                    // see if this will fully hide the header
                    if (current_scroll_y < 0 && current_scroll_y + new_scroll_to > 0)
                    {
                        // only scroll enough to hide the header
                        new_scroll_to = -current_scroll_y;
                    }

                    current_scroll_y += new_scroll_to;

                    Header.OffsetTopAndBottom(-new_scroll_to);
                    ((View)ContentView).OffsetTopAndBottom(-new_scroll_to);
                    ViewCompat.PostInvalidateOnAnimation(this);

                    if (current_scroll_y == 0)
                    {
                        ContentView.IgnoreTouchEvents = false;
                        return(true);
                    }
                    else
                    {
                        should_cancel_before_up    = true;
                        should_send_down_before_up = false;
                    }

                    if (Math.Abs(current_scroll_y) >= header_measured_height)
                    {
                        if (refresh_state != PullToRefresharpRefreshState.Refreshing)
                        {
                            SetPullDownIconProgress(1);
                            UpdateRefreshState(PullToRefresharpRefreshState.ReleaseToRefresh);
                        }
                    }
                    else
                    {
                        // Don't update anything if we are refreshing.
                        if (refresh_state != PullToRefresharpRefreshState.Refreshing)
                        {
                            SetPullDownIconProgress((float)Math.Abs(current_scroll_y) / (float)header_measured_height);
                            UpdateRefreshState(PullToRefresharpRefreshState.PullToRefresh);
                        }
                    }

                    return(true);
                }
                return(false);

            case MotionEventActions.Up:
                return(HandleUpEvent(e));
            }

            return(base.OnTouchEvent(e));
        }