Ahmed Salah
Ahmed Salah

Reputation: 969

How to add swiping dots to Tabbed/Carousel page?

I'm wondering how to add dots indicating to swipe screens in either TabbedPage or CarouselPage like in the below image?

enter image description here

I tried adding images for that but they don't look natural so is there a real way for doing that?

My above workaround explanation in an example with 3 page:

I create 3 images each image has 3 dots one of them is highlighted:

First image highlighted dot is the first one.

Second image highlighted dot is the second one.

and etc.

Upvotes: 2

Views: 2432

Answers (1)

Abdul Gani
Abdul Gani

Reputation: 689

you can use Xamarin.Forms.CarouselView and write a user control for page indicators. Follow the steps below,

Using Package Console, Install-Package Xamarin.Forms.CarouselView -Version 2.3.0-pre2 (Xamarin.Forms.CarouselView) package from NuGet in all 3 projects (PCL, iOS and Android).

add reference to Carousel view in the page directives,

xmlns:cv="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.CarouselView" 

and the Xaml code as below,

 <StackLayout  Padding="0,0,0,5" BackgroundColor="#d8d8d8" >
     <cv:CarouselView x:Name="cview" ItemsSource="{Binding DataSource}" Position="{Binding Position, Mode=TwoWay}">
        <cv:CarouselView.ItemTemplate>
          <DataTemplate>       
            <Image  Aspect="AspectFill" HorizontalOptions="Center" VerticalOptions="Center" Source="{Binding PickedImage}" />
          </DataTemplate>
        </cv:CarouselView.ItemTemplate>
      </cv:CarouselView>
  <cutomControl:CarouselIndicators IndicatorHeight="16" IndicatorWidth="16" UnselectedIndicator="unselected_circle.png" SelectedIndicator="selected_circle.png" Position="{Binding Position}" ItemsSource="{Binding DataSource}" />
</StackLayout>

notice, Position and your viewmodel should have,

private int _position;
    public int Position
    {
        get { return _position; }
        set
        {
            _position = value;
            OnPropertyChanged();
        }
    }

notice, customControl below CarouselView.. Yes, you need to write a custom control for it. Just use the below custom control code and add reference in the page directive,

so your page directive will be as below,

 xmlns:cutomControl="clr-namespace:XXXX.CustomControls;assembly=XXXX"
 xmlns:cv="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.CarouselView"

and the custom control code is,

public class CarouselIndicators : Grid
{
    private ImageSource UnselectedImageSource = null;
    private ImageSource SelectedImageSource = null;
    private readonly StackLayout _indicators = new StackLayout() { Orientation = StackOrientation.Horizontal, HorizontalOptions = LayoutOptions.CenterAndExpand };

    public CarouselIndicators()
    {
        this.HorizontalOptions = LayoutOptions.CenterAndExpand;
        this.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
        this.Children.Add(_indicators);
    }

    public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(int), typeof(CarouselIndicators), 0, BindingMode.TwoWay, propertyChanging: PositionChanging);
    public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(CarouselIndicators), Enumerable.Empty<object>(), BindingMode.OneWay, propertyChanged: ItemsChanged);
    public static readonly BindableProperty SelectedIndicatorProperty = BindableProperty.Create(nameof(SelectedIndicator), typeof(string), typeof(CarouselIndicators), "", BindingMode.OneWay);
    public static readonly BindableProperty UnselectedIndicatorProperty = BindableProperty.Create(nameof(UnselectedIndicator), typeof(string), typeof(CarouselIndicators), "", BindingMode.OneWay);
    public static readonly BindableProperty IndicatorWidthProperty = BindableProperty.Create(nameof(IndicatorWidth), typeof(double), typeof(CarouselIndicators), 0.0, BindingMode.OneWay);
    public static readonly BindableProperty IndicatorHeightProperty = BindableProperty.Create(nameof(IndicatorHeight), typeof(double), typeof(CarouselIndicators), 0.0, BindingMode.OneWay);

    public string SelectedIndicator
    {
        get { return (string)this.GetValue(SelectedIndicatorProperty); }
        set { this.SetValue(SelectedIndicatorProperty, value); }
    }

    public string UnselectedIndicator
    {
        get { return (string)this.GetValue(UnselectedIndicatorProperty); }
        set { this.SetValue(UnselectedIndicatorProperty, value); }
    }

    public double IndicatorWidth
    {
        get { return (double)this.GetValue(IndicatorWidthProperty); }
        set { this.SetValue(IndicatorWidthProperty, value); }
    }

    public double IndicatorHeight
    {
        get { return (double)this.GetValue(IndicatorHeightProperty); }
        set { this.SetValue(IndicatorHeightProperty, value); }
    }

    public int Position
    {
        get { return (int)this.GetValue(PositionProperty); }
        set { this.SetValue(PositionProperty, value); }
    }

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)this.GetValue(ItemsSourceProperty); }
        set { this.SetValue(ItemsSourceProperty, (object)value); }
    }

    private void Clear()
    {
        _indicators.Children.Clear();
    }

    private void Init(int position)
    {

        if (UnselectedImageSource == null)
            UnselectedImageSource = ImageSource.FromFile(UnselectedIndicator);

        if (SelectedImageSource == null)
            SelectedImageSource = ImageSource.FromFile(SelectedIndicator);

        if (_indicators.Children.Count > 0)
        {
            for (int i = 0; i < _indicators.Children.Count; i++)
            {
                if (((Image)_indicators.Children[i]).ClassId == nameof(State.Selected) && i != position)
                    _indicators.Children[i] = BuildImage(State.Unselected, i);
                else if (((Image)_indicators.Children[i]).ClassId == nameof(State.Unselected) && i == position)
                    _indicators.Children[i] = BuildImage(State.Selected, i);
            }
        }
        else
        {
            var enumerator = ItemsSource.GetEnumerator();
            int count = 0;
            while (enumerator.MoveNext())
            {
                Image image = null;
                if (position == count)
                    image = BuildImage(State.Selected, count);
                else
                    image = BuildImage(State.Unselected, count);

                _indicators.Children.Add(image);

                count++;
            }
        }
    }

    private Image BuildImage(State state, int position)
    {
        var image = new Image()
        {
            WidthRequest = IndicatorWidth,
            HeightRequest = IndicatorHeight,
            ClassId = state.ToString()
        };

        switch (state)
        {
            case State.Selected:
                image.Source = SelectedImageSource;
                break;
            case State.Unselected:
                image.Source = UnselectedImageSource;
                break;
            default:
                throw new Exception("Invalid state selected");
        }

        image.GestureRecognizers.Add(new TapGestureRecognizer() { Command = new Command(() => { Position = position; }) });

        return image;
    }

    private static void PositionChanging(object bindable, object oldValue, object newValue)
    {
        var carouselIndicators = bindable as CarouselIndicators;

        carouselIndicators.Init(Convert.ToInt32(newValue));
    }

    private static void ItemsChanged(object bindable, object oldValue, object newValue)
    {
        var carouselIndicators = bindable as CarouselIndicators;

        carouselIndicators.Clear();
        carouselIndicators.Init(0);
    }

    public enum State
    {
        Selected,
        Unselected
    }
}

Upvotes: 2

Related Questions