Rating Bar

    Skand Swami

We belong to an era where people are dependent on feedbacks and review from fellow users to make up their minds and decide whether to invest in a certain service or to purchase an article. A rating system always comes in handy in such requirements. So we present you with a precise procedure to create a rating bar in Xamarin for both Android and iOS

ratingbarimage.png 
A Rating bar is used to take an input on a numerical scale from the user. It has a vast implementation for survey and feedback requirements. Here, we follow a custom control approach in order to create a rating bar. We start by creating a control for rating bar in the shared project. We implement that control using renderers in both iOS and Android.

Step 1- Create an empty project in Xamarin.Forms, let’s say “RatingBarDemo”.





Step 2- Make a folder named Controls and create a file named CustomRatingBar.cs, In CustomRatingBar, make some bindable property as in the following code:

namespace RatingBarDemo.Controls
{
    class CustomRatingBar:View
    {
        public event EventHandler<float> RatingChanged;
        public event EventHandler Tapped;

        public static BindableProperty IsSmallStyleProperty = BindableProperty.Create("IsSmallStyle", typeof(bool), 
typeof(CustomRatingBar), false);
        public bool IsSmallStyle
        {
            get { return (bool)GetValue(IsSmallStyleProperty); }
            set { SetValue(IsSmallStyleProperty, value); }
        }

        public static BindableProperty IsReadonlyProperty = BindableProperty.Create("IsReadonly", typeof(bool), typeof(CustomRatingBar), true);
        public bool IsReadonly
        {
            get { return (bool)GetValue(IsReadonlyProperty); }
            set { SetValue(IsReadonlyProperty, value); }
        }

        public static BindableProperty MaxStarsProperty = BindableProperty.Create("MaxStars", typeof(int), typeof(CustomRatingBar), 5);
        public int MaxStars
        {
            get { return (int)GetValue(MaxStarsProperty); }
            set { SetValue(MaxStarsProperty, value); }
        }

        public static BindableProperty RatingProperty = BindableProperty.Create("Rating", typeof(float), typeof(CustomRatingBar), 0f);
        public float Rating
        {
            get { return (float)GetValue(RatingProperty); }
            set { SetValue(RatingProperty, value); OnPropertyChanged("Rating"); }
        }

        public static BindableProperty StepSizeProperty = BindableProperty.Create("StepSize", typeof(float), typeof(CustomRatingBar), 0.5f);
        public float StepSize
        {
            get { return (float)GetValue(StepSizeProperty); }
            set { SetValue(StepSizeProperty, value); }
        }

        public void OnRatingChanged(float rating)
        {
            if (RatingChanged != null)
                RatingChanged.Invoke(this, rating);
        }

        public Color GetFillColor()
        {
            return Rating == 0 ? Color.Gray : Rating <= 2 ? Colors.StarRed : Rating <= 3 ? Colors.StarOrange : Rating <= 4 ? Colors.StarYellow : Colors.StarGreen;
        }

        public void OnTapped()
        {
            if (Tapped != null)
                Tapped.Invoke(this, null);
        }
    }
}

Step 3(optional)- Make a Helper class for different colour requisites, below code use.

namespace RatingBarDemo.Helpers
{
    class Colors
    {
        public static readonly Color StarRed = Color.FromHex("#E41E26");
        public static readonly Color StarOrange = Color.FromHex("#FF6817");
        public static readonly Color StarYellow = Color.FromHex("#FFDE17");
        public static readonly Color StarGreen = Color.FromHex("#3AB54A");
    }
}

Step 4 - For Android- Now we make a renderer for rating bar in android.

[assembly: ExportRenderer(typeof(CustomRatingBar), typeof(RatingBarRenderer))]
namespace RatingBarDemo.Droid.Renderers
{
    class RatingBarRenderer:ViewRenderer<CustomRatingBar, RatingBar>
    {
        CustomRatingBar element;
        RatingBar ratingBar;

        protected override void OnElementChanged(ElementChangedEventArgs<CustomRatingBar> e)
        {
            if (e.NewElement == null)
                return;
            element = Element as CustomRatingBar;
            base.OnElementChanged(e);
            if (e.NewElement == null || Control != null)
                return;
            SetStars();
        }

        public void SetStars()
        {
            ratingBar = new RatingBar(Forms.Context, null, element.IsSmallStyle ? Android.Resource.Attribute.RatingBarStyleSmall
                           : Android.Resource.Attribute.RatingBarStyle)
            {
                StepSize = 1.0f,
                NumStars = element.MaxStars,
                Rating = element.Rating,
            };

            RegisterEvents(element.IsReadonly);

            LayerDrawable drawable = (LayerDrawable)ratingBar.ProgressDrawable;
            SetDefaultColor(drawable);
            SetFillColor(drawable);

            SetNativeControl(ratingBar);
        }

        private void RegisterEvents(bool isReadOnly)
        {
            ratingBar.RatingBarChange -= Control_RatingBarChange;
            if (!isReadOnly)
            {
                ratingBar.RatingBarChange += Control_RatingBarChange;
            }
        }

        private void SetFillColor(LayerDrawable drawable)
        {
            if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop)
            {
                Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTint(drawable, element.GetFillColor().ToAndroid());
            }
            else
            {
                drawable.GetDrawable(2).SetColorFilter(element.GetFillColor().ToAndroid(),
                 Android.Graphics.PorterDuff.Mode.SrcAtop);
            }
        }

        private void SetDefaultColor(LayerDrawable drawable)
        {
            drawable.GetDrawable(0).SetColorFilter(Android.Graphics.Color.Gray,
                    Android.Graphics.PorterDuff.Mode.SrcAtop); 
        }


        private void Control_RatingBarChange(object sender, RatingBar.RatingBarChangeEventArgs e)
        {
            element.OnRatingChanged(e.Rating);
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName.Equals(CustomRatingBar.RatingProperty.PropertyName))
            {
                RegisterEvents(false);
                ratingBar.Rating = element.Rating;
                RegisterEvents(element.IsReadonly);
                SetFillColor((LayerDrawable)ratingBar.ProgressDrawable);
            }
        }
    }
}

Step 5 - For Android- Now creating a renderer for iOS for rating bar as well.

[assembly: ExportRenderer(typeof(CustomRatingBar), typeof(RatingBarRenderer))]

namespace RatingBarDemo.iOS.Renderers
{
    class RatingBarRenderer:ViewRenderer<CustomRatingBar, UIView>
    {
        const string IconStarBlank = "ic_star_blank.png";
        const string IconStarYellow = "ic_star_yellow.png";
        const string IconStarGreen = "ic_star_green.png";
        const string IconStarOrange = "ic_star_orange.png";
        const string IconStarRed = "ic_star_red.png";
 
        UIView rateView;
        UIButton oneStar, twoStar, threeStar, fourStar, fiveStar;
        float starSize;
        CustomRatingBar element;


        protected override void OnElementChanged(ElementChangedEventArgs<CustomRatingBar> e)
        {
            base.OnElementChanged(e);
            element = Element as CustomRatingBar;
            if (element == null)
                return;
            InitializeButton();
            SetTouchEvents(element.IsReadonly);

            rateView = new UIView()
            {
                
            };
            if (element.IsSmallStyle)
            {
                starSize = 15;
                rateView.Frame = new CGRect(0, 0, 95, 15);
                SetRatingBarSmall();
                AddStarInView();
                if (element.Rating >= 0)
                    ShowRatingBar();
            }
            else
            {
                starSize = 30;
                rateView.Frame = new CGRect(0, 0, 170, 30);
                SetRatingBarDefault();
                AddStarInView();
                ShowRatingBar();
            }
            SetNativeControl(rateView);
            var tapGesture = new UITapGestureRecognizer(OnRateViewTapped);
            rateView.AddGestureRecognizer(tapGesture);
        }

        private void OnRateViewTapped()
        {
            element.OnTapped();
        }

        protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if (e.PropertyName.Equals(CustomRatingBar.RatingProperty.PropertyName))
            {
                ShowRatingBar();
            }
            else if (e.PropertyName.Equals(CustomRatingBar.IsReadonlyProperty.PropertyName))
            {
                SetTouchEvents(element.IsReadonly);
            }
        }

        // rating bar bind with value;
        private void ShowRatingBar()
        {
            if (Element.Rating <= 0)
                SetBlankStarRating();
            else if (Element.Rating <= 1)
                SetOneStarRating();
            else if (Element.Rating <= 2)
                SetTwoStarRating();
            else if (Element.Rating <= 3)
                SetThreeStarRating();
            else if (Element.Rating <= 4)
                SetFourStarRating();
            else if (Element.Rating <= 5)
                SetFiveStarRating();
        }

        // button initialize
        private void InitializeButton()
        {
            oneStar = UIButton.FromType(UIButtonType.Custom);
            oneStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);

            twoStar = UIButton.FromType(UIButtonType.Custom);
            twoStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);

            threeStar = UIButton.FromType(UIButtonType.Custom);
            threeStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);

            fourStar = UIButton.FromType(UIButtonType.Custom);
            fourStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);

            fiveStar = UIButton.FromType(UIButtonType.Custom);
            fiveStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
        }

        void OneStar_TouchUpInside(object sender, EventArgs e)
        {
            element.OnRatingChanged(1);
        }
        void TwoStar_TouchUpInside(object sender, EventArgs e)
        {
            element.OnRatingChanged(2);
        }
        void ThreeStar_TouchUpInside(object sender, EventArgs e)
        {
            element.OnRatingChanged(3);
        }
        void FourStar_TouchUpInside(object sender, EventArgs e)
        {
            element.OnRatingChanged(4);
        }
        void FiveStar_TouchUpInside(object sender, EventArgs e)
        {
            element.OnRatingChanged(5);
        }

        private void SetTouchEvents(bool isReadOnly)
        {
            oneStar.TouchUpInside -= OneStar_TouchUpInside;
            twoStar.TouchUpInside -= TwoStar_TouchUpInside;
            threeStar.TouchUpInside -= ThreeStar_TouchUpInside;
            fourStar.TouchUpInside -= FourStar_TouchUpInside;
            fiveStar.TouchUpInside -= FiveStar_TouchUpInside;
            if (isReadOnly == false)
            {
                oneStar.TouchUpInside += OneStar_TouchUpInside;
                twoStar.TouchUpInside += TwoStar_TouchUpInside;
                threeStar.TouchUpInside += ThreeStar_TouchUpInside;
                fourStar.TouchUpInside += FourStar_TouchUpInside;
                fiveStar.TouchUpInside += FiveStar_TouchUpInside;
            }
        }


        // rating bar size
        private void SetRatingBarSmall()
        {
            //int x = (int)(App.ScreenSize.Width - ratingBarWidth) / 2;
            int x = 0;
            oneStar.Frame = new CGRect(x, 0, starSize, starSize);
            twoStar.Frame = new CGRect(x = x + 20, 0, starSize, starSize);
            threeStar.Frame = new CGRect(x = x + 20, 0, starSize, starSize);
            fourStar.Frame = new CGRect(x = x + 20, 0, starSize, starSize);
            fiveStar.Frame = new CGRect(x = x + 20, 0, starSize, starSize);
        }

        private void SetRatingBarDefault()
        {
            //int x = (int)(App.ScreenSize.Width - ratingBarWidth) / 2;
            int x = 0;
            oneStar.Frame = new CGRect(x, 0, starSize, starSize);
            twoStar.Frame = new CGRect(x = x + 35, 0, starSize, starSize);
            threeStar.Frame = new CGRect(x = x + 35, 0, starSize, starSize);
            fourStar.Frame = new CGRect(x = x + 35, 0, starSize, starSize);
            fiveStar.Frame = new CGRect(x = x + 35, 0, starSize, starSize);
        }

        private void AddStarInView()
        {
            rateView.AddSubview(oneStar);
            rateView.AddSubview(twoStar);
            rateView.AddSubview(threeStar);
            rateView.AddSubview(fourStar);
            rateView.AddSubview(fiveStar);
        }

        private void SetBlankStarRating()
        {
            oneStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            twoStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            threeStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fourStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fiveStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
        }


        private void SetOneStarRating()
        {
            oneStar.SetImage(UIImage.FromFile(IconStarRed), UIControlState.Normal);
            twoStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            threeStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fourStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fiveStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
        }

        private void SetTwoStarRating()
        {
            oneStar.SetImage(UIImage.FromFile(IconStarRed), UIControlState.Normal);
            twoStar.SetImage(UIImage.FromFile(IconStarRed), UIControlState.Normal);
            threeStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fourStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fiveStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
        }

        private void SetThreeStarRating()
        {
            oneStar.SetImage(UIImage.FromFile(IconStarOrange), UIControlState.Normal);
            twoStar.SetImage(UIImage.FromFile(IconStarOrange), UIControlState.Normal);
            threeStar.SetImage(UIImage.FromFile(IconStarOrange), UIControlState.Normal);
            fourStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
            fiveStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
        }

        private void SetFourStarRating()
        {
            oneStar.SetImage(UIImage.FromFile(IconStarYellow), UIControlState.Normal);
            twoStar.SetImage(UIImage.FromFile(IconStarYellow), UIControlState.Normal);
            threeStar.SetImage(UIImage.FromFile(IconStarYellow), UIControlState.Normal);
            fourStar.SetImage(UIImage.FromFile(IconStarYellow), UIControlState.Normal);
            fiveStar.SetImage(UIImage.FromFile(IconStarBlank), UIControlState.Normal);
        }

        private void SetFiveStarRating()
        {
            oneStar.SetImage(UIImage.FromFile(IconStarGreen), UIControlState.Normal);
            twoStar.SetImage(UIImage.FromFile(IconStarGreen), UIControlState.Normal);
            threeStar.SetImage(UIImage.FromFile(IconStarGreen), UIControlState.Normal);
            fourStar.SetImage(UIImage.FromFile(IconStarGreen), UIControlState.Normal);
            fiveStar.SetImage(UIImage.FromFile(IconStarGreen), UIControlState.Normal);
        }
    }
}
Step 6 - Now u can use the rating bar in XAML files as:



<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     x:Class="RatingBarDemo.Views.RatingBarPage"
      xmlns:uc="clr-namespace:RatingBarDemo.Controls" >
      <uc:CustomRatingBar x:Name="customRatingBar" MaxStars="5"
RatingChanged="OnRatingChanged" IsReadonly="false" />
      </ContentPage>


Note- This custom Rating Bar having following features-

     1- This Control having property to set it size small or large.
            2- You can set a maximum number of stars.
            3- You can change rating bar colour.
            4- These all facilities in both Android and iOS also.


Find the Sample for the same here.