Xamarin.Forms 自动完成跨平台
Xamarin.Forms Autocomplete CrossPlatform
我是 Xamarin.Forms 平台的新手。我希望你能帮助我前进。我想在 xamarin.forms 中有一个类似自动完成的控件,如下所示
(来源:codetheory.in)
能否请您指导如何在 Xamarin.Forms 中实现?我想用入口控件实现它
TIA
您没有包含您想要的内容,只是某种自动完成。
我将针对项目列表指出一般的手动方式:
- 使用 TextBox 允许用户输入文本。
- 使用列表将所有对象及其可搜索的 属性(例如对象名称)收集在一起。
- 当用户在 TextBox 中键入内容时,应用程序应在列表中搜索在 TextBox 中输入的字符串。
- 建议应根据键入的字符串值显示在 TextBox 下的 ListView 中。
- 用户单击 ListView 项目,这是一个建议,然后通过将对象名称从单击的项目中提取到文本框来自动完成。
无需上述冗长的粗略过程即可完成自动完成的一般方法是使用 Android AutoCompleteTextView。
您仍然可以使用基本逻辑在 Xamarin Forms 中完成。
查找 here Android 的 AutoCompleteTextView。
在 Xamarin Forms 中查找 here, here and here 以获取有关自动完成的帮助。
请阅读这些文章并尝试使用自定义渲染器在 Xamarin.Forms 上实施解决方案。
我在我的项目中实现了一个 AutocompleteView。可以参考一下。
public class AutoCompleteView : ContentView
{
public static readonly BindableProperty SuggestionsProperty = BindableProperty.Create(nameof(Suggestions), typeof(IEnumerable), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnSuggestionsChanged);
public static readonly BindableProperty SearchTextProperty = BindableProperty.Create(nameof(SearchText), typeof(string), typeof(AutoCompleteView), null, BindingMode.TwoWay, null, OnSearchTextChanged);
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnPlaceholderChanged);
public static readonly BindableProperty MaximumVisibleSuggestionItemsProperty = BindableProperty.Create(nameof(MaximumVisibleSuggestionItems), typeof(int), typeof(AutoCompleteView), 4);
public static readonly BindableProperty SuggestionItemTemplateProperty = BindableProperty.Create(nameof(SuggestionItemTemplate), typeof(DataTemplate), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnSuggestionItemTemplateChanged);
public static readonly BindableProperty DisplayPropertyNameProperty = BindableProperty.Create(nameof(DisplayPropertyName), typeof(string), typeof(AutoCompleteView));
public IEnumerable Suggestions
{
get { return (IEnumerable)GetValue(SuggestionsProperty); }
set { SetValue(SuggestionsProperty, value); }
}
public string SearchText
{
get { return (string)GetValue(SearchTextProperty); }
set { SetValue(SearchTextProperty, value); }
}
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public int MaximumVisibleSuggestionItems
{
get { return (int)GetValue(MaximumVisibleSuggestionItemsProperty); }
set { SetValue(MaximumVisibleSuggestionItemsProperty, value); }
}
public DataTemplate SuggestionItemTemplate
{
get { return (DataTemplate)GetValue(SuggestionItemTemplateProperty); }
set { SetValue(SuggestionItemTemplateProperty, value); }
}
public string DisplayPropertyName
{
get { return (string)GetValue(DisplayPropertyNameProperty); }
set { SetValue(DisplayPropertyNameProperty, value); }
}
public ItemsStack SuggestionsListView { get; private set; }
public Entry SearchEntry { get; private set; }
public IEnumerable OriginSuggestions { get; private set; }
public NestedScrollView SuggestionWrapper { get; private set; }
public Grid Container { get; private set; }
public bool IsSelected { get; private set; }
public int TotalNumberOfTypings { get; private set; }
private static void OnSuggestionsChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
var suggestions = (IEnumerable)newValue;
autoCompleteView.OriginSuggestions = suggestions;
suggestions = autoCompleteView.FilterSuggestions(suggestions, autoCompleteView.SearchText);
autoCompleteView.SuggestionsListView.ItemsSource = suggestions;
}
private static void OnSearchTextChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
var suggestions = autoCompleteView.OriginSuggestions;
if (newValue != null)
{
suggestions = autoCompleteView.FilterSuggestions(suggestions, autoCompleteView.SearchText);
// assign when initializing with data
if (autoCompleteView.SearchEntry.Text != autoCompleteView.SearchText)
{
autoCompleteView.SearchEntry.Text = autoCompleteView.SearchText;
}
}
autoCompleteView.SuggestionsListView.ItemsSource = suggestions;
if (Device.OS == TargetPlatform.Android)
{
// update the layout -> only do this when user is typing instead of selection an item from suggestions list
// -> prevent duplicated update layout
if (!autoCompleteView.IsSelected)
{
autoCompleteView.UpdateLayout();
}
else
{
autoCompleteView.IsSelected = false;
}
}
}
private static void OnSuggestionItemTemplateChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
if (autoCompleteView.SuggestionsListView != null)
{
autoCompleteView.SuggestionsListView.ItemTemplate = autoCompleteView.SuggestionItemTemplate;
}
}
public IEnumerable FilterSuggestions(IEnumerable suggestions, string keyword)
{
if (string.IsNullOrEmpty(keyword) || suggestions == null) return suggestions;
var searchWords = keyword.ConvertToNonMark().ToLower().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var result = suggestions.Cast<object>();
foreach (var item in searchWords)
{
if (!string.IsNullOrEmpty(DisplayPropertyName))
{
result = result.Where(x => x.GetType().GetRuntimeProperty(DisplayPropertyName).GetValue(x).ToString().ConvertToNonMark().ToLower().Contains(item)).ToList();
}
else
{
result = result.Where(x => x.ToString().ConvertToNonMark().ToLower().Contains(item)).ToList();
}
}
return result;
}
private static void OnPlaceholderChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
autoCompleteView.SearchEntry.Placeholder = newValue?.ToString();
}
public void UpdateLayout()
{
var expectedHeight = this.getExpectedHeight();
Container.HeightRequest = expectedHeight;
Container.ForceLayout();
}
private void SearchEntry_TextChanged(object sender, TextChangedEventArgs e)
{
TotalNumberOfTypings++;
Device.StartTimer(TimeSpan.FromMilliseconds(1000), () => {
TotalNumberOfTypings--;
if (TotalNumberOfTypings == 0)
{
SearchText = e.NewTextValue;
}
return false;
});
}
private void SearchEntry_Focused(object sender, FocusEventArgs e)
{
UpdateLayout();
IsSelected = false;
}
private void SearchEntry_Unfocused(object sender, FocusEventArgs e)
{
Container.HeightRequest = 50;
Container.ForceLayout();
}
private void SuggestionsListView_ItemSelected(object sender, ItemTappedEventArgs e)
{
IsSelected = true;
SearchEntry.Text = !string.IsNullOrEmpty(DisplayPropertyName) ? e.Item?.GetType()?.GetRuntimeProperty(DisplayPropertyName)?.GetValue(e.Item)?.ToString() : e.Item?.ToString();
Container.HeightRequest = 50;
Container.ForceLayout();
}
private void OverlapContentView_Tapped(object sender, TappedEventArgs e)
{
UpdateLayout();
IsSelected = false;
}
private int getExpectedHeight()
{
var items = SuggestionsListView.ItemsSource as IList;
int wrapperHeightRequest = items != null ?
(items.Count >= MaximumVisibleSuggestionItems ? MaximumVisibleSuggestionItems * 40 : items.Count * 40) : 0;
if (Device.OS == TargetPlatform.Android)
{
return wrapperHeightRequest + 50;
}
return MaximumVisibleSuggestionItems * 40 + 50;
}
public AutoCompleteView()
{
Container = new Grid();
SearchEntry = new Entry();
SuggestionsListView = new ItemsStack();
SuggestionWrapper = new NestedScrollView();
// init Grid Layout
Container.RowSpacing = 0;
Container.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
Container.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
Container.RowDefinitions.Add(new RowDefinition() { Height = 50 });
Container.HeightRequest = 50;
// init Search Entry
SearchEntry.HorizontalOptions = LayoutOptions.Fill;
SearchEntry.VerticalOptions = LayoutOptions.Fill;
SearchEntry.TextChanged += SearchEntry_TextChanged;
SearchEntry.Unfocused += SearchEntry_Unfocused;
SearchEntry.Focused += SearchEntry_Focused;
// init Suggestions ListView
SuggestionsListView.BackgroundColor = Color.White;
SuggestionsListView.ItemTapped += SuggestionsListView_ItemSelected;
SuggestionsListView.VerticalOptions = LayoutOptions.End;
SuggestionsListView.Spacing = 1;
// suggestions Listview's wrapper
SuggestionWrapper.VerticalOptions = LayoutOptions.Fill;
SuggestionWrapper.Orientation = ScrollOrientation.Vertical;
SuggestionWrapper.BackgroundColor = Color.White;
SuggestionWrapper.Content = SuggestionsListView;
Container.Children.Add(SuggestionWrapper);
Container.Children.Add(SearchEntry, 0, 1);
this.Content = Container;
}
}
用法示例:
<customControls:AutoCompleteView SearchText="{Binding User.UniversitySchool}" Suggestions="{Binding Schools}" DisplayPropertyName="Name" Placeholder="Please choose your school">
<customControls:AutoCompleteView.SuggestionItemTemplate>
<DataTemplate>
<ContentView Padding="10">
<Label Text="{Binding Name}" HeightRequest="20" LineBreakMode="HeadTruncation" Style="{StaticResource MainContentLabel}" />
</ContentView>
</DataTemplate>
</customControls:AutoCompleteView.SuggestionItemTemplate>
</customControls:AutoCompleteView>
在这个视图中,我使用了 ItemStack 控件。你可以参考这个:https://gist.github.com/NVentimiglia/2723411428cdbb72fac6
我有一个 Xamarin.Forms 自定义控件,您可以使用它与 iOS、Android 和 UWP 一起使用。它使用自定义渲染器在幕后提供原生 UI。我构建这个是因为我没有找到任何控件可以提供良好的原生体验并且在下拉列表打开时不会更改控件的高度。
所有文档 + 对 NuGet 包的参考都可以在这里找到:
https://github.com/dotMorten/XamarinFormsControls/tree/master/AutoSuggestBox
您可以使用 SyncFusion AutoComplete 插件轻松实现此目的。这为您提供了多种选择,而不是进行自定义渲染。
参考:https://help.syncfusion.com/xamarin/sfautocomplete/getting-started
我尝试按照 Imdad 的回答构建自己的 suggestions/autocomplete。我的标准阻碍了我,当建议填充列表视图时,它必须显示在顶部或展开。没有列表视图永久占用 space。
你可以试试https://github.com/XamFormsExtended/Xfx.Controls
但是我遇到了一些问题。它显示在顶部
我遇到了一个问题,即自动完成视图中的文本不会从源绑定更新或从使用 https://github.com/XLabs/Xamarin-Forms-Labs 自动完成视图的代码隐藏。这会暂时推开阻碍以显示建议
我亲自去解决了这个问题https://github.com/dotMorten/XamarinFormsControls/tree/master/AutoSuggestBox
我正在使用这个库SupportWidgetXF
它是跨平台的。
我是 Xamarin.Forms 平台的新手。我希望你能帮助我前进。我想在 xamarin.forms 中有一个类似自动完成的控件,如下所示
(来源:codetheory.in)
能否请您指导如何在 Xamarin.Forms 中实现?我想用入口控件实现它
TIA
您没有包含您想要的内容,只是某种自动完成。
我将针对项目列表指出一般的手动方式:
- 使用 TextBox 允许用户输入文本。
- 使用列表将所有对象及其可搜索的 属性(例如对象名称)收集在一起。
- 当用户在 TextBox 中键入内容时,应用程序应在列表中搜索在 TextBox 中输入的字符串。
- 建议应根据键入的字符串值显示在 TextBox 下的 ListView 中。
- 用户单击 ListView 项目,这是一个建议,然后通过将对象名称从单击的项目中提取到文本框来自动完成。
无需上述冗长的粗略过程即可完成自动完成的一般方法是使用 Android AutoCompleteTextView。
您仍然可以使用基本逻辑在 Xamarin Forms 中完成。
查找 here Android 的 AutoCompleteTextView。 在 Xamarin Forms 中查找 here, here and here 以获取有关自动完成的帮助。
请阅读这些文章并尝试使用自定义渲染器在 Xamarin.Forms 上实施解决方案。
我在我的项目中实现了一个 AutocompleteView。可以参考一下。
public class AutoCompleteView : ContentView
{
public static readonly BindableProperty SuggestionsProperty = BindableProperty.Create(nameof(Suggestions), typeof(IEnumerable), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnSuggestionsChanged);
public static readonly BindableProperty SearchTextProperty = BindableProperty.Create(nameof(SearchText), typeof(string), typeof(AutoCompleteView), null, BindingMode.TwoWay, null, OnSearchTextChanged);
public static readonly BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnPlaceholderChanged);
public static readonly BindableProperty MaximumVisibleSuggestionItemsProperty = BindableProperty.Create(nameof(MaximumVisibleSuggestionItems), typeof(int), typeof(AutoCompleteView), 4);
public static readonly BindableProperty SuggestionItemTemplateProperty = BindableProperty.Create(nameof(SuggestionItemTemplate), typeof(DataTemplate), typeof(AutoCompleteView), null, BindingMode.OneWay, null, OnSuggestionItemTemplateChanged);
public static readonly BindableProperty DisplayPropertyNameProperty = BindableProperty.Create(nameof(DisplayPropertyName), typeof(string), typeof(AutoCompleteView));
public IEnumerable Suggestions
{
get { return (IEnumerable)GetValue(SuggestionsProperty); }
set { SetValue(SuggestionsProperty, value); }
}
public string SearchText
{
get { return (string)GetValue(SearchTextProperty); }
set { SetValue(SearchTextProperty, value); }
}
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
public int MaximumVisibleSuggestionItems
{
get { return (int)GetValue(MaximumVisibleSuggestionItemsProperty); }
set { SetValue(MaximumVisibleSuggestionItemsProperty, value); }
}
public DataTemplate SuggestionItemTemplate
{
get { return (DataTemplate)GetValue(SuggestionItemTemplateProperty); }
set { SetValue(SuggestionItemTemplateProperty, value); }
}
public string DisplayPropertyName
{
get { return (string)GetValue(DisplayPropertyNameProperty); }
set { SetValue(DisplayPropertyNameProperty, value); }
}
public ItemsStack SuggestionsListView { get; private set; }
public Entry SearchEntry { get; private set; }
public IEnumerable OriginSuggestions { get; private set; }
public NestedScrollView SuggestionWrapper { get; private set; }
public Grid Container { get; private set; }
public bool IsSelected { get; private set; }
public int TotalNumberOfTypings { get; private set; }
private static void OnSuggestionsChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
var suggestions = (IEnumerable)newValue;
autoCompleteView.OriginSuggestions = suggestions;
suggestions = autoCompleteView.FilterSuggestions(suggestions, autoCompleteView.SearchText);
autoCompleteView.SuggestionsListView.ItemsSource = suggestions;
}
private static void OnSearchTextChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
var suggestions = autoCompleteView.OriginSuggestions;
if (newValue != null)
{
suggestions = autoCompleteView.FilterSuggestions(suggestions, autoCompleteView.SearchText);
// assign when initializing with data
if (autoCompleteView.SearchEntry.Text != autoCompleteView.SearchText)
{
autoCompleteView.SearchEntry.Text = autoCompleteView.SearchText;
}
}
autoCompleteView.SuggestionsListView.ItemsSource = suggestions;
if (Device.OS == TargetPlatform.Android)
{
// update the layout -> only do this when user is typing instead of selection an item from suggestions list
// -> prevent duplicated update layout
if (!autoCompleteView.IsSelected)
{
autoCompleteView.UpdateLayout();
}
else
{
autoCompleteView.IsSelected = false;
}
}
}
private static void OnSuggestionItemTemplateChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
if (autoCompleteView.SuggestionsListView != null)
{
autoCompleteView.SuggestionsListView.ItemTemplate = autoCompleteView.SuggestionItemTemplate;
}
}
public IEnumerable FilterSuggestions(IEnumerable suggestions, string keyword)
{
if (string.IsNullOrEmpty(keyword) || suggestions == null) return suggestions;
var searchWords = keyword.ConvertToNonMark().ToLower().Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
var result = suggestions.Cast<object>();
foreach (var item in searchWords)
{
if (!string.IsNullOrEmpty(DisplayPropertyName))
{
result = result.Where(x => x.GetType().GetRuntimeProperty(DisplayPropertyName).GetValue(x).ToString().ConvertToNonMark().ToLower().Contains(item)).ToList();
}
else
{
result = result.Where(x => x.ToString().ConvertToNonMark().ToLower().Contains(item)).ToList();
}
}
return result;
}
private static void OnPlaceholderChanged(object bindable, object oldValue, object newValue)
{
var autoCompleteView = bindable as AutoCompleteView;
autoCompleteView.SearchEntry.Placeholder = newValue?.ToString();
}
public void UpdateLayout()
{
var expectedHeight = this.getExpectedHeight();
Container.HeightRequest = expectedHeight;
Container.ForceLayout();
}
private void SearchEntry_TextChanged(object sender, TextChangedEventArgs e)
{
TotalNumberOfTypings++;
Device.StartTimer(TimeSpan.FromMilliseconds(1000), () => {
TotalNumberOfTypings--;
if (TotalNumberOfTypings == 0)
{
SearchText = e.NewTextValue;
}
return false;
});
}
private void SearchEntry_Focused(object sender, FocusEventArgs e)
{
UpdateLayout();
IsSelected = false;
}
private void SearchEntry_Unfocused(object sender, FocusEventArgs e)
{
Container.HeightRequest = 50;
Container.ForceLayout();
}
private void SuggestionsListView_ItemSelected(object sender, ItemTappedEventArgs e)
{
IsSelected = true;
SearchEntry.Text = !string.IsNullOrEmpty(DisplayPropertyName) ? e.Item?.GetType()?.GetRuntimeProperty(DisplayPropertyName)?.GetValue(e.Item)?.ToString() : e.Item?.ToString();
Container.HeightRequest = 50;
Container.ForceLayout();
}
private void OverlapContentView_Tapped(object sender, TappedEventArgs e)
{
UpdateLayout();
IsSelected = false;
}
private int getExpectedHeight()
{
var items = SuggestionsListView.ItemsSource as IList;
int wrapperHeightRequest = items != null ?
(items.Count >= MaximumVisibleSuggestionItems ? MaximumVisibleSuggestionItems * 40 : items.Count * 40) : 0;
if (Device.OS == TargetPlatform.Android)
{
return wrapperHeightRequest + 50;
}
return MaximumVisibleSuggestionItems * 40 + 50;
}
public AutoCompleteView()
{
Container = new Grid();
SearchEntry = new Entry();
SuggestionsListView = new ItemsStack();
SuggestionWrapper = new NestedScrollView();
// init Grid Layout
Container.RowSpacing = 0;
Container.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Star });
Container.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Star });
Container.RowDefinitions.Add(new RowDefinition() { Height = 50 });
Container.HeightRequest = 50;
// init Search Entry
SearchEntry.HorizontalOptions = LayoutOptions.Fill;
SearchEntry.VerticalOptions = LayoutOptions.Fill;
SearchEntry.TextChanged += SearchEntry_TextChanged;
SearchEntry.Unfocused += SearchEntry_Unfocused;
SearchEntry.Focused += SearchEntry_Focused;
// init Suggestions ListView
SuggestionsListView.BackgroundColor = Color.White;
SuggestionsListView.ItemTapped += SuggestionsListView_ItemSelected;
SuggestionsListView.VerticalOptions = LayoutOptions.End;
SuggestionsListView.Spacing = 1;
// suggestions Listview's wrapper
SuggestionWrapper.VerticalOptions = LayoutOptions.Fill;
SuggestionWrapper.Orientation = ScrollOrientation.Vertical;
SuggestionWrapper.BackgroundColor = Color.White;
SuggestionWrapper.Content = SuggestionsListView;
Container.Children.Add(SuggestionWrapper);
Container.Children.Add(SearchEntry, 0, 1);
this.Content = Container;
}
}
用法示例:
<customControls:AutoCompleteView SearchText="{Binding User.UniversitySchool}" Suggestions="{Binding Schools}" DisplayPropertyName="Name" Placeholder="Please choose your school">
<customControls:AutoCompleteView.SuggestionItemTemplate>
<DataTemplate>
<ContentView Padding="10">
<Label Text="{Binding Name}" HeightRequest="20" LineBreakMode="HeadTruncation" Style="{StaticResource MainContentLabel}" />
</ContentView>
</DataTemplate>
</customControls:AutoCompleteView.SuggestionItemTemplate>
</customControls:AutoCompleteView>
在这个视图中,我使用了 ItemStack 控件。你可以参考这个:https://gist.github.com/NVentimiglia/2723411428cdbb72fac6
我有一个 Xamarin.Forms 自定义控件,您可以使用它与 iOS、Android 和 UWP 一起使用。它使用自定义渲染器在幕后提供原生 UI。我构建这个是因为我没有找到任何控件可以提供良好的原生体验并且在下拉列表打开时不会更改控件的高度。 所有文档 + 对 NuGet 包的参考都可以在这里找到: https://github.com/dotMorten/XamarinFormsControls/tree/master/AutoSuggestBox
您可以使用 SyncFusion AutoComplete 插件轻松实现此目的。这为您提供了多种选择,而不是进行自定义渲染。
参考:https://help.syncfusion.com/xamarin/sfautocomplete/getting-started
我尝试按照 Imdad 的回答构建自己的 suggestions/autocomplete。我的标准阻碍了我,当建议填充列表视图时,它必须显示在顶部或展开。没有列表视图永久占用 space。
你可以试试https://github.com/XamFormsExtended/Xfx.Controls 但是我遇到了一些问题。它显示在顶部
我遇到了一个问题,即自动完成视图中的文本不会从源绑定更新或从使用 https://github.com/XLabs/Xamarin-Forms-Labs 自动完成视图的代码隐藏。这会暂时推开阻碍以显示建议
我亲自去解决了这个问题https://github.com/dotMorten/XamarinFormsControls/tree/master/AutoSuggestBox
我正在使用这个库SupportWidgetXF
它是跨平台的。