【问题标题】:How do I overlay two Xamarin Forms layouts such that both can receive touch inputs?如何覆盖两个 Xamarin Forms 布局,以便两者都可以接收触摸输入?
【发布时间】:2017-08-21 20:06:06
【问题描述】:

(我最初在 Xamarin 论坛上发布了这个,但后来决定我可能会在这里得到更快的答案?) TL;DR:有些布局会计算在透明背景上的点击次数,而另一些则不会。在容器上设置 InputTransparent 会为其所有子级设置它,我觉得子级应该能够覆盖父级。我需要创建覆盖另一个元素的元素,并通过透明区域传递水龙头,但仍然有可点击的按钮。当我用网格尝试这个时,它不起作用。我不想回到 AbsoluteLayouts。我现在主要在 iOS 上工作,我不太确定这是否是 Android 中的问题。 Windows Phone/UWP 不在桌面上。

加长版:

我正在重写一些在较旧的 Xamarin Forms(我认为是 1.3)中运行良好的布局。我们最近升级到 2.1,它对布局代码的错误决策造成了严重破坏。我的任务是更新布局以表现自己。虽然我知道 2.2 已经发布,但我只是尝试升级,我输入的所有内容在该版本中似乎也是正确的,所以这不是 2.1 与 2.2 的问题,或者至少如果进行了一些改进,它们没有帮助我。

它是一个地图应用程序,所以所有布局的核心是一个昂贵的、喜怒无常的 OpenGL 元素。这个元素非常不喜欢被重新设置,所以我采用了一种类似于这个想象中的 XAML 的布局:

<ContentPage>
    <CustomLayout>
        <OurHeaderControl /> 
        <TheMapControl /> 
        <OurFooterControl /> 
        <MapOverlay /> 
    </CustomLayout>
</ContentPage

“MapOverlay”的目的是通过在页眉/页脚区域和/或地图顶部添加 Xamarin 元素来实现我们的工作流程。例如,一种布局在页脚上方的底部添加了一个方向列表,因此它显示地图的空间较小。自定义布局理解这一点,并在叠加层之后布置地图,以便它可以要求正确的地图边界。

在此布局中,我无法点击 MapOverlay 结束的任何内容。我可以让它 InputTransparent 并点击这些东西,但是它的所有孩子也不能点击。这在旧布局中并非如此。

这是我看到的旧布局和新布局之间的唯一区别:

旧的布局是 AbsoluteLayouts 的复杂混乱。看起来是这样的,不是我写的:

<ContentPage>
    <AbsoluteLayout> // "main layout"
        <AbsoluteLayout> // "map layout"
            <Map /> // An AbsoluteLayout containing the OpenGL view.
        </AbsoluteLayout>
        <AbsoluteLayout> // "child layout"
            <SubPage /> // An AbsoluteLayout
        </AbsoluteLayout>
    </AbsoluteLayout>
</ContentPage>

主布局包含 AbsoluteLayouts 来约束子视图。一个子视图本身就是一个 AbsoluteLayout,其中包含 Map 和一些与之关联的其他元素。另一个孩子是叠加层,它始终是一个包含与该叠加层相关的元素的 AbsoluteLayout。这些布局都在循环中相互引用,并随着布局事件的变化而相互更新。这是一场令人着迷的乒乓球比赛,最终安定下来。通常。有时事情就这样消失了。很明显,我重写它是有原因的。

但我可以在每一层点击我需要点击的内容,但我不明白。

所以,让我们来谈谈我需要做什么,也许会弄清楚它是否是一个错误,为什么它不能工作,或者它是否与其他布局一起工作是巧合。这是一个非 XAML 页面布局,它展示了我的项目起源于您无法在共享库中使用 XAML 的时代:

我需要能够点按此 UI 中的两个按钮并让它们做出响应。

public class MyPage : ContentPage {
    public MyPage() {

        var mainLayout = new AbsoluteLayout();

        // Two buttons will be overlaid.
        var overlaidButton = new Button() {
            Text = "Overlaid",
            Command = new Command((o) => DisplayAlert("Upper!", "Overlaid button.", "Ah."))
        };
        mainLayout.Children.Add(overlaidButton, new Rectangle(0.25, 0.25, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize), AbsoluteLayoutFlags.PositionProportional);

        // The top overlay layout will be a grid.
        var overlay = new Grid() {
            RowDefinitions = { new RowDefinition() { Height = new GridLength(1.0, GridUnitType.Star) } },
            ColumnDefinitions = {
                new ColumnDefinition() { Width = new GridLength(1.0, GridUnitType.Star) },
                new ColumnDefinition() { Width = new GridLength(1.0, GridUnitType.Star) },
            },
            BackgroundColor = Color.Transparent
        };
        var overlayingButton = new Button() {
            Text = "Overlaying",
            Command = new Command((o) => DisplayAlert("Upper!", "Overlaying button.", "Ah."))
        };
        overlay.Children.Add(overlayingButton, 0, 1);
        mainLayout.Children.Add(overlay, new Rectangle(0, 0, 1.0, 1.0), AbsoluteLayoutFlags.All);

        // This pair of property sets makes the overlaid button clickable, but not the overlaying!
//      overlay.InputTransparent = true;
//      upperOverlayButton.InputTransparent = false;


        Content = mainLayout;
    }
}

即使我将 Grid 更改为 AbsoluteLayout,这也只能让我点击“叠加”按钮。

我被难住了。我花了 2 周时间来调试初始布局并提出新的解决方案。我真的不想拆散我们所有的布局并将所有内容放在一个大的 AbsoluteLayout 或自定义布局中。在WPF中,有两种透明:“背景透明”表示背景仍然可以命中测试,“空背景”表示背景不能命中测试。有没有办法像这样在 Xamarin 中覆盖布局?

或者,更恰当地说,为什么我们的旧布局中众多 AbsoluteLayouts 的复杂嵌套可以像我需要的那样工作,但这个更简单的布局却不是?

更新

以下是我记得的一些附加信息:

【问题讨论】:

    标签: xamarin.forms


    【解决方案1】:

    一般而言,与其他两个平台相比,在 Grid 中处理 InputTransparent 的方式似乎与 iOS 的行为相比。目前我并不确定是否会将当前行为量化为错误,但我知道遇到平台行为差异会令人沮丧。

    不过,如果我理解正确的话,可以针对您的情况进行某种修复。似乎之前曾提交过类似的报告,并且通过this SO link 提到了有关 iOS 的行为。问题是在非 Forms iOS 应用的范围内提出的,但逻辑可以在这里应用。

    通过使用自定义渲染器(我们以CustomGrid为例),您可以具体实现Grid的iOS实现,以按照上述链接查找底层视图的方式:

    CustomGrid.cs (PCL):

    public class CustomGrid : Grid
    {
        public CustomGrid() { }
    }
    

    CustomGrid.cs (iOS):

    [assembly: ExportRenderer(typeof(CustomGrid), typeof(CustomGridRenderer))]
    public class CustomGridRenderer : ViewRenderer
    {
        public override UIKit.UIView HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent uievent)
        {
            UIView hitView = base.HitTest(point, uievent);
            if (hitView == this)
            {
                return null;
            }
            return hitView;
        }
    }
    

    通过这种方式,您不应为 iOS 显式设置 InputTransparent,并且对 Grid 本身的任何点击都会发送到下面的任何内容。由于 Android 与 InputTransparent 一起工作,但在这种特殊情况下,您可以将其包装在 Device.OnPlatform 语句中,如果您不想这样做,则跳过实现 Android 自定义渲染器:

    Device.OnPlatform(Android: () => 
    {
        overlay.InputTransparent = true 
    });
    

    使用修改为使用CustomGrid 和iOS 渲染器的上述代码,我可以点击这两个按钮。

    【讨论】:

    • 啊,看起来很有希望。我要试一试!
    • 有效!太感谢了!我确实认为我会将其归类为“错误”,如果只是因为它是一个平台差异,我无法使用简单的属性设置器来解决。但现在我可以着手解决更大的问题,比如“我们怎么搞砸了这个 StackLayout,为什么里面还有 5 个其他嵌套布局?”
    猜你喜欢
    • 1970-01-01
    • 2012-09-06
    • 2015-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多