Xaramin AutoCompleteView

最近用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>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值