Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the same interface rendered differently on an emulator and a device?

I am writing a custom overlay for a ZXing's QR reader. Here is the working example:

public partial class CustomScanPage : ContentPage
{
    ZXingScannerView zxing;
    private List<BoxView> _boxes;


    public CustomScanPage() : base()
    {
        zxing = new ZXingScannerView
        {
            HorizontalOptions = LayoutOptions.FillAndExpand,
            VerticalOptions = LayoutOptions.FillAndExpand
        };
        zxing.OnScanResult += (result) =>
            Device.BeginInvokeOnMainThread(async () =>
            {
                zxing.IsAnalyzing = false;

                SetBoxesColor(Color.FromHex("#76ff03"));
                await Task.Delay(2000);

                await DisplayAlert("Scanned Barcode", result.Text, "OK");

                SetBoxesColor(Color.White);
            });
        zxing.Options = new MobileBarcodeScanningOptions {
            PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE }
        };
        var overlay = BuildGrid();

        var grid = new Grid
        {
            VerticalOptions = LayoutOptions.FillAndExpand,
            HorizontalOptions = LayoutOptions.FillAndExpand,
        };
        grid.Children.Add(zxing);
        grid.Children.Add(overlay);

        Content = grid;
    }

    protected override void OnAppearing()
    {
        base.OnAppearing();

        zxing.IsScanning = true;
    }

    protected override void OnDisappearing()
    {
        zxing.IsScanning = false;

        base.OnDisappearing();
    }

    private AbsoluteLayout BuildGrid()
    {
        var al = new AbsoluteLayout();
        var mask = new BoxView
        {
            HorizontalOptions = LayoutOptions.Fill,
            VerticalOptions = LayoutOptions.Fill,
            BackgroundColor = Color.Transparent
        };

        var maskSide = 196;

        var yBegin = Math.Round((App.ScreenHeight - maskSide) / 2);
        var xBegin = Math.Round((App.ScreenWidth - maskSide) / 2);
        var barLong = 40;
        var barShort = 4;
        var barColor = Color.White;

        var grid = new Grid { ColumnSpacing = 0, RowSpacing = 0 };
        grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(yBegin, GridUnitType.Absolute) });
        grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(maskSide, GridUnitType.Absolute) });
        grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(App.ScreenHeight - yBegin, GridUnitType.Absolute) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(xBegin, GridUnitType.Absolute) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(maskSide, GridUnitType.Absolute) });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(App.ScreenWidth - xBegin, GridUnitType.Absolute) });

        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232"), }, 0, 0);
        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 0, 1);
        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 0, 2);

        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 1, 0);
        grid.Children.Add(mask, 1, 1);
        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 1, 2);

        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 2, 0);
        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 2, 1);
        grid.Children.Add(new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = Color.FromHex("#80323232") }, 2, 2);

        grid.HorizontalOptions = LayoutOptions.FillAndExpand;
        grid.VerticalOptions = LayoutOptions.FillAndExpand;

        AbsoluteLayout.SetLayoutBounds(grid, new Rectangle(0, 0, 1, 1));
        AbsoluteLayout.SetLayoutFlags(grid, AbsoluteLayoutFlags.All);

        al.Children.Add(grid);

        var b1 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b1, new Rectangle(xBegin - barShort, yBegin, barShort, barLong - barShort));
        AbsoluteLayout.SetLayoutFlags(b1, AbsoluteLayoutFlags.None);

        var b2 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b2, new Rectangle(xBegin - barShort, yBegin - barShort, barLong, barShort));
        AbsoluteLayout.SetLayoutFlags(b2, AbsoluteLayoutFlags.None);

        var b3 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b3, new Rectangle(xBegin + maskSide, yBegin, barShort, barLong - barShort));
        AbsoluteLayout.SetLayoutFlags(b3, AbsoluteLayoutFlags.None);

        var b4 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b4, new Rectangle(xBegin - barLong + maskSide + barShort, yBegin - barShort, barLong, barShort));
        AbsoluteLayout.SetLayoutFlags(b4, AbsoluteLayoutFlags.None);

        var b5 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b5, new Rectangle(xBegin - barShort, yBegin + maskSide + barShort - barLong, barShort, barLong - barShort));
        AbsoluteLayout.SetLayoutFlags(b5, AbsoluteLayoutFlags.None);

        var b6 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b6, new Rectangle(xBegin - barShort, yBegin + maskSide, barLong, barShort));
        AbsoluteLayout.SetLayoutFlags(b6, AbsoluteLayoutFlags.None);

        var b7 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b7, new Rectangle(xBegin + maskSide - barLong + barShort, yBegin + maskSide, barLong, barShort));
        AbsoluteLayout.SetLayoutFlags(b7, AbsoluteLayoutFlags.None);

        var b8 = new BoxView { HorizontalOptions = LayoutOptions.Fill, VerticalOptions = LayoutOptions.Fill, BackgroundColor = barColor };

        AbsoluteLayout.SetLayoutBounds(b8, new Rectangle(xBegin + maskSide, yBegin + maskSide - barLong + barShort, barShort, barLong - barShort));
        AbsoluteLayout.SetLayoutFlags(b8, AbsoluteLayoutFlags.None);

        al.Children.Add(b1);
        al.Children.Add(b2);
        al.Children.Add(b3);
        al.Children.Add(b4);
        al.Children.Add(b5);
        al.Children.Add(b6);
        al.Children.Add(b7);
        al.Children.Add(b8);

        _boxes = new List<BoxView> { b1, b2, b3, b4, b5, b6, b7, b8 };

        return al;
    }

    private void SetBoxesColor(Color c)
    {
        foreach (var box in _boxes)
        {
            box.Color = c;
        }
    }
}

As you can see, there is nothing complicated. But the problem is, how this page is rendered. On Xamarin Android Player devices (Nexus 4 KitKat HD and Nexus 5 Lollipop Full HD) everything looks as expected:

enter image description here

However when I run the same application (without pink background on the grid) on my Nexus 5x device (Marshmallow, Full HD), as you can see, pixels aren't precise, there is a little mismatch on the upper right green shape, a little offset on the left shapes and there is a one-pixel transparent gap on the grid row:

enter image description here

The fact that the code is rendered correctly on two devices makes me believe that my point arithmetic is correct. What could be the issue? And more importantly, what can I do to resolve it?

EDIT:

Maybe somehow the reason is that my device's pt/px ratio is different than emulator devices' ratio?

EDIT:

@Cheesebaron's answer was wrong. Here's what I tried to do, which didn't help at all: I created CustomBoxView, CustomBoxViewNative, CustomBoxViewRenderer and used CustomBoxView instead of BoxView. It didn' work.

CustomBoxView:

public class CustomBoxView : View
{
    public static readonly BindableProperty ColorProperty = BindableProperty.Create("Color", typeof (string),
        typeof (CustomBoxView), "#FF0000");

    public string Color
    {
        get { return (string) GetValue(ColorProperty); }
        set { SetValue(ColorProperty, value); }
    }
}

CustomBoxViewNative:

public class CustomBoxViewNative : View
{
    private Canvas _canvas;
    private Rect _rect;

    public CustomBoxViewNative(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
    {
    }

    public CustomBoxViewNative(Context context) : base(context)
    {
    }

    public CustomBoxViewNative(Context context, IAttributeSet attrs) : base(context, attrs)
    {
    }

    public CustomBoxViewNative(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
    {
    }

    public CustomBoxViewNative(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
    {
    }

    protected override void OnDraw(Canvas canvas)
    {
        var paintCircle = new Paint { Color = Color.White };
        paintCircle.StrokeWidth = Width*Resources.DisplayMetrics.Density;
        _rect = new Rect(0, 0, Width, Height);
        _canvas = canvas;
        _canvas.DrawRect(_rect, paintCircle);
    }
}

CustomBoxViewRenderer:

public class CustomBoxViewRenderer : ViewRenderer<CustomBoxView, CustomBoxViewNative>
{
    protected override void OnElementChanged(ElementChangedEventArgs<CustomBoxView> e)
    {
        base.OnElementChanged(e);

        if (e.OldElement != null || this.Element == null)
            return;

        var nativeControl = new CustomBoxViewNative(Forms.Context);

        SetNativeControl(nativeControl);
    }
}
like image 653
nicks Avatar asked Aug 18 '16 11:08

nicks


People also ask

Why do apps sometimes look different in the designer than they do in the emulator?

The reason why the designs look different is that the emulator's screen configuration is different to that which is used to render the design preview.

What is difference between emulator and real device?

The emulator/simulators are not able to simulate the battery issues. Real-world devices can easily perform the same. The emulator/simulators are not able to simulate the incoming interrupts for SMS as well as the incoming calls. Real-world devices can easily simulate incoming interrupts.

How emulators are different from other devices?

Emulators are used to mimic the real device, its software and its hardware. On the other hand the job of a simulator is to provide a software environment to run the app. It only impersonates the software part of the device.

What is a disadvantage of using an emulator versus a real device for testing mobile apps?

In the initial development stages, real handsets are harder to connect to IDE than emulators, which can slow down the debugging process.


1 Answers

As written in the comments you do not factor in the display density in your calculations. Hence, it might be a bit off on some devices when you render your lines.

I know Xamarin Forms Labs have a Display class where you can get the density of your screen. If you want to get it yourself through a dependency service you can do something like this:

public interface IDisplayInfo
{
    float Density { get; }
}

On Android:

public class DisplayInfo : IDisplayInfo
{
    public float Density => Application.Context.Resources.DisplayMetrics.Density;
}

On iOS:

public class DisplayInfo : IDisplayInfo
{
    public float Density => UIScreen.MainScreen.Scale;
}

Register your dependency service on each platform:

[assembly: Xamarin.Forms.Dependency (typeof (DisplayInfo))]

Then when you need to use it:

var display = DependencyService.Get<IDisplayInfo>();

var density = display.Density;

Then you just multiply your stroke widths with the density.

There is a small example here: https://stackoverflow.com/a/14405451/368379

like image 77
Cheesebaron Avatar answered Oct 17 '22 08:10

Cheesebaron