我如何知道视图何时完成渲染?
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,请考虑 View
s..
View
s 有 Draw
方法你可以覆盖,你也可以连接到 ViewTreeObserver
和监控 OnPreDraw
和 OnDraw
,等等等等... 有时您必须监视 View 的父级 (ViewGroup
) 以确定绘图何时完成或何时完成。
此外,所有标准小部件都通过 xml 资源完全膨胀,并且经过优化,因此您永远不会看到 Draw/OnDraw 方法调用(注意:您应该总是(?)得到 OnPreDraw
如果你强制它,监听器调用)。
不同的 View
s / Widget
s 表现不同,并且无法查看所有挑战,您将确定 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
我注意到当 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,请考虑 View
s..
View
s 有 Draw
方法你可以覆盖,你也可以连接到 ViewTreeObserver
和监控 OnPreDraw
和 OnDraw
,等等等等... 有时您必须监视 View 的父级 (ViewGroup
) 以确定绘图何时完成或何时完成。
此外,所有标准小部件都通过 xml 资源完全膨胀,并且经过优化,因此您永远不会看到 Draw/OnDraw 方法调用(注意:您应该总是(?)得到 OnPreDraw
如果你强制它,监听器调用)。
不同的 View
s / Widget
s 表现不同,并且无法查看所有挑战,您将确定 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