Phy*_*dha 11 c# xaml listview xamarin.android xamarin.forms
所以我的Xamarin.Forms应用程序(在Android上)使用了一个性能问题ListView.原因是,因为我在ListView中使用了一个非常复杂的自定义控件ItemTemplate.
为了提高性能,我在自定义控件中实现了许多缓存功能,并将ListView设置CachingStrategy为RecycleElement.
表现并没有好转.所以我试图找出原因是什么.
我终于发现了一些非常奇怪的bug并将其隔离在一个新的空应用中.代码如下:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:c="clr-namespace:ListViewBug.Controls"
xmlns:vm="clr-namespace:ListViewBug.ViewModels"
x:Class="ListViewBug.MainPage">
<ContentPage.BindingContext>
<vm:MainViewModel />
</ContentPage.BindingContext>
<ListView ItemsSource="{Binding Numbers}" CachingStrategy="RetainElement"
HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<c:TestControl Foo="{Binding}" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
Run Code Online (Sandbox Code Playgroud)
public class TestControl : Grid
{
static int id = 0;
int myid;
public static readonly BindableProperty FooProperty = BindableProperty.Create("Foo", typeof(string), typeof(TestControl), "", BindingMode.OneWay, null, (bindable, oldValue, newValue) =>
{
int sourceId = ((TestControl)bindable).myid;
Debug.WriteLine(String.Format("Refreshed Binding on TestControl with ID {0}. Old value: '{1}', New value: '{2}'", sourceId, oldValue, newValue));
});
public string Foo
{
get { return (string)GetValue(FooProperty); }
set { SetValue(FooProperty, value); }
}
public TestControl()
{
this.myid = ++id;
Label label = new Label
{
Margin = new Thickness(0, 15),
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
Text = this.myid.ToString()
};
this.Children?.Add(label);
}
}
Run Code Online (Sandbox Code Playgroud)
public class MainViewModel
{
public List<string> Numbers { get; set; } = new List<string>()
{
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
"fifteen",
"sixteen",
"seventeen",
"eighteen",
"nineteen",
"twenty"
};
}
Run Code Online (Sandbox Code Playgroud)
注意CachingStrategy就是RetainElement.每个都TestControl获得一个唯一的升序ID,显示在UI中.让我们运行应用程序!
[0:] Refreshed Binding on TestControl with ID 1. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 1. Old value: '', New value: 'one'
[0:] Refreshed Binding on TestControl with ID 2. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 2. Old value: '', New value: 'two'
[0:] Refreshed Binding on TestControl with ID 3. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 3. Old value: '', New value: 'three'
[0:] Refreshed Binding on TestControl with ID 4. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 4. Old value: '', New value: 'four'
[0:] Refreshed Binding on TestControl with ID 5. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 5. Old value: '', New value: 'five'
[0:] Refreshed Binding on TestControl with ID 6. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 6. Old value: '', New value: 'six'
[0:] Refreshed Binding on TestControl with ID 7. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 7. Old value: '', New value: 'seven'
[0:] Refreshed Binding on TestControl with ID 8. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 8. Old value: '', New value: 'eight'
[0:] Refreshed Binding on TestControl with ID 9. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 9. Old value: '', New value: 'nine'
[0:] Refreshed Binding on TestControl with ID 10. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 10. Old value: '', New value: 'ten'
[0:] Refreshed Binding on TestControl with ID 11. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 11. Old value: '', New value: 'eleven'
[0:] Refreshed Binding on TestControl with ID 12. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 12. Old value: '', New value: 'twelve'
Run Code Online (Sandbox Code Playgroud)
好吧,每个Binding由于某种原因被解雇两次.这不会发生在我的实际应用中,因此我并不在乎.我还比较了oldValue和newValue,如果它们是相同的则不执行任何操作,因此这种行为不会影响性能.
有趣的事情发生在我们设置CachingStrategy为RecycleElement:
[0:] Refreshed Binding on TestControl with ID 1. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 1. Old value: '', New value: 'one'
[0:] Refreshed Binding on TestControl with ID 2. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 2. Old value: '', New value: 'one'
[0:] Refreshed Binding on TestControl with ID 3. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 3. Old value: '', New value: 'two'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'one', New value: 'two'
[0:] Refreshed Binding on TestControl with ID 4. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 4. Old value: '', New value: 'three'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'two', New value: 'three'
[0:] Refreshed Binding on TestControl with ID 5. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 5. Old value: '', New value: 'four'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'three', New value: 'four'
[0:] Refreshed Binding on TestControl with ID 6. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 6. Old value: '', New value: 'five'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'four', New value: 'five'
[0:] Refreshed Binding on TestControl with ID 7. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 7. Old value: '', New value: 'six'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'five', New value: 'six'
[0:] Refreshed Binding on TestControl with ID 8. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 8. Old value: '', New value: 'seven'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'six', New value: 'seven'
[0:] Refreshed Binding on TestControl with ID 9. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 9. Old value: '', New value: 'eight'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'seven', New value: 'eight'
[0:] Refreshed Binding on TestControl with ID 10. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 10. Old value: '', New value: 'nine'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'eight', New value: 'nine'
[0:] Refreshed Binding on TestControl with ID 11. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 11. Old value: '', New value: 'ten'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'nine', New value: 'ten'
[0:] Refreshed Binding on TestControl with ID 12. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 12. Old value: '', New value: 'eleven'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'ten', New value: 'eleven'
[0:] Refreshed Binding on TestControl with ID 13. Old value: '', New value: ''
[0:] Refreshed Binding on TestControl with ID 13. Old value: '', New value: 'twelve'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'eleven', New value: 'twelve'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'twelve', New value: 'one'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'one', New value: 'two'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'two', New value: 'three'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'three', New value: 'four'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'four', New value: 'five'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'five', New value: 'six'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'six', New value: 'seven'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'seven', New value: 'eight'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'eight', New value: 'nine'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'nine', New value: 'ten'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'ten', New value: 'eleven'
[0:] Refreshed Binding on TestControl with ID 1. Old value: 'eleven', New value: 'twelve'
Run Code Online (Sandbox Code Playgroud)
哎呀.单元格1是不可见的,但它的Binding更新了很多.我甚至没有触摸屏幕一次,因此不涉及滚动.
当我点击屏幕并向下滚动大约一个或两个像素时,ID 1的绑定会再次刷新15次.
请参阅此视频,向我滚动ListView:https:
//www.youtube.com/watch?v = EuWTGclz7uc
这是我真实应用程序中的绝对性能杀手,这TestControl是一个非常复杂的控件.
有趣的是,在我的真实应用程序中,它是ID 2而不是ID 1,这是错误的.我假设它总是第二个单元格,所以如果ID为2,我最终会立即返回.这使得ListView性能更加流畅.
现在我已经能够用2以外的ID重现这个问题,我害怕我自己的解决方案.
因此我的问题是:这个看不见的单元是什么,为什么它会得到如此多的绑定更新以及如何绕过性能问题?
我测试了Xamarin.Forms版本2.3.4.247,2.3.4.270和2.4.0.269-pre2 on
我没有在iOS设备上测试.
设置CachingStrategy为RecycleElement会弄乱您的列表视图,因为您使用的值TextBock不是从 BindingContext 中检索的。( int myid;)。
我们来看看Xamarin文档RecycleElement
然而,它通常是首选,并且应该在以下情况下使用:
- 当每个细胞具有少量到中等数量的结合时。
- 当每个单元格的 BindingContext 定义所有单元格数据时。
- 当每个单元格很大程度上相似时,单元格模板不变。
在虚拟化期间,单元将更新其绑定上下文,因此如果应用程序使用此模式,则必须确保正确处理绑定上下文更新。有关单元格的所有数据都必须来自绑定上下文,否则可能会出现一致性错误。
RecycleElement当每个单元格的 BindingContext 定义了所有单元格数据时,您应该考虑使用模式。您int myid是单元格数据,但不是由绑定上下文定义的。
为什么?
我可以猜测,在RecycleElement滚动模式下:控件没有被更改,仅对其绑定进行更改。我认为这样做是为了减少渲染控件的时间。(也可以减少大量项目的内存使用)
myId 因此带有1 的文本块可以作为“Two”值的容器。(这就是虚拟化的意义。)
答案:改变你的myId逻辑,从中检索它就BindingContext可以了。
| 归档时间: |
|
| 查看次数: |
497 次 |
| 最近记录: |