Skip to content

Instantly share code, notes, and snippets.

@kevinmutlow
Last active June 25, 2020 16:09
Show Gist options
  • Save kevinmutlow/77cf36ff2b3ddeabddeda5115020bb8e to your computer and use it in GitHub Desktop.
Save kevinmutlow/77cf36ff2b3ddeabddeda5115020bb8e to your computer and use it in GitHub Desktop.
Custom SearchBar for Xamarin.Forms with Filter icon and auto-search on text changed
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App.Core.Controls.ExtSearchBar"
x:Name="Root">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<!--
WidthRequest="1" ==> Xamarin bug ==> SearchBar inside a Grid, needs WidthRequest set (workaround)
https://bugzilla.xamarin.com/show_bug.cgi?id=59595
-->
<SearchBar
x:Name="searchBar"
WidthRequest="1"
Placeholder="{Binding Source={x:Reference Root}, Path=Placeholder}"
SearchCommand="{Binding Source={x:Reference Root}, Path=SearchCommand}"
Text="{Binding Source={x:Reference Root}, Path=Text}"
TextChanged="SearchBar_TextChanged"/>
<Image
x:Name="iconFilter"
Source="ic_equalizer_ver.png"
IsVisible="{Binding Source={x:Reference Root}, Path=HasFilterIcon}"
VerticalOptions="Fill"
HorizontalOptions="End"
WidthRequest="25"
Aspect="AspectFit"
Margin="0,0,20,0">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="IconFilter_Tapped" />
</Image.GestureRecognizers>
</Image>
</Grid>
</ContentView>
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App.Core.Controls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ExtSearchBar : ContentView
{
CancellationTokenSource cts = null;
public ExtSearchBar()
{
InitializeComponent();
}
#region Properties
private int _textChangedDelay;
public int TextChangedDelay
{
get { return _textChangedDelay; }
set { _textChangedDelay = value; }
}
#endregion
#region Bindable Properties
#region HasFilterIcon
public static BindableProperty HasFilterIconProperty =
BindableProperty.Create(
nameof(HasFilterIcon),
typeof(bool),
typeof(ExtSearchBar),
defaultValue: default(bool),
defaultBindingMode: BindingMode.OneWay
);
public bool HasFilterIcon
{
get { return (bool)GetValue(HasFilterIconProperty); }
set { SetValue(HasFilterIconProperty, value); }
}
#endregion
#region Placeholder
public static BindableProperty PlaceholderProperty =
BindableProperty.Create(
nameof(Placeholder),
typeof(string),
typeof(ExtSearchBar),
defaultValue: default(string),
defaultBindingMode: BindingMode.OneWay
);
public string Placeholder
{
get { return (string)GetValue(PlaceholderProperty); }
set { SetValue(PlaceholderProperty, value); }
}
#endregion
#region Text
public static BindableProperty TextProperty =
BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(SearchBar),
defaultValue: default(string),
defaultBindingMode: BindingMode.TwoWay
);
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
#endregion
#region TextChangedCommand
public static BindableProperty TextChangedCommandProperty =
BindableProperty.Create(
nameof(TextChangedCommand),
typeof(Command),
typeof(SearchBar),
defaultValue: default(Command),
defaultBindingMode: BindingMode.OneWay
);
public Command TextChangedCommand
{
get { return (Command)GetValue(TextChangedCommandProperty); }
set { SetValue(TextChangedCommandProperty, value); }
}
#endregion
#region FilterCommand
public static BindableProperty FilterCommandProperty =
BindableProperty.Create(
nameof(FilterCommand),
typeof(ICommand),
typeof(SearchBar),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay
);
public ICommand FilterCommand
{
get { return (ICommand)GetValue(FilterCommandProperty); }
set { SetValue(FilterCommandProperty, value); }
}
#endregion
#region FilterCommandParameter
public static BindableProperty FilterCommandParameterProperty =
BindableProperty.Create(
nameof(FilterCommandParameter),
typeof(object),
typeof(SearchBar),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay
);
public object FilterCommandParameter
{
get { return (object)GetValue(FilterCommandParameterProperty); }
set { SetValue(FilterCommandParameterProperty, value); }
}
#endregion
#region SearchCommand
public static BindableProperty SearchCommandProperty =
BindableProperty.Create(
nameof(SearchCommand),
typeof(Command),
typeof(SearchBar),
defaultValue: default(Command),
defaultBindingMode: BindingMode.OneWay
);
public Command SearchCommand
{
get { return (Command)GetValue(SearchCommandProperty); }
set { SetValue(SearchCommandProperty, value); }
}
#endregion
#endregion
#region Methods
public async void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
if (HasFilterIcon)
{
var sbar = sender as Xamarin.Forms.SearchBar;
if (string.IsNullOrWhiteSpace(sbar?.Text))
{
iconFilter.TranslateTo(0, 0);
}
else
{
int transX = -25; //Good for Android
if(Device.RuntimePlatform == Device.iOS)
transX = -80; //Good for iOS
if (iconFilter.TranslationX != transX)
iconFilter.TranslateTo(transX, 0, 100);
}
}
if (TextChangedCommand != null)
{
if (cts != null) cts.Cancel();
cts = new CancellationTokenSource();
var ctoken = cts.Token;
try
{
var millisDelay = TextChangedDelay > 0 ? TextChangedDelay : 650;
await Task.Delay(millisDelay, ctoken);
if (ctoken.IsCancellationRequested)
return;
if (TextChangedCommand.CanExecute(null))
TextChangedCommand?.Execute(null);
}
catch (OperationCanceledException)
{
// Expected
}
}
}
private void IconFilter_Tapped(object sender, EventArgs e)
{
if (FilterCommand?.CanExecute(FilterCommandParameter) ?? false)
FilterCommand?.Execute(FilterCommandParameter);
}
#endregion
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:App.Core.Controls"
x:Class="App.Core.Pages.Other.SearchBarPage"
Title="{ Binding PageTitle }">
<StackLayout>
<controls:ExtSearchBar
Placeholder="Search for an item"
Text="{ Binding SearchText }"
TextChangedDelay="550"
TextChangedCommand="{ Binding SearchCommand }"
SearchCommand="{ Binding SearchCommand }"
HasFilterIcon="True"
FilterCommand="{ Binding FilterTappedCommand }" />
</StackLayout>
</ContentPage>
using App.Core.ViewModels.Other;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App.Core.Pages.Other
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SearchBarPage : ContentPage
{
public SearchBarPage ()
{
InitializeComponent ();
BindingContext = new SearchBarPageViewModel(this.Navigation);
}
}
}
using System.Diagnostics;
using System.Windows.Input;
using Xamarin.Forms;
namespace App.Core.ViewModels.Other
{
public class SearchBarPageViewModel : BasePageViewModel
{
private readonly INavigation Navigation;
public SearchBarPageViewModel(INavigation navigation)
{
Navigation = navigation;
PageTitle = "Extended SearchBar";
}
#region Properties
private string _SearchText;
public string SearchText
{
get { return _SearchText; }
set { SetPropertyValue(ref _SearchText, value); }
}
#endregion
#region Commands
public ICommand SearchCommand => new Command(SearchAction);
public ICommand FilterTappedCommand => new Command(FilterTappedAction);
#endregion
#region Methods
void SearchAction()
{
Debug.WriteLine("SearchAction");
}
void FilterTappedAction()
{
Debug.WriteLine("FilterTappedAction");
}
#endregion
}
}
@fedemkr
Copy link

fedemkr commented Oct 14, 2019

Excellent gist, just a couple of things I'd improve:

  • Make TextChangedDelay autoproperty with default value, so you don't need the line var millisDelay = TextChangedDelay > 0 ? TextChangedDelay : 650;, just:
public int TextChangedDelay { get; set; } = 650;
  • Add CharactersThreshold so that the search is done when a minimum number of characters is inputed:

Fist, adding the property:

public int CharactersThreshold { get; set; } = 3;

and then in the SearchBar_TextChanged instead of if (TextChangedCommand != null), you can put this:

if (TextChangedCommand == null
    ||
    (!string.IsNullOrEmpty(e.NewTextValue) && e.NewTextValue.Length < CharactersThreshold))
    return;

HIH

@lupitalabra23
Copy link

necesito que mi searchbar busque x registro utilizando los datos de mysql, ya tengo las listview y se pintan los datos, utilice mvvm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment