【发布时间】:2018-06-12 05:33:43
【问题描述】:
我想创建一个页面布局,在其中显示多个 TableViews 以及其他视图,例如每个视图之间的 Label。
在此过程中,我需要具备以下能力:
- 使
TableView不默认为垂直扩展布局选项(默认值似乎不可更改),以便它们可以垂直堆叠在StackLayout中。 - 隐藏相应的
UITableView部分页眉/页脚,以便它们之间没有多余的垂直间距。
我通过以下CustomTableView 控件实现了这一点:
public class CustomTableView : TableView
{
public bool NoHeader { get; set; }
public bool NoFooter { get; set; }
protected override SizeRequest OnSizeRequest(double widthConstraint, double heightConstraint)
{
if(!VerticalOptions.Expands)
{
// Call OnSizeRequest that is overwritten in custom renderer
var baseOnSizeRequest = GetVisualElementOnSizeRequest();
return baseOnSizeRequest(widthConstraint, heightConstraint);
}
return base.OnSizeRequest(widthConstraint, heightConstraint);
}
public Func<double, double, SizeRequest> GetVisualElementOnSizeRequest()
{
var handle = typeof(VisualElement).GetMethod(
"OnSizeRequest",
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new Type[] { typeof(double), typeof(double) },
null)?.MethodHandle;
var pointer = handle.Value.GetFunctionPointer();
return (Func<double, double, SizeRequest>)Activator.CreateInstance(
typeof(Func<double, double, SizeRequest>), this, pointer);
}
}
以及遵循 this SO answer 的建议的自定义 iOS 渲染器,用于隐藏 UITableView 页眉/页脚:
public class CustomTableViewRenderer : TableViewRenderer
{
public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
{
Control.LayoutIfNeeded();
var size = new Size(Control.ContentSize.Width, Control.ContentSize.Height);
return new SizeRequest(size);
}
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
{
base.OnElementChanged(e);
if(e.NewElement is CustomTableView view)
{
Control.ScrollEnabled = false;
SetSource();
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
var view = (CustomTableView)Element;
if(e.PropertyName == TableView.HasUnevenRowsProperty.PropertyName)
{
SetSource();
}
}
private void SetSource()
{
var view = (CustomTableView)Element;
if(view.NoFooter || view.NoHeader)
{
Control.Source = new CustomTableViewModelRenderer(view);
}
else
{
Control.Source = Element.HasUnevenRows ? new UnEvenTableViewModelRenderer(Element) :
new TableViewModelRenderer(Element);
}
}
public class CustomTableViewModelRenderer : UnEvenTableViewModelRenderer
{
private readonly CustomTableView _view;
public CustomTableViewModelRenderer(CustomTableView model)
: base(model)
{
_view = model;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
if(_view.HasUnevenRows)
{
return UITableView.AutomaticDimension;
}
return base.GetHeightForRow(tableView, indexPath);
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
if(_view.NoHeader)
{
return 0.00001f;
}
return base.GetHeightForHeader(tableView, section);
}
public override UIView GetViewForHeader(UITableView tableView, nint section)
{
if(_view.NoHeader)
{
return new UIView(new CGRect(0, 0, 0, 0));
}
return base.GetViewForHeader(tableView, section);
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
if(_view.NoFooter)
{
return 0.00001f;
}
return 10f;
}
public override UIView GetViewForFooter(UITableView tableView, nint section)
{
if(_view.NoFooter)
{
return new UIView(new CGRect(0, 0, 0, 0));
}
return base.GetViewForFooter(tableView, section);
}
}
}
然后我创建一个简单的 Xamarin.Forms ContentPage 来实现它:
public class TablePage : ContentPage
{
public TablePage()
{
Table1 = new PageTableView
{
BackgroundColor = Color.Cyan,
Root = new TableRoot
{
new TableSection("Table 1")
{
new LabelEntryTableCell("Label A"),
new LabelEntryTableCell("Label B")
}
}
};
var label1 = new Label
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
"Vivamus accumsan lacus orci. Nulla in enim erat.",
Margin = new Thickness(15, 5, 15, 0),
BackgroundColor = Color.Yellow
};
Table2 = new PageTableView
{
BackgroundColor = Color.Red,
Root = new TableRoot
{
new TableSection("Table 2")
{
new LabelEntryTableCell("Label C"),
new LabelEntryTableCell("Label D")
}
}
};
var label2 = new Label
{
Text = "Duis vulputate mattis elit. " +
"Donec vulputate lorem vitae elit posuere, quis consequat ligula imperdiet.",
Margin = new Thickness(15, 5, 15, 0),
BackgroundColor = Color.Green
};
StackLayout = new StackLayout
{
Children = { Table1, label1, Table2, label2 },
Spacing = 0
};
BackgroundColor = Color.Gray;
Title = "Custom Table View";
Content = new ScrollView
{
Content = StackLayout
};
}
public PageTableView Table1 { get; set; }
public PageTableView Table2 { get; set; }
public StackLayout StackLayout { get; set; }
}
public class PageTableView : CustomTableView
{
public PageTableView()
{
Intent = TableIntent.Settings;
HasUnevenRows = true;
RowHeight = -1;
VerticalOptions = LayoutOptions.Start;
NoFooter = true;
}
}
public class LabelEntryTableCell : ViewCell
{
public LabelEntryTableCell(string labelText)
{
var label = new Label { Text = labelText };
var entry = new Entry();
View = new StackLayout
{
Children = { label, entry },
BackgroundColor = Color.White,
Padding = 10
};
}
}
这一切似乎都按预期工作,但是,当TablePage 第一次显示CustomTableView 的大部分内容时,会被截断。然后我可以将设备旋转到横向,问题就会自行纠正,按照我的意图显示我的布局。然后将设备旋转回纵向仍然显示正确的布局,CustomTableView 的更多信息也不再在该方向上被截断。这种情况每次都会发生。在此处查看演示:
我也可以通过将其添加到 TablePage 来解决问题:
protected override void OnAppearing()
{
base.OnAppearing();
// This will also fix the problem, but you see a flash of the old layout
// when the page first displays.
StackLayout.RedrawLayout();
}
...
public class CustomStackLayout : StackLayout
{
public void RedrawLayout()
{
InvalidateLayout();
}
}
我该如何解决这个问题?每当我执行旋转/OnAppearing hack 来纠正它时,我的自定义控件/渲染器/页面都会按预期工作,但我不知道为什么。看来我需要在UITableView 上调用一些重绘方法。
示例项目
如果您想查看此隔离测试场景的完整源代码/项目,您可以在 GitHub here 上找到它。
【问题讨论】: