我注意到,当像BoxView这样的VisualElement上触发OnElementPropertyChanged时,底层平台视图的属性此时不会更新。
我想知道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类还有一些复杂性,因为它需要进行子类化。但我认为你可以看到我追求的目标。
虽然我已经注意到VisualElement上的属性,如IsInNativeLayout。但这似乎只在Win / WP8中实现。此外,VisualElementRenderer上的UpdateNativeWidget也是如此,但我无法找出利用它们的正确方法。
有什么想法吗? 非常感谢。
答案 0 :(得分:5)
TL;DR
:逃跑,不要走这条路......
在iOS
上显示屏幕内容的所有内容都发生在UIView
(或子类)中,而drawRect:
是执行绘图的方法。因此,当drawRect:完成时,UIView
正在绘制完成。
注意:动画可能正在发生,您可能会看到已完成数百个已完成的渲染周期。您可能需要挂钩每个动画的完成处理程序,以确定何时完成事情"渲染"。
注意:绘图是在屏幕外完成的,根据iDevice,屏幕刷新Hz可以是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
或子类,您可以通过OpenGL获取表面,绘制/转换到屏幕等。 .. 可能不在View
范围内,但对于您的用例,请考虑View
s ..
View
您可以覆盖Draw
个方法,您还可以挂钩ViewTreeObserver
并监控OnPreDraw
和OnDraw
等等等等...有时您必须监视View的父级(ViewGroup
)以确定何时完成绘制或何时完成绘制。
此外,所有标准窗口小部件都通过xml资源完全膨胀,并且已经过优化,因此您永远不会看到Draw / OnDraw方法调用(注意:如果强制它,您应该始终(?)获得OnPreDraw
侦听器调用)。
不同的View
s / Widget
行为不同,无法审核您确定View
何时真正完成的所有挑战"呈现" ...
示例:
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方法......
注意:动画,效果等...可以使这些方法在屏幕区域完全显示并准备好进行用户交互之前调用许多,很多,很多次。
个人注意事项:由于疯狂的客户要求,我曾经走过这条道路,在将我的头撞在桌面上一段时间之后,对UI领域的实际目标进行了审查,我重写了这个要求,再也没有尝试过这个; - )
答案 1 :(得分:3)
我将回答我自己的问题,希望这个解决方案可以帮助那些在未来努力解决这个问题的人。
关注@ SushiHangover的建议,不要再做这样的事了。 (虽然他的建议会起作用并且很健全)。在平台完成渲染视图时尝试收听/接收通知是一个可怕的想法。正如@SushiHangover提到的那样,有太多可能出错的事情。
那是什么让我走上了这条道路?
我需要一个类似于iOS中的PIN码用户界面来解锁你的设备,以及许多其他应用程序。当用户按下打击垫上的数字时,我想更新相应的显示器&#34;框&#34; (垫子上方的盒子)。当用户输入最后一个数字时我想要最后一个&#34;框&#34;要填写,在我的情况下,背景颜色更改,然后执行继续,其中视图将转换到工作流中的下一个屏幕。
当我尝试在第四个框上设置BackgroundColor属性然后转换屏幕时出现问题。但是,由于执行在呈现更改之前不等待属性更改屏幕转换。当然,这会带来糟糕的用户体验。
试图修复它,我想&#34;哦!我只需要在呈现视图时得到通知&#34;。正如我所提到的那样,并不聪明。
在查看类似UI的一些客观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