【发布时间】:2018-10-23 18:39:45
【问题描述】:
我发现在 iOS 上,当页面真正出现在屏幕上时调用 OnAppearing,而在 Android 上,它在创建时调用。
我正在使用此事件来懒惰地构造一个昂贵的构造视图,但显然 Android 行为会破坏这一点。
在 Android 上是否有某种方式可以知道屏幕何时出现在屏幕上?
【问题讨论】:
标签: xamarin xamarin.forms
我发现在 iOS 上,当页面真正出现在屏幕上时调用 OnAppearing,而在 Android 上,它在创建时调用。
我正在使用此事件来懒惰地构造一个昂贵的构造视图,但显然 Android 行为会破坏这一点。
在 Android 上是否有某种方式可以知道屏幕何时出现在屏幕上?
【问题讨论】:
标签: xamarin xamarin.forms
您可以使用该事件:
this.Appearing += YourPageAppearing;
否则,您应该使用包含生命周期方法的Application 类的方法:
protected override void OnStart()
{
Debug.WriteLine ("OnStart");
}
protected override void OnSleep()
{
Debug.WriteLine ("OnSleep");
}
protected override void OnResume()
{
Debug.WriteLine ("OnResume");
}
【讨论】:
在 Android 上,在向用户显示页面视图之前立即调用 Xamarin.Forms.Page.OnAppearing(而不是在“创建”(构造)页面时)。
如果您希望初始视图快速出现,通过省略昂贵的子视图,使用绑定使该视图的 IsVisible 最初为“false”。这将使其远离可视化树,从而避免构建它的大部分成本。将(不可见)视图放置在网格单元中,其尺寸是恒定的(在 DP 或“*”中 - “自动”以外的任何内容。)因此,当您使其可见时,该布局将为该视图“准备好”。
方法 1:
现在您只需要在视图模型中绑定一个将 IsVisible 更改为“true”的绑定。
最简单的 hack 是在 OnAppearing 中触发一个动作,该动作将在 250 毫秒后更改该变量。
方法 2:
干净的替代方法是创建一个自定义页面渲染器,并覆盖“draw”。
有draw,调用base.draw后,检查页面上的action属性。
如果不为 null,则调用该操作,然后将其清除(因此只发生一次)。
我通过从自定义页面基类继承来做到这一点:
我的每个页面的 XAML(将“ContentPage”更改为“exodus:ExBasePage”):
<exodus:ExBasePage
xmlns:exodus="clr-namespace:Exodus;assembly=Exodus"
x:Class="YourNamespace.YourPage">
...
</exodus:ExBasePage>
xaml.cs:
using Exodus;
// After creating page, change "ContentPage" to "ExBasePage".
public partial class YourPage : ExBasePage
{
...
我的自定义内容页面。注意:包括不需要的代码,与 iOS 安全区域和 Android 硬后退按钮相关:
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
namespace Exodus
{
public abstract partial class ExBasePage : ContentPage
{
public ExBasePage()
{
// Each sub-class calls InitializeComponent(); not needed here.
ExBasePage.SetupForLightStatusBar( this );
}
// Avoids overlapping iOS status bar at top, and sets a dark background color.
public static void SetupForLightStatusBar( ContentPage page )
{
page.On<Xamarin.Forms.PlatformConfiguration.iOS>().SetUseSafeArea( true );
// iOS NOTE: Each ContentPage must set its BackgroundColor to black or other dark color (when using LightContent for status bar).
//page.BackgroundColor = Color.Black;
page.BackgroundColor = Color.FromRgb( 0.3, 0.3, 0.3 );
}
// Per-platform ExBasePageRenderer uses these.
public System.Action NextDrawAction;
/// <summary>
/// Override to do something else (or to do nothing, i.e. suppress back button).
/// </summary>
public virtual void OnHardwareBackButton()
{
// Normal content page; do normal back button behavior.
global::Exodus.Services.NavigatePopAsync();
}
}
}
Android 项目中的渲染器:
using System;
using Android.Content;
using Android.Views;
using Android.Graphics;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Exodus;
using Exodus.Android;
[assembly: ExportRenderer( typeof( ExBasePage ), typeof( ExBasePageRenderer ) )]
namespace Exodus.Android
{
public class ExBasePageRenderer : PageRenderer
{
public ExBasePageRenderer( Context context ) : base( context )
{
}
protected override void OnElementChanged( ElementChangedEventArgs<Page> e )
{
base.OnElementChanged( e );
var page = Element as ExBasePage;
if (page != null)
page.firstDraw = true;
}
public override void Draw( Canvas canvas )
{
try
{
base.Draw( canvas );
var page = Element as ExBasePage;
if (page?.NextDrawAction != null)
{
page.NextDrawAction();
page.NextDrawAction = null;
}
}
catch (Exception ex)
{
// TBD: Got Disposed exception on Android Bitmap, after rotating phone (in simulator).
// TODO: Log exception.
Console.WriteLine( "ExBasePageRenderer.Draw exception: " + ex.ToString() );
}
}
}
}
在第一次绘制页面后执行一些操作:
public partial class YourPage : ExBasePage
{
protected override void OnAppearing()
{
// TODO: OnPlatform code - I don't have it handy.
// On iOS, we call immediately "DeferredOnAppearing();"
// On Android, we set this field, and it is done in custom renderer.
NextDrawAction = DeferredOnAppearing;
}
void DeferredOnAppearing()
{
// Whatever you want to happen after page is drawn first time:
// ((MyViewModel)BindingContext).ExpensiveViewVisible = true;
// Where MyViewModel contains:
// public bool ExpensiveViewVisible { get; set; }
// And your XAML contains:
// <ExpensiveView IsVisible={Binding ExpensiveViewVisible}" ... />
}
}
注意:我在 iOS 上执行此操作的方式不同,因为 iOS 上的 Xamarin Forms(不正确 - 不符合规范)在页面绘制后调用 OnAppearing。
所以我有 OnPlatform 逻辑。在 iOS 上,OnAppearing 立即调用 DeferredOnAppearing。在 Android 上,显示的行已完成。
希望 iOS 最终会被修复为调用 OnAppearing BEFORE,
为了两个平台之间的一致性。
如果是这样,我会为 iOS 添加一个类似的渲染器。
(当前的 iOS 实现意味着由于弹出导航堆栈,无法在第二次出现之前更新视图。 相反,它出现了过时的内容,那么你就有机会了 纠正它。这不好。)
【讨论】: