我注意到,当像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也是如此,但我无法弄清楚利用它们的正确方法。
有任何想法吗?非常感激。
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
s有你可以覆盖的Draw
方法,你也可以挂钩到ViewTreeObserver
并监视OnPreDraw
和OnDraw
等等...有时你必须监视View的父级(ViewGroup
)以确定何时将完成绘图或什么时候完成。
此外,所有标准窗口小部件都通过xml资源完全膨胀,并且已经过优化,因此您永远不会看到Draw / OnDraw方法调用(注意:如果强制它,您应该始终(?)获取OnPreDraw
侦听器调用)。
不同的View
s / Widget
s表现不同,没有办法回顾所有的挑战,你将确定什么时候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领域的实际目标进行了审查,并重新编写了要求,再也没试过;-)
我将回答我自己的问题,希望解决方案能够帮助那些在未来努力解决这个问题的人。
按照@ SushiHangover的建议,RUN不要再做这样的事了。 (虽然他的建议会起作用并且很健全)。在平台完成渲染视图时尝试收听/接收通知是一个可怕的想法。正如@SushiHangover提到的那样,有太多可能出错的事情。
是什么让我走上了这条道路?
我需要一个类似于iOS中的PIN码UI来解锁你的设备,以及许多其他应用程序。当用户按下打击垫上的数字时,我想更新相应的显示“框”(打击垫上方的框)。当用户输入最后一个数字时,我希望填写最后一个“框”,在我的情况下是背景颜色更改,然后执行继续,其中视图将转换到工作流中的下一个屏幕。
当我尝试在第四个框上设置BackgroundColor属性然后转换屏幕时出现问题。但是,由于执行不会等待属性在呈现更改之前更改屏幕转换。当然,这会带来糟糕的用户体验。
为了解决这个问题,我想“哦!我只需要在渲染视图时得到通知”。正如我所提到的那样,并不聪明。
在查看类似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
• 如何使用 useEffect 和 useState 重新渲染组件?
• 如何知道 iframe 视频 (Vimeo) 何时加载完成?
• “未找到查看 livewire”:无法在我开发的 Laravel 包中呈现 Livewire 全页组件
• 如何使 Shiny modal 中的数据在渲染前持续更新
• Kotlin compose 如何知道挂起函数何时完成?
• Unity 2019.4 内置管道,Bloom 后处理不起作用
• 如何使用 Express 提供部分动态的 HTML 页面?
• base64 图像在 headertemplate puppeteer 中尝试渲染图像时损坏
• 每次打开抽屉或屏幕方向发生变化时,如何阻止我的本机 expo 应用程序中的 WebView 渲染?