Untitled

mail@pastecode.io avatarunknown
plain_text
a month ago
4.8 kB
2
Indexable
Never
public class CustomScrollView : ScrollView
{
    /// <summary>
    /// InputScrollX is a receiver bindable property to reflect 
    /// ScrollView X coordinate changing from outer clients.
    /// Update flow: Scroller -> InternalSequencer -> ScrollView.
    /// Whereas, ScrollView.ScrollX is a transmitter property to reflect 
    /// X coordinate changing from ScrollView to outer clients.
    /// Update flow: Scrubber -> ScrollView -> InternalSequencer -> Scroller
    /// </summary>
    public static readonly BindableProperty InputScrollXProperty =
        BindableProperty.CreateAttached(
            propertyName: "InputScrollX",
            returnType: typeof(double),
            declaringType: typeof(CustomScrollView),
            defaultValue: 0d,
            propertyChanged: InputScrollXChanged);

    /// <summary>
    /// Ratio of visible ScrollView content width to 
    /// the whole ScrollView content width, 
    /// including invisible part beyond the edge of the screen.
    /// Used to correct reflection calculation of visible part of the screen on mini-scroller.
    /// </summary>
    public static readonly BindableProperty WidthRatioProperty =
        BindableProperty.CreateAttached(
            propertyName: "WidthRatio",
            returnType: typeof(double),
            declaringType: typeof(CustomScrollView),
            defaultValue: 1d);

    /// <summary>
    /// Width of the whole ScrollView content, including invisible part beyond the edge of the screen.
    /// </summary>
    public static readonly BindableProperty TotalWidthProperty =
       BindableProperty.CreateAttached(
           propertyName: "TotalWidth",
           returnType: typeof(double),
           declaringType: typeof(CustomScrollView),
           defaultValue: 0d);

    
    public static double GetInputScrollX(BindableObject view)
    {
        return (double)view.GetValue(InputScrollXProperty);
    }

    public static void SetInputScrollX(BindableObject view, double value)
    {
        view.SetValue(InputScrollXProperty, value);
    }

    public static double GetWidthRatio(BindableObject view)
    {
        return (double)view.GetValue(WidthRatioProperty);
    }

    public static void SetWidthRatio(BindableObject view, double value)
    {
        view.SetValue(WidthRatioProperty, value);
    }

    public static double GetTotalWidth(BindableObject view)
    {
        return (double)view.GetValue(TotalWidthProperty);
    }

    public static void SetTotalWidth(BindableObject view, double value)
    {
        view.SetValue(TotalWidthProperty, value);
    }
    

    private static void ScrollViewPropertyChanged(object? sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName is nameof(ScrollView.ContentSize) && sender is ScrollView scrollView)
        {
            ScrollViewContentSizeChanged(scrollView);
        }
    }

    private static void ScrollViewContentSizeChanged(ScrollView scrollView)
    {
        SetTotalWidth(scrollView, scrollView.ContentSize.Width);
        UpdateWidthRatio(scrollView);
    }

    private static async void InputScrollXChanged(BindableObject view, object oldValue, object newValue)
    {
        if (view is not ScrollView scrollView || scrollView.IsEnabled is false)
        {
            return;
        }

        var scrollX = (double)newValue;
        scrollX = AdjustToScrollerCenter(scrollView, scrollX);

        var targetScrollX = scrollX > 0 ? scrollX : 0;
        await scrollView.ScrollToAsync(targetScrollX, 0, false);
    }

    /// <summary>
    /// Adjust target scrollX coordinate for ScrollView, 
    /// to correlate ScrollView moving with the mini Scroller moving,
    /// relative to Scroller center.
    /// </summary>
    private static double AdjustToScrollerCenter(ScrollView scrollView, double scrollX)
    {
        return scrollX - 0.5 * scrollView.Width;
    }

    /// <summary>
    /// Returns width of the whole ScrollView content, 
    /// including invisible part beyond the edge of the screen
    /// </summary>
    private static double GetContentSizeWidth(ScrollView scrollView)
    {
        return scrollView.ContentSize.Width > 0 ? scrollView.ContentSize.Width : scrollView.Width;
    }

    private static void UpdateWidthRatio(ScrollView scrollView)
    {
        var widthRatio = scrollView.Width / GetContentSizeWidth(scrollView);
        SetWidthRatio(scrollView, widthRatio);
    }

    
    private void ScrollViewSizeChanged(object? sender, EventArgs e)
    {
        if (sender is not ScrollView scrollView)
        {
            return;
        }

        UpdateWidthRatio(scrollView);
    }
}