最近用Xamarin开发APP时需要做一个AutoComplete,兜了一圈发现也没什么特别好用的,就根据一些经验重新写了一个
public class AutoCompleteView : ContentView
{
private const int RowHeight = 30;
public static readonly BindableProperty SearchTextProperty =
BindableProperty.Create(nameof(SearchText), typeof(string), typeof(AutoCompleteView),
defaultBindingMode: BindingMode.TwoWay, propertyChanged: OnSearchTextChanged);
public static readonly BindableProperty SearchTextColorProperty =
BindableProperty.Create(nameof(SearchTextColor), typeof(Color), typeof(AutoCompleteView), Color.Black,
propertyChanged: OnSearchTextColorChanged);
public static readonly BindableProperty MaximumVisibleElementsProperty =
BindableProperty.Create(nameof(MaximumVisibleElements), typeof(int), typeof(AutoCompleteView), 4);
public static readonly BindableProperty MinimumPrefixCharacterProperty =
BindableProperty.Create(nameof(MinimumPrefixCharacter), typeof(int), typeof(AutoCompleteView), 1);
public static readonly BindableProperty PlaceholderProperty =
BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(AutoCompleteView),
propertyChanged: OnPlaceholderChanged);
public static readonly BindableProperty PlaceholderColorProperty =
BindableProperty.Create(nameof(PlaceholderColor), typeof(Color), typeof(AutoCompleteView), Color.DarkGray,
propertyChanged: OnPlaceholderColorChanged);
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(AutoCompleteView), propertyChanged: OnItemSourceChanged);
public static readonly BindableProperty IsClearButtonVisibleProperty =
BindableProperty.Create(nameof(IsClearButtonVisible), typeof(bool), typeof(AutoCompleteView), true,
propertyChanged: OnIsClearImageVisibleChanged);
public static readonly BindableProperty SearchModeProperty = BindableProperty.Create(nameof(SearchMode),
typeof(SearchMode), typeof(AutoCompleteView), SearchMode.Contains, BindingMode.TwoWay, propertyChanged: OnSearchModeChanged);
private static void OnSearchTextChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var autoCompleteView = bindable as AutoCompleteView;
var searchText = (string)newvalue;
autoCompleteView.UpdateSuggestions(searchText);
}
private static void OnItemSourceChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var autoCompleteView = bindable as AutoCompleteView;
var items = (IEnumerable)newvalue;
autoCompleteView._originSuggestions = items;
}
private static void OnSearchTextColorChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var AutoCompleteView = bindable as AutoCompleteView;
var textColor = (Color)newvalue;
AutoCompleteView.SearchEntry.TextColor = textColor;
}
private static void OnPlaceholderColorChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var AutoCompleteView = bindable as AutoCompleteView;
var placeholderColor = (Color)newvalue;
AutoCompleteView.SearchEntry.PlaceholderColor = placeholderColor;
}
private static void OnIsClearImageVisibleChanged(BindableObject bindable, object oldValue, object newValue)
{
var AutoCompleteView = bindable as AutoCompleteView;
var isVisible = (bool)newValue;
AutoCompleteView.ClearSearchEntryImage.IsVisible = isVisible;
}
private static void OnPlaceholderChanged(BindableObject bindable, object oldValue, object newValue)
{
var AutoCompleteView = bindable as AutoCompleteView;
var placeholder = (string)newValue;
AutoCompleteView.SearchEntry.Placeholder = placeholder;
}
private static void OnSearchModeChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var AutoCompleteView = bindable as AutoCompleteView;
var searchType = (SearchMode)newvalue;
AutoCompleteView.SearchMode = searchType;
}
public string SearchText
{
get => (string)GetValue(SearchTextProperty);
set => SetValue(SearchTextProperty, value);
}
public Color SearchTextColor
{
get => (Color)GetValue(SearchTextColorProperty);
set => SetValue(SearchTextColorProperty, value);
}
public int MaximumVisibleElements
{
get => (int)GetValue(MaximumVisibleElementsProperty);
set => SetValue(MaximumVisibleElementsProperty, value);
}
public int MinimumPrefixCharacter
{
get => (int)GetValue(MinimumPrefixCharacterProperty);
set => SetValue(MinimumPrefixCharacterProperty, value);
}
public IEnumerable ItemsSource
{
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public string Placeholder
{
get => (string)GetValue(PlaceholderProperty);
set => SetValue(PlaceholderProperty, value);
}
public Color PlaceholderColor
{
get => (Color)GetValue(PlaceholderColorProperty);
set => SetValue(PlaceholderColorProperty, value);
}
public bool IsClearButtonVisible
{
get => (bool)GetValue(IsClearButtonVisibleProperty);
set => SetValue(IsClearButtonVisibleProperty, value);
}
public SearchMode SearchMode
{
get => (SearchMode)GetValue(SearchModeProperty);
set
{
SetValue(SearchModeProperty, value);
UpdateSuggestions(SearchText);
}
}
private StackLayout Container;
private ScrollView SuggestionWrapper;
private StackLayout SuggestionsStackLayout;
private BorderlessEntry SearchEntry;
private Image ClearSearchEntryImage;
private IEnumerable _originSuggestions = Array.Empty<object>();
public AutoCompleteView()
{
InitContainer();
InitSearchEntry();
InitClearImage();
InitSuggestionsListView();
InitSuggestionsScrollView();
PlaceControlsInContainer();
Content = Container;
}
private void InitContainer()
{
Container = new StackLayout
{
HeightRequest = 60,
HorizontalOptions = LayoutOptions.FillAndExpand,
//BackgroundColor = Color.Red,
Orientation = StackOrientation.Vertical,
Padding = 0
};
}
private void InitSearchEntry()
{
SearchEntry = new BorderlessEntry
{
HorizontalOptions = LayoutOptions.FillAndExpand,
Margin = 5,
//BackgroundColor = Color.Blue,
HeightRequest = 50
};
SearchEntry.TextChanged += SearchEntry_TextChanged;
}
private void InitClearImage()
{
ClearSearchEntryImage = new Image
{
Source = "",
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.End,
WidthRequest = 24,
HeightRequest = 24,
Margin = Device.RuntimePlatform != Device.UWP ? new Thickness(0, 0, 5, 0) : new Thickness(0, 0, 10, 5)
};
var tapGestureRecognizer = new TapGestureRecognizer();
tapGestureRecognizer.Tapped += (x, y) =>
{
SearchEntry.Text = "";
};
ClearSearchEntryImage.GestureRecognizers.Add(tapGestureRecognizer);
}
private void InitSuggestionsListView()
{
SuggestionsStackLayout = new StackLayout
{
//BackgroundColor = Color.Silver,
VerticalOptions = LayoutOptions.End,
Spacing = 0,
Margin = 5,
};
}
private void InitSuggestionsScrollView()
{
SuggestionWrapper = new ScrollView
{
Orientation = ScrollOrientation.Vertical,
VerticalOptions = LayoutOptions.End,
Content = new ShadowedFrame
{
Content = SuggestionsStackLayout,
Padding = 0,
BorderColor = Color.Silver
},
Padding = 0,
IsVisible = true,
HorizontalOptions = LayoutOptions.FillAndExpand
};
}
private void PlaceControlsInContainer()
{
SearchEntry.HorizontalOptions = new LayoutOptions
{
Alignment = LayoutAlignment.Fill,
Expands = true
};
Container.Children.Add(SearchEntry);
Container.Children.Add(SuggestionWrapper);
}
private void SearchEntry_TextChanged(object sender, TextChangedEventArgs e)
{
SearchText = e.NewTextValue;
}
private void SearchEntry_IconChanged(string searchText)
{
ClearSearchEntryImage.Source = string.IsNullOrEmpty(searchText)
? ""
: "";
}
private void UpdateSuggestions(string newSearchText)
{
var newSuggestions = _originSuggestions;
SearchEntry_IconChanged(newSearchText);
if (newSearchText.Length >= MinimumPrefixCharacter)
{
newSuggestions = FilterSuggestions(newSuggestions, newSearchText);
}
else
{
SuggestionWrapper.HeightRequest = 0;
Container.HeightRequest = SearchEntry.HeightRequest + 30;
Container.ForceLayout();
return;
}
SuggestionWrapper.IsVisible = newSearchText.Length != 0 &&
newSearchText.Length >= MinimumPrefixCharacter &&
newSuggestions.Cast<object>().Count() != 0;
SuggestionsStackLayout.Children.Clear();
if(!string.IsNullOrEmpty(newSearchText))
{
foreach (var item in newSuggestions)
{
StackLayout layout = new StackLayout()
{
Orientation = StackOrientation.Vertical
};
Label itemLabel = new Label
{
Text = item.ToString(),
TextColor = System.Drawing.Color.Black,
FontSize = 12,
HeightRequest = RowHeight,
Padding = 0,
BackgroundColor = Color.Pink,
VerticalTextAlignment = TextAlignment.Center,
GestureRecognizers =
{
new TapGestureRecognizer
{
Command = new Command(() =>
{
SearchEntry.Text = item.ToString();
SuggestionWrapper.IsVisible = false;
UpdateLayout();
})
}
},
};
BoxView frame = new BoxView()
{
HeightRequest = 1,
BackgroundColor = Color.Silver,
HorizontalOptions = LayoutOptions.FillAndExpand
};
layout.Children.Add(itemLabel);
layout.Children.Add(frame);
SuggestionsStackLayout.Children.Add(layout);
}
}
if (SuggestionWrapper.IsVisible)
{
UpdateLayout();
}
}
private IEnumerable FilterSuggestions(IEnumerable itemsSource, string searchText)
{
return itemsSource
.Cast<object>()
.Where(obj => SearchMode.Filter(searchText, obj))
.ToArray();
}
private int GetSuggestionsListHeight()
{
if (!SuggestionWrapper.IsVisible)
{
return 0;
}
var items = SuggestionsStackLayout.Children.Cast<object>().ToList();
return items.ToList().Count >= MaximumVisibleElements
? MaximumVisibleElements * RowHeight
: items.Count * RowHeight;
}
private void UpdateLayout()
{
var listHeight = GetSuggestionsListHeight();
SuggestionWrapper.HeightRequest = listHeight;
//Container.HeightRequest = SuggestionsStackLayout.Children.Count > 4 ? listHeight + SearchEntry.HeightRequest : listHeight + SearchEntry.HeightRequest + 20;
Container.HeightRequest = listHeight + SearchEntry.HeightRequest + 30;
Container.ForceLayout();
}
}
public class BorderlessEntry : Entry
{
}
public class SearchMode
{
private readonly Func<string, object, bool> _filter;
private SearchMode(Func<string, object, bool> filter)
{
_filter = filter;
}
public bool Filter(string entry, object obj) => _filter(entry, obj);
public static SearchMode StartsWith { get; } = new SearchMode((entry, obj) => obj.ToString().ToLower().StartsWith(entry.ToLower()));
public static SearchMode Contains { get; } = new SearchMode((entry, obj) => obj.ToString().ToLower().Contains(entry.ToLower()));
public static SearchMode EndsWith { get; } = new SearchMode((entry, obj) => obj.ToString().ToLower().EndsWith(entry.ToLower()));
public static SearchMode Using(Func<string, object, bool> filter)
{
return new SearchMode(filter);
}
}
public class ShadowedFrame : Frame
{
}
前端引用参照
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="Factory Country: " FontSize="14" TextColor="#081f2c" VerticalOptions="Start" Margin="0,20,0,0"></Label>
<!--<Entry Text="{Binding FactoryCountry}" VerticalOptions="Center" HorizontalOptions="FillAndExpand"></Entry>-->
<customControls:AutoCompleteView HorizontalOptions="FillAndExpand" Placeholder="Enter countrycode" SearchText="{Binding SearchCountry}" VerticalOptions="StartAndExpand"
ItemsSource="{Binding Countries}" SearchMode="{Binding SearchMode}" MaximumVisibleElements="8"/>
</StackLayout>