我如何知道视图何时完成渲染?

How can I know when a view is finished rendering?

我注意到当 OnElementPropertyChanged 在像 BoxView 这样的 VisualElement 上触发时,底层平台视图的属性当时没有更新。

我想知道 VisualElement 对应的平台视图什么时候完成渲染,比如:

this.someBoxView.ViewHasRendered += (sender, e) => { // Here I would know underlying UIView (in iOS) has finished rendering };

查看 Xamarin.Forms 中的一些代码,即 VisualElementRenderer.cs,似乎我可以在 OnPropertyChanged 完成后引发事件。类似于:

protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
        SetBackgroundColor(Element.BackgroundColor);
    else if (e.PropertyName == Layout.IsClippedToBoundsProperty.PropertyName)
        UpdateClipToBounds();
    else if (e.PropertyName == PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty.PropertyName)
        SetBlur((BlurEffectStyle)Element.GetValue(PlatformConfiguration.iOSSpecific.VisualElement.BlurEffectProperty));

    // Raise event
    VisualElement visualElement = sender as VisualElement;
    visualElement.ViewHasRendered();        
}

自然地,将事件添加到 VisualElement class 会更加复杂,因为它需要被子class编辑。但我想你可以看到我在追求什么。

在四处寻找时,我注意到 VisualElement 上的属性,如 IsInNativeLayout。但这似乎只在 Win/WP8 中实施。此外,VisualElementRenderer 上的 UpdateNativeWidget 也是如此,但是我想不出利用它们的正确方法。

有什么想法吗? 非常感谢。

TL;DR : 运行 走开,不要走这条路...

iOS 上,在屏幕上显示内容的所有内容都发生在 UIView(或子类)中,drawRect: 是进行绘图的方法。所以当 drawRect: 完成时,UIView 正在绘制。

注意:可能会出现动画,您可能会看到数百个完整的渲染周期已完成。您可能需要挂接到每个动画的完成处理程序以确定真正完成的时间 "rendering".

注意:绘图已完成 off-screen 并且根据 iDevice,屏幕刷新频率可能为 30FPS、60FPS,或者在 iPad Pro 的情况下它是可变的(30-60hz)。 ..

示例:

public class CustomRenderer : ButtonRenderer
{
    public override void Draw(CGRect rect)
    {
        base.Draw(rect);
        Console.WriteLine("A UIView is finished w/ drawRect: via msg/selector)");
    }
}

Android上,常见绘制内容的方法是通过View或子类,您可以获得表面,draw/bilt通过OpenGL 到屏幕等... 可能 不在 View 内,但对于您的 use-case,请考虑 Views..

Views 有 Draw 方法你可以覆盖,你也可以连接到 ViewTreeObserver 和监控 OnPreDrawOnDraw,等等等等... 有时您必须监视 View 的父级 (ViewGroup) 以确定绘图何时完成或何时完成。

此外,所有标准小部件都通过 xml 资源完全膨胀,并且经过优化,因此您永远不会看到 Draw/OnDraw 方法调用(注意:您应该总是(?)得到 OnPreDraw 如果你强制它,监听器调用)。

不同的 Views / Widgets 表现不同,并且无法查看所有挑战,您将确定 View 何时真正完成 "rendering"。 ..

示例:

public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer, 
    ViewTreeObserver.IOnDrawListener, ViewTreeObserver.IOnPreDrawListener
{
    public bool OnPreDraw() // IOnPreDrawListener
    {
        System.Console.WriteLine("A View is *about* to be Drawn");
        return true;
    }

    public void OnDraw() // IOnDrawListener
    {
        System.Console.WriteLine("A View is really *about* to be Drawn");
    }

    public override void Draw(Android.Graphics.Canvas canvas)
    {
        base.Draw(canvas);
        System.Console.WriteLine("A View was Drawn");
    }

    protected override void Dispose(bool disposing)
    {
        Control?.ViewTreeObserver.RemoveOnDrawListener(this);
        Control?.ViewTreeObserver.RemoveOnPreDrawListener(this);
        base.Dispose(disposing);
    }

    protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
    {           
        base.OnElementChanged(e);
        if (e.OldElement == null)
        {
            Control?.SetWillNotDraw(false); // force the OnPreDraw to be called :-(
            Control?.ViewTreeObserver.AddOnDrawListener(this); // API16+
            Control?.ViewTreeObserver.AddOnPreDrawListener(this); // API16+
            System.Console.WriteLine($"{Control?.ViewTreeObserver.IsAlive}");
        }
    }

}

杂项:

注意:布局优化、内容缓存、GPU 缓存、是否在 Widget/View 中启用了硬件加速等...可以防止调用 Draw 方法...

注意:动画、效果等...可能会导致这些方法在屏幕区域完全显示之前被调用 很多很多 次,并且为用户交互做好准备。

个人说明:由于疯狂的客户要求,我已经走上了这条路,在办公桌上敲了一段时间之后,回顾了 [=74= 那个领域的实际目标] 完成了,我 re-wrote 满足了要求,再也没有尝试过 ;-)

我将回答我自己的问题,希望该解决方案能帮助将来遇到此问题的人。

听从@SushiHangover 的建议,运行 不要放弃做这样的事情。 (尽管他的建议会奏效并且很合理)。尝试在平台完成渲染视图时通知 listen/be 是一个糟糕的主意。正如@SushiHangover 提到的那样,有太多事情可能出错。

那么是什么让我走上了这条路?

我需要一个类似于 iOS 中的 PIN 码 UI 来解锁您的设备,以及许多其他应用程序中的 PIN 码。当用户在键盘上按下数字时,我想更新相应的显示 "box"(键盘上方的框)。当用户输入最后一个数字时,我希望最后一个 "box" 被填充,在我的例子中,背景颜色发生变化,然后执行以继续,其中视图将转换到工作流中的下一个屏幕。

当我尝试在第四个框上设置 BackgroundColor 属性 然后转换屏幕时出现问题。但是,由于执行不会等待 属性 在呈现更改之前更改屏幕转换。这自然会导致糟糕的用户体验。

为了修复它,我想到了 "Oh! I simply need to be notified when the view has been rendered"。不聪明,正如我所提到的。

在查看了类似 UI 的一些 objective C 实现后,我意识到修复它非常简单。 UI 应稍等片刻,然后让 BackgroundColor 属性 呈现。

解决方案

private async Task HandleButtonTouched(object parameter)    
{
    if (this.EnteredEmployeeCode.Count > 4)
        return;         

    string digit = parameter as string;
    this.EnteredEmployeeCode.Add(digit);
    this.UpdateEmployeeCodeDigits();

    if (this.EnteredEmployeeCode.Count == 4) {
        // let the view render on the platform
        await Task.Delay (1);

        this.SignIn ();
    }
}

一个小的毫秒延迟就足以让视图完成渲染,而不必进入一个巨大的兔子洞并尝试监听它。

再次感谢@SushiHangover 的详细回复。你真棒我的朋友! :D