Dennis Schröer
Dennis Schröer

Reputation: 2422

Make WebView's list scrollable

I have a page which looks like this*:

<ScrollView>
  <WebView/>
  <WebView/>
  <WebView/>
</Scrollview>

* not the actual layout, but that it enough for the question and makes the problem clearer.

I don't know how exactly a WebView looks like because it can be very variable. A WebView can contain a scrollable list.

I want to achieve this scrolling behaviour:

enter image description here

These are two WebViews (the one with the blue header and the one with the green header). I only want to scroll the list in the blue WebView when I'm dragging in the list. If I touch anywhere else (for example on the blue or green header), I want to scroll in the ScrollView.

My attempt to solve this is a CustomRenderer for the WebView, which I found in this thread in the Xamarin forums:

protected override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
    base.OnElementChanged(e);

    if (e.OldElement != null)
    {
        Control.Touch -= Control_Touch;
    }

    if (e.NewElement != null)
    {
        Control.Touch += Control_Touch;
    }
}

void Control_Touch(object sender, TouchEventArgs e)
{
    // Executing this will prevent the Scrolling to be intercepted by parent views
    switch (e.Event.Action)
    {
        case MotionEventActions.Down:
            Control.Parent.RequestDisallowInterceptTouchEvent(true);
            break;
        case MotionEventActions.Up:
            Control.Parent.RequestDisallowInterceptTouchEvent(false);
            break;
    }
    // Calling this will allow the scrolling event to be executed in the WebView
    Control.OnTouchEvent(e.Event);
}

This nearly does the job, I can scroll in the list inside of the WebView now, but it won't scroll in the ScrollView anymore if I tap anywhere else in the WebView.

So I tried to check for the height of the inner list:

case MotionEventActions.Down:
    if (ComputeVerticalScrollRange() > MeasuredHeight)
        Control.Parent.RequestDisallowInterceptTouchEvent(true);
    break;
case MotionEventActions.Up:
    if (ComputeVerticalScrollRange() > MeasuredHeight)
        Control.Parent.RequestDisallowInterceptTouchEvent(false);
    break;

MeasuredHeight is the height of the WebView. ComputeVerticalScrollRange() will always return the same height as MeasuredHeight tho. So one possible solution would be to get the height of the inner list in the WebView, but I don't know how I can get that (I'm new to Android). And maybe, there are even other solutions to this.

Upvotes: 0

Views: 437

Answers (1)

Grace Feng
Grace Feng

Reputation: 16652

For the code Control.Parent.RequestDisallowInterceptTouchEvent(true);, you will need to make sure this Control.Parent is really the ScrollViewer.

AFAIK, to implement the view for each platform, it uses renderers, the Control.Parent here is the WebViewRenderer, so I think what you need is possible this:

Control.Parent.Parent.Parent.Parent.RequestDisallowInterceptTouchEvent(true);

The real problem is when to set it to true or false, you may for example modify your Control_Touch event like this:

private void Control_Touch(object sender, TouchEventArgs e)
{
    var viewheight = Control.MeasuredHeight;
    var height = (int)Math.Floor(Control.ContentHeight * Control.Scale);
    switch (e.Event.Action)
    {
        case MotionEventActions.Down:
            downY = e.Event.GetY();
            mAction = ActionState.None;
            Control.Parent.Parent.Parent.Parent.RequestDisallowInterceptTouchEvent(true);
            break;

        case MotionEventActions.Move:
            upY = e.Event.GetY();
            var delta = downY - upY;
            if (Math.Abs(delta) > MIN_DISTANCE)
            {
                if (delta < 0)
                {
                    mAction = ActionState.TB;
                    //top reached
                    if (Control.ScrollY == 0)
                    {
                        Control.Parent.Parent.Parent.Parent.RequestDisallowInterceptTouchEvent(false);
                    }
                }
                else if (delta > 0)
                {
                    mAction = ActionState.BT;

                    //bottom reached
                    if (Control.ScrollY + viewheight + 5 >= height)
                    {
                        Control.Parent.Parent.Parent.Parent.RequestDisallowInterceptTouchEvent(false);
                    }
                }
            }
            break;

        case MotionEventActions.Up:
            Control.Parent.Parent.Parent.Parent.RequestDisallowInterceptTouchEvent(true);
            break;
    }
    Control.OnTouchEvent(e.Event);
}

I used two custom WebView to test my demo, the idea is to disable scrolling of WebView when it reached top and it is been scrolling from top to bottom, or vice versa. You may have a try.

Upvotes: 1

Related Questions