.NET MAUI中MVVM模式下图形元素动态行为实现问题
问题分析与解决方案
你的方案存在三个核心问题,修正后完全可以通过MVVM实现进度弧的动画控制:
问题1:类职责混淆
你让ProgressArcDrawable同时继承GraphicsView并实现IDrawable,这混淆了控件容器和绘制逻辑的职责。GraphicsView是用来展示IDrawable的UI控件,而IDrawable仅负责绘制逻辑,两者应该分离。
问题2:未触发UI重绘
你的BindableProperty没有设置propertyChanged回调,当ArcProgress或ArcColor值变化时,不会通知UI重新绘制,导致界面无更新。
问题3:资源绑定上下文缺失
放在ContentPage.Resources中的对象默认不会继承页面的BindingContext,所以{Binding ArcProgress}无法找到ViewModel中的属性,绑定直接失效;而硬编码的Stroke="20"是直接赋值,不受绑定上下文影响,因此能生效。
修正后的实现代码
步骤1:分离绘制逻辑与控件
创建独立的绘制类ArcDrawable,仅负责绘制逻辑:
public class ArcDrawable : IDrawable { public double ArcProgress { get; set; } public float Stroke { get; set; } public Color ArcColor { get; set; } public void Draw(ICanvas canvas, RectF dirtyRect) { var endAngle = 90 - (int)Math.Round(ArcProgress * 360, MidpointRounding.AwayFromZero); canvas.StrokeColor = ArcColor; canvas.StrokeSize = Stroke; canvas.DrawArc(Stroke / 2, Stroke / 2, dirtyRect.Width - Stroke, dirtyRect.Height - Stroke, 90, endAngle, false, false); } }
步骤2:创建自定义控件ArcProgressView
继承GraphicsView,定义BindableProperty并在属性变化时触发重绘:
public class ArcProgressView : GraphicsView { private readonly ArcDrawable _drawable; public ArcProgressView() { _drawable = new ArcDrawable(); Drawable = _drawable; } public double ArcProgress { get => (double)GetValue(ArcProgressProperty); set => SetValue(ArcProgressProperty, value); } public float Stroke { get => (float)GetValue(StrokeProperty); set => SetValue(StrokeProperty, value); } public Color ArcColor { get => (Color)GetValue(ArcColorProperty); set => SetValue(ArcColorProperty, value); } public static readonly BindableProperty ArcProgressProperty = BindableProperty.Create( nameof(ArcProgress), typeof(double), typeof(ArcProgressView), 0.0, propertyChanged: (bindable, oldVal, newVal) => { if (bindable is ArcProgressView view) { view._drawable.ArcProgress = (double)newVal; view.Invalidate(); // 触发UI重绘 } }); public static readonly BindableProperty StrokeProperty = BindableProperty.Create( nameof(Stroke), typeof(float), typeof(ArcProgressView), 0f, propertyChanged: (bindable, oldVal, newVal) => { if (bindable is ArcProgressView view) { view._drawable.Stroke = (float)newVal; view.Invalidate(); } }); public static readonly BindableProperty ArcColorProperty = BindableProperty.Create( nameof(ArcColor), typeof(Color), typeof(ArcProgressView), Colors.Black, propertyChanged: (bindable, oldVal, newVal) => { if (bindable is ArcProgressView view) { view._drawable.ArcColor = (Color)newVal; view.Invalidate(); } }); }
步骤3:修改XAML布局
直接使用自定义控件,无需放在资源中:
<Grid> <controls:ArcProgressView HeightRequest="350" WidthRequest="350" ArcProgress="{Binding ArcProgress}" Stroke="20" ArcColor="{Binding Scrum.ScrumTheme.AccentColor}" /> </Grid>
步骤4:ViewModel代码保持不变
[ObservableProperty] double arcProgress;
后续动画实现
在ViewModel中可以通过定时器或异步方法更新ArcProgress值,比如每秒递增:
private async Task UpdateProgressAsync() { while (ArcProgress < 1.0) { ArcProgress += 0.1; await Task.Delay(1000); } }
内容的提问来源于stack exchange,提问作者Lilly
相关产品推荐
相关产品推荐

