如何在Xamarin.Forms中获取ContentView动画入场前的高度?
我最近也折腾过类似的Xamarin.Forms动画布局问题,看到你遇到的这个场景特别有共鸣——做顶部滑入通知时,第一次入场拿不到ContentView的高度,导致动画直接跳转到最终位置,确实挺头疼的。
先帮你梳理下问题核心:你在ToastNotificationView.AnimateIn()里把控件设为可见后,因为组件还没完成首次布局测量,所以拿不到真实高度,没法把它先放到屏幕外,结果动画没生效,通知直接显出来了。而退场后再打开能正常工作,是因为组件已经完成过一次布局,高度被缓存下来了。
给你几个亲测有效的解决思路,不用硬编码高度也能完美适配可变高度的通知:
方案一:后台预渲染+强制测量
这个思路是让组件在用户看不到的地方先完成渲染和测量,拿到真实高度后再正式做动画。代码大概这样写:
public void AnimateIn() { // 第一次动画时才做预渲染 if (double.IsNaN(this.Height)) { // 创建一个完全不可见的临时容器,放到屏幕外 var tempContainer = new AbsoluteLayout { Opacity = 0, IsVisible = true }; // 把当前Toast加到临时容器,位置设到屏幕外 tempContainer.Children.Add(this, new Rectangle(-1000, -1000, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize)); // 把临时容器加到主页面的布局里,触发渲染 Application.Current.MainPage.Content.Children.Add(tempContainer); // 强制触发布局测量 this.Measure(double.PositiveInfinity, double.PositiveInfinity); // 等待布局完成(加个短延迟确保测量完成) Task.Delay(10).Wait(); var actualHeight = this.Height; // 移除临时容器,避免内存占用 Application.Current.MainPage.Content.Children.Remove(tempContainer); // 现在可以用真实高度执行动画了 StartSlideAnimation(actualHeight); } else { // 非首次动画,直接用缓存的高度 StartSlideAnimation(this.Height); } } private void StartSlideAnimation(double height) { // 先把通知移到屏幕顶部外 this.TranslateTo(0, -height, 0); // 设为可见,然后执行滑入动画 this.IsVisible = true; this.TranslateTo(0, 0, 300, Easing.CubicOut); }
这里要注意,加10ms的小延迟是为了确保Xamarin.Forms完成布局计算,避免拿到的高度还是NaN;用完一定要把临时容器移除,不然会积累无用控件。
方案二:监听SizeChanged事件触发动画
这个方法更省心,不用手动处理渲染,等Xamarin.Forms自己完成布局后拿到高度再启动动画:
private bool _isFirstAnimation = true; public void AnimateIn() { // 先把控件设为可见,但不马上启动动画 this.IsVisible = true; if (_isFirstAnimation) { // 第一次动画时监听SizeChanged事件 this.SizeChanged += OnToastSizeChanged; } else { // 非首次直接用已有高度启动动画 StartSlideAnimation(this.Height); } } private void OnToastSizeChanged(object sender, EventArgs e) { // 拿到有效高度后立即移除事件,避免重复触发 this.SizeChanged -= OnToastSizeChanged; _isFirstAnimation = false; // 启动滑入动画 StartSlideAnimation(this.Height); } private void StartSlideAnimation(double height) { this.TranslateTo(0, -height, 0); this.TranslateTo(0, 0, 300, Easing.CubicOut); }
这个方案的优势是完全依赖Xamarin.Forms的布局生命周期,不会有测量不准的问题,而且代码更简洁。唯一要注意的是用_isFirstAnimation标志避免重复绑定事件。
方案三:平台自定义渲染器(进阶)
如果上面的跨平台方案满足不了你的性能需求,也可以针对安卓和iOS写自定义渲染器,直接调用原生控件的测量API获取高度。比如安卓端可以调用View.Measure()方法,iOS端用UIView.SizeThatFits(),不过这个方案需要写平台特定代码,适合对动画流畅度要求极高的场景。
总的来说,方案二更推荐,代码简单还不容易出问题,完全能解决你首次入场拿不到高度的问题,而且适配可变高度的通知毫无压力。
内容的提问来源于stack exchange,提问作者Kristoffer Berge

