MAUI迁移:自定义TableView FooterText实现及Renderer相关疑问
迁移Xamarin Forms自定义TableView至MAUI的问题
我们正在将Xamarin Forms(XF)应用迁移至MAUI,由于XF与MAUI原生均不支持为TableSection或Table添加Footer,因此需要迁移自定义TableView控件及渲染器。此前认为MAUI已移除Renderer,但发现其仍存在于Handlers.Compatibility命名空间中。
现有XF自定义Renderer代码如下:
public class BreatheTableViewRenderer : TableViewRenderer { /// <summary> /// Raises the <see cref="E:ElementChanged" /> event. /// </summary> /// <param name="e">The <see cref="ElementChangedEventArgs{TableView}"/> instance containing the event data.</param> protected override void OnElementChanged(ElementChangedEventArgs<TableView> e) { base.OnElementChanged(e); if (this.Control == null) { return; } var tableView = this.Control as UITableView; var formsTableView = this.Element as TableView; tableView.WeakDelegate = new BreatheTableViewModelRenderer(formsTableView); } private class BreatheTableViewModelRenderer : TableViewModelRenderer { private BreatheTableView view; public BreatheTableViewModelRenderer(TableView model) : base(model) { this.view = model as BreatheTableView; } protected BreatheTableView BreatheTableView { get => this.view; private set => this.view = value; } public override UIView GetViewForFooter(UITableView tableView, nint section) { // Do we have a TableView Footer and if so is this is the last section if (this.BreatheTableView.FooterText != null && section == tableView.NumberOfSections() - 1) { // ... if it is use that last section to display the footer return this.GetViewForTableFooter(this.BreatheTableView.FooterText); } TableSection tblSection = this.View.Root[(int)section]; string sectionFooterText = BreatheTableView.GetSectionFooterText(tblSection); if (string.IsNullOrEmpty(sectionFooterText) == false) { return this.GetViewForTableFooter(sectionFooterText); } return null; } private UIView GetViewForTableFooter(string text) { var footerView = new UITextView() { Text = text, Font = UIFont.PreferredFootnote, TextContainerInset = new UIEdgeInsets(5, 10, 5, 10), TextColor = UIColor.LightGray, BackgroundColor = UIColor.Clear, Editable = false, }; if (this.BreatheTableView.FooterAlignment == TextAlignment.Start) { footerView.TextAlignment = UITextAlignment.Left; } else if (this.BreatheTableView.FooterAlignment == TextAlignment.Center) { footerView.TextAlignment = UITextAlignment.Center; } else if (this.BreatheTableView.FooterAlignment == TextAlignment.End) { footerView.TextAlignment = UITextAlignment.Right; } return footerView; } } }
针对你提出的两个疑问,解答如下:
1. 如何自定义实现GetViewForFooter?
在MAUI中有两种可行方案,分别适配迁移过渡和原生扩展需求:
方案一:使用兼容性Renderer(复用XF逻辑)
MAUI保留了XF风格的兼容性Renderer,可直接基于原有代码改造,核心逻辑无需大幅修改:
- 引用兼容性命名空间
Microsoft.Maui.Controls.Handlers.Compatibility及iOS专属命名空间Microsoft.Maui.Controls.Handlers.Compatibility.iOS - 自定义Renderer继承
TableViewRenderer,在OnElementChanged中替换原生UITableView的WeakDelegate - 复用原有
BreatheTableViewModelRenderer的逻辑,仅调整命名空间引用
改造后核心代码:
using Microsoft.Maui.Controls.Handlers.Compatibility; using Microsoft.Maui.Controls.Handlers.Compatibility.iOS; using UIKit; public class BreatheTableViewRenderer : TableViewRenderer { protected override void OnElementChanged(ElementChangedEventArgs<TableView> e) { base.OnElementChanged(e); if (Control == null) return; var tableView = Control as UITableView; var formsTableView = Element as BreatheTableView; tableView.WeakDelegate = new BreatheTableViewModelRenderer(formsTableView); } private class BreatheTableViewModelRenderer : TableViewModelRenderer { private readonly BreatheTableView _view; public BreatheTableViewModelRenderer(TableView model) : base(model) { _view = model as BreatheTableView; } protected BreatheTableView BreatheTableView => _view; public override UIView GetViewForFooter(UITableView tableView, nint section) { // 完全复用原有Footer逻辑 if (BreatheTableView.FooterText != null && section == tableView.NumberOfSections() - 1) { return GetViewForTableFooter(BreatheTableView.FooterText); } TableSection tblSection = View.Root[(int)section]; string sectionFooterText = BreatheTableView.GetSectionFooterText(tblSection); if (!string.IsNullOrEmpty(sectionFooterText)) { return GetViewForTableFooter(sectionFooterText); } return null; } private UIView GetViewForTableFooter(string text) { var footerView = new UITextView() { Text = text, Font = UIFont.PreferredFootnote, TextContainerInset = new UIEdgeInsets(5, 10, 5, 10), TextColor = UIColor.LightGray, BackgroundColor = UIColor.Clear, Editable = false, }; switch (BreatheTableView.FooterAlignment) { case TextAlignment.Start: footerView.TextAlignment = UITextAlignment.Left; break; case TextAlignment.Center: footerView.TextAlignment = UITextAlignment.Center; break; case TextAlignment.End: footerView.TextAlignment = UITextAlignment.Right; break; } return footerView; } } }
在MauiProgram.cs中注册兼容性Renderer:
builder.ConfigureMauiHandlers(handlers => { #if IOS handlers.AddCompatibilityRenderer(typeof(BreatheTableView), typeof(BreatheTableViewRenderer)); #endif });
方案二:使用MAUI原生Handler(推荐)
MAUI推荐使用Handler替代传统Renderer,更轻量灵活:
- 自定义Handler继承
TableViewHandler - 在
ConnectHandler方法中为原生UITableView设置自定义Delegate,实现GetViewForFooter逻辑
示例代码:
using Microsoft.Maui.Handlers; using UIKit; public class BreatheTableViewHandler : TableViewHandler { protected override void ConnectHandler(UITableView platformView) { base.ConnectHandler(platformView); platformView.WeakDelegate = new BreatheTableViewDelegate(this); } private class BreatheTableViewDelegate : UITableViewDelegate { private readonly BreatheTableViewHandler _handler; public BreatheTableViewDelegate(BreatheTableViewHandler handler) { _handler = handler; } public override UIView GetViewForFooter(UITableView tableView, nint section) { var breatheTableView = _handler.VirtualView as BreatheTableView; if (breatheTableView == null) return null; if (breatheTableView.FooterText != null && section == tableView.NumberOfSections() - 1) { return GetViewForTableFooter(breatheTableView.FooterText, breatheTableView.FooterAlignment); } TableSection tblSection = breatheTableView.Root[(int)section]; string sectionFooterText = BreatheTableView.GetSectionFooterText(tblSection); if (!string.IsNullOrEmpty(sectionFooterText)) { return GetViewForTableFooter(sectionFooterText, breatheTableView.FooterAlignment); } return null; } private UIView GetViewForTableFooter(string text, TextAlignment alignment) { var footerView = new UITextView() { Text = text, Font = UIFont.PreferredFootnote, TextContainerInset = new UIEdgeInsets(5, 10, 5, 10), TextColor = UIColor.LightGray, BackgroundColor = UIColor.Clear, Editable = false, }; switch (alignment) { case TextAlignment.Start: footerView.TextAlignment = UITextAlignment.Left; break; case TextAlignment.Center: footerView.TextAlignment = UITextAlignment.Center; break; case TextAlignment.End: footerView.TextAlignment = UITextAlignment.Right; break; } return footerView; } } }
在MauiProgram.cs中注册Handler:
builder.ConfigureMauiHandlers(handlers => { #if IOS handlers.AddHandler(typeof(BreatheTableView), typeof(BreatheTableViewHandler)); #endif });
2. MAUI中的Renderer是否已被移除?
MAUI并未完全移除Renderer,而是提供了两种扩展机制:
- 兼容性Renderer:保留在
Microsoft.Maui.Controls.Handlers.Compatibility命名空间下,专门用于兼容XF现有Renderer代码,降低迁移成本。 - 原生Handler:MAUI主推的新扩展方式,相比传统Renderer更轻量化、模块化,是MAUI原生扩展的标准方案。
内容的提问来源于stack exchange,提问作者Pat Long - Munkii Yebee
相关产品推荐
相关产品推荐

