1

So I made an interface with reference to an SVG file where coordinates of views are copied from. For instance, the whole SVG image is 250x800, so I set the width and height of the AbsoluteLayout to be the same. The individual control sizes are also copied to the layout bounds, each are 250x200 in this example. Corner radius properties, font sizes, border radius properties are also copied, but for the sake of brevity, the use is not shown here.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="AndroidSquare.MainPage">
    <AbsoluteLayout WidthRequest="250" HeightRequest="800" HorizontalOptions="Center" VerticalOptions="Center">
        <BoxView Color="Red"    AbsoluteLayout.LayoutBounds="0,  0,250,200"/>
        <BoxView Color="Yellow" AbsoluteLayout.LayoutBounds="0,200,250,200"/>
        <BoxView Color="Green"  AbsoluteLayout.LayoutBounds="0,400,250,200"/>
        <BoxView Color="Blue"   AbsoluteLayout.LayoutBounds="0,600,250,200"/>

    </AbsoluteLayout>

</ContentPage>

Running it in the Android Emulator provided with Visual Studio 16.1.4 (Nexus 5X, Android 9.0, x86):

Fail

As you can see, the view is too large. The following piece of code scales it back.

public MainPage()
{
    InitializeComponent();
    ScalePage(this);
}
static void ScalePage(ContentPage page) {
    void Scale(object _, EventArgs __) {
        var contentMeasure = page.Content.Measure(double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
        var contentWidth = contentMeasure.Request.Width;
        var contentHeight = contentMeasure.Request.Height;
        var contentWidthOnScreen = page.Content.Width;
        var contentHeightOnScreen = page.Content.Height;
        var displayInfo = Xamarin.Essentials.DeviceDisplay.MainDisplayInfo;
        var (appWidth, appHeight) =
            Device.RuntimePlatform == Device.UWP ?
                // Sadly, Xamarin.Essentials.DeviceDisplay.MainDisplayInfo returns the size of the whole monitor, not the app window size
                (page.Width, page.Height) :
                // Sadly, page.Width and page.Height are both -1. on start up with Android which are uninitialized values.
                (displayInfo.Width / displayInfo.Density, displayInfo.Height / displayInfo.Density);
        var scaleW = appWidth / contentWidth;
        var scaleH = appHeight / contentHeight;
        var scale = Math.Min(scaleW, scaleH);
        page.Scale = scale;
        // Move page back to screen
        // Scale is applied to translation as well
        if (scale < 1) {
            page.TranslationX = (contentWidthOnScreen - contentWidth) / 2 * scale * scale;
            page.TranslationY = (contentHeightOnScreen - contentHeight) / 2 * scale * scale;
        }
    }
    page.Appearing += Scale;
    page.SizeChanged += Scale;
}

It now looks like this:

Still fail

How to make the bottom blue box show itself in its entirety? This seems like a problem specifically for Android, because on UWP it looks like this:

UWP is bae

Referencing Android View Disappearing When Go Outside Of Parent, I have tried a platform effect that sets all parents and children to not clip children out of bounds:

[assembly: Xamarin.Forms.ResolutionGroupName("My")]
[assembly: Xamarin.Forms.ExportEffect(typeof(AndroidSquare.Droid.AndroidVisibleOutOfBoundsChildrenEffect), "Effect")]

namespace AndroidSquare.Droid
{
    class AndroidVisibleOutOfBoundsChildrenEffect : Xamarin.Forms.Platform.Android.PlatformEffect
    {
        protected override void OnAttached()
        {
            void NoClip(Android.Views.ViewGroup v)
            {
                v.SetClipChildren(false);
                v.SetClipToPadding(false);
            }
            void NoClipChildren(Android.Views.ViewGroup v)
            {
                NoClip(v);
                for (var i = 0; i < v.ChildCount; i++)
                    if (v.GetChildAt(i) is Android.Views.ViewGroup child)
                        NoClipChildren(child);
            }
            void NoClipParent(Android.Views.ViewGroup v)
            {
                NoClip(v);
                if (v.Parent is Android.Views.ViewGroup parent) NoClipParent(parent);
            }
            // Control is null, effect is used on ContentPage
            NoClipChildren(Container);
            NoClipParent(Container);
        }
        protected override void OnDetached() { }
    }
}
public MainPage()
{
    InitializeComponent();
    ScalePage(this);
    Effects.Add(Effect.Resolve("My.Effect"));
}

Breakpoints set inside the effect are hit as expected, however, the output still looks like the second screenshot. How to make the Android version look the same as UWP? I have been stuck on this issue for days.

Happypig375
  • 1,022
  • 1
  • 12
  • 32
  • Why do you even need to have to calculate the scale yourself? Why not put the 4 squares into a grid view where you allocate 25% of the height to each row? Wouldn't that also work, or do you _really_ want to do this with an effect? – Cheesebaron Jun 30 '19 at 20:51
  • That won't work for corner radius, font size, etc. Those won't accept star values. – Happypig375 Jul 01 '19 at 04:11
  • I mentioned the use of these at the top of the question. – Happypig375 Jul 01 '19 at 04:11

1 Answers1

1

How to make the bottom blue box show itself in its entirety? This seems like a problem specifically for Android

If you scale your page as the code, because the screen width and height don'e change, so you just scale the AbsoluteLayout.

When I don't use the code to scale, I also don't see the entire bule boxview, so you just add Scrollview to display all boxviews.

<ScrollView>
        <AbsoluteLayout
            HeightRequest="800"
            HorizontalOptions="Center"
            VerticalOptions="Center"
            WidthRequest="250">
            <BoxView AbsoluteLayout.LayoutBounds="0,  0,250,200" Color="Red" />
            <BoxView AbsoluteLayout.LayoutBounds="0,200,250,200" Color="Yellow" />
            <BoxView AbsoluteLayout.LayoutBounds="0,400,250,200" Color="Green" />
            <BoxView AbsoluteLayout.LayoutBounds="0,600,250,200" Color="Blue" />

        </AbsoluteLayout>
    </ScrollView>

enter image description here

Cherry Bu - MSFT
  • 10,160
  • 1
  • 10
  • 16
  • This requires the user to scroll to see everything though. This would look ugly. Is there a way to show everything on one screen? – Happypig375 Jul 01 '19 at 04:10
  • @Happypig375,You set AbsoluteLayout height 800, but the android device screen height is not so big, so you need to use Scrollview to scroll. – Cherry Bu - MSFT Jul 03 '19 at 05:51
  • With the scaling code, I can scale it to the screen height. But Android deletes the views originally below the screen height. UWP doesn't have this problem. Everything can be fit into the screen, as shown in the result for the same code for UWP. – Happypig375 Jul 03 '19 at 11:08