【问题标题】:Why is the same interface rendered differently on an emulator and a device?为什么相同的界面在模拟器和设备上呈现不同?
【发布时间】:2016-08-20 21:29:54
【问题描述】:

我正在为 ZXing 的 QR 阅读器编写自定义叠加层。这是工作示例:

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;
        }
    }
}

如您所见,没有什么复杂的。但问题是,这个页面是如何呈现的。在 Xamarin Android Player 设备(Nexus 4 KitKat HD 和 Nexus 5 Lollipop Full HD)上,一切都如预期的那样:

但是,当我在 Nexus 5x 设备(Marshmallow,全高清)上运行相同的应用程序(网格上没有粉红色背景)时,如您所见,像素不精确,右上角有点不匹配绿色的形状,左边的形状有一点偏移,网格行上有一个像素的透明间隙:

代码在两个设备上正确呈现的事实让我相信我的点算术是正确的。可能是什么问题?更重要的是,我能做些什么来解决它?

编辑:

也许某种原因是我设备的pt/px 比率与模拟器设备的比率不同?

编辑:

@Cheesebaron 的回答是错误的。这是我尝试做的,但根本没有帮助:我创建了CustomBoxViewCustomBoxViewNativeCustomBoxViewRenderer,并使用CustomBoxView 而不是BoxView。没用。

自定义框视图:

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);
    }
}

【问题讨论】:

  • 不要相信模拟器,多设备运行试试看。
  • 是亚像素渲染问题。您需要考虑笔画宽度的屏幕密度。
  • @Cheesebaron 你能更具体一点吗?也许提供一些资源
  • (我不了解 Xamarin)问题似乎与 rect、stroke 等的不同密度和大小计算有关。将浮点分数四舍五入造成一个像素间隙。要解决此问题,请尽可能使用 dp 值。对于某些特定情况,您可以改进计算,例如代替 x/y 使用 x+y-1/y 等来获得更高的值并防止丢失 1 个像素。

标签: android xamarin xamarin.forms


【解决方案1】:

如 cmets 中所述,您在计算中并未考虑显示密度。因此,在您渲染线条时,在某些设备上可能会有些偏差。

我知道 Xamarin Forms Labs 有一个 Display 类,您可以在其中获取屏幕的密度。如果你想通过依赖服务自己获取它,你可以这样做:

public interface IDisplayInfo
{
    float Density { get; }
}

在安卓上:

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

在 iOS 上:

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

在每个平台上注册你的依赖服务:

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

那么当你需要使用它时:

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

var density = display.Density;

然后你只需将笔画宽度乘以density

这里有一个小例子:https://stackoverflow.com/a/14405451/368379

【讨论】:

  • 您的代码返回的密度等于3。这是否意味着我应该乘以等于 196 的 maskSide 并使用结果值?这会弄乱我的尺码。也许您是说它应该是 3 的 倍数
  • 请问,您能详细说明一下吗?我不明白需要做什么。
  • 它与笔画有什么关系?我根本没有使用任何东西
  • 我稍后再看看,看看能不能给你一些建议:)
  • @NikaGamkrelidze,关于 “...它与笔画有什么关系?” 这意味着你的线条!!我对 Android 代码不够熟悉,无法向您展示,但是您的代码中设置绿线/框 像素宽度 的任何部分都必须乘以密度。例如.. GreenBox.width = myWidth * Density); 尝试并调整,因为你可以看到结果,我不能。您的怀疑是正确的,尽管 “...我的设备的 pt/px 比率与模拟器设备的比率不同?” 并且解决方案考虑了密度(它本身受显示分辨率的影响)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-26
  • 2013-07-20
  • 2020-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多