by Baris Ceviz


Posted on Wednesday 26 April 2017


Xamarin Forms Radial Progress Kullanımı

Selamlar,

Bu yazımda Manisa'da düzenlenen #HackWithMVP hackathonunda geliştirdiğimiz uygulamada kullandığımız bir Component'in Xamarin.Forms ile nasıl kullanacağınızdan bahsedeceğim. Uygulamanızda bir Progress göstermek istediğinizde güzel bir UI ile gösterebileceğiniz bir kontrolü XAML içerisinde kullanabileceğiniz hale getirmeniz için ne yapabilirsiniz hemen ona bakalım.

Öncelikle Radial Progress componentini edinelim. İsterseniz siteden indirip referans edebilirsiniz fakat tavsiyem Visual Studio üzerinden indirip eklemenizdir. Bunun için ne yapmanız gerektiğini resimlerle anlatacağım.

Xamarin Forms Radial Progress

Android projesinde Components e sağ tıklayıp Get More Components e tıklıyoruz. Eğer Xamarin Account unuz ile giriş yapmadıysanız size bir hesap giriş sayfası gelecektir. Xamarin hesabınız ile giriş yaparak devam edelim ve Ardından Components sayfası gelecektir. Bu sayfada arama bölümüne Radial Progress yazalım. Ardından listede Radial Progress ı seçelim.

Xamarin Forms Radial Progress

Seçtikten sonra karşımıza gelecek Radial Progress sayfasının detaylarında Add to App i seçelim ve Android projemize ekleyelim. Eğer bu component i iOS üzerinde de kullanacaksanız aynı işlemleri iOS projesine de yapalım. Bu işlemleri tamamladıktan sonra artık kod yazma zamanı :)

Xamarin Forms Radial Progress

Portable Class Library projesine bir Controls klasörü oluşturup içerisine RadialProgressControl class ı oluşturalım. İçerisine kullanacağımız bindableproperty leri tanımlayalım. Böylelikle XAML tarafında data bind edebiliriz.

    public class RadialProgressControl : View
    {
        #region BindableProperty Variables

        public static readonly BindableProperty MinimumProperty = BindableProperty.Create(nameof(Minimum), typeof(float), typeof(RadialProgressControl), default(float));
        public static readonly BindableProperty MaximumProperty = BindableProperty.Create(nameof(Maximum), typeof(float), typeof(RadialProgressControl), default(float));
        public static readonly BindableProperty ValueProperty = BindableProperty.Create(nameof(Value), typeof(float), typeof(RadialProgressControl), default(float));
        public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(nameof(ProgressColor), typeof(Color), typeof(RadialProgressControl), default(Color));
        public static readonly BindableProperty LabelHiddenProperty = BindableProperty.Create(nameof(LabelHidden), typeof(Boolean), typeof(RadialProgressControl), default(Boolean));
        public static readonly BindableProperty IsDoneProperty = BindableProperty.Create(nameof(IsDone), typeof(Boolean), typeof(RadialProgressControl), default(Boolean));

        #endregion

        #region Properties

        public float Minimum
        {
            get
            {
                return (float)GetValue(MinimumProperty);
            }
            set
            {
                SetValue(MinimumProperty, value);
            }
        }
        public float Maximum
        {
            get
            {
                return (float)GetValue(MaximumProperty);
            }
            set
            {
                SetValue(MaximumProperty, value);
            }
        }
        public float Value
        {
            get
            {
                return (float)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }
        public Color ProgressColor
        {
            get
            {
                return (Color)GetValue(ProgressColorProperty);
            }
            set
            {
                SetValue(ProgressColorProperty, value);
            }
        }
        public bool LabelHidden
        {
            get { return (Boolean)GetValue(LabelHiddenProperty); }
            set { SetValue(LabelHiddenProperty, value); }
        }
        public bool IsDone
        {
            get { return (Boolean)GetValue(IsDoneProperty); }
            set { SetValue(IsDoneProperty, value); }
        }

        #endregion
    }

Class ımızı oluşturduktan sonra sıra geldi Custom Renderer yazmaya. iOS ve Android için Custom Renderer yazarak Radial Progress ı bağlayabiliriz. Android projemizin içerisine Renderers açalım ve içerisine RadialProgressControlRenderer class ı oluşturalım. Custom Renderer ı tanıması için assembly ile bu class ın Renderer olduğunu ve hangi control çağrıldığında hangi renderer çalışması gerektiğini belirtmemiz gerekir. Eğer bu işlemi yapmazsak control ü derlerken Android veya iOS Custom Renderer a girmeyecektir ve control derlenemeyecektir.

[assembly: ExportRenderer(typeof(RadialProgressControl), typeof(RadialProgressControlRenderer))]
namespace RadialProgressExample.Droid.Renderers
***

Namespace in üzerine ExportRenderer ile tanımlama yapıyorsunuz. İlk parametrede sizin hangi render edilecek kontrolünüze odaklanmanızı istiyor. İkinci parametrede ise hangi Renderer ın çalışması gerektiğini istiyor. Bu class ların namespace leri farklı olduğu için using bölümüne referans olarak ekleyebilir veya direk namespace altından çağırabilirsiniz. Şimdi Renderer class ımızı yazalım.

public class RadialProgressControlRenderer : ViewRenderer<RadialProgressControl, RadialProgressView>
    {
        RadialProgressView progressView;
        public RadialProgressControlRenderer()
        {
            progressView = new RadialProgressView(Forms.Context);
        }

        protected override void OnElementChanged(ElementChangedEventArgs<RadialProgressControl> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || this.Element == null)
                return;

            if (e.OldElement != null)
                e.OldElement.PropertyChanged -= Element_PropertyChanged;

            if (this.Element != null)
                this.Element.PropertyChanged += Element_PropertyChanged;

            var element = (RadialProgressControl)Element;

            progressView.Value = element.Value;
            progressView.MaxValue = element.Maximum;
            progressView.MinValue = element.Minimum;
            progressView.LabelHidden = element.LabelHidden;
            progressView.ProgressColor = element.ProgressColor.ToAndroid();

            SetNativeControl(progressView);
        }

        private void Element_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == RadialProgressControl.MaximumProperty.PropertyName)
            {
                progressView.MaxValue = Element.Maximum;
            }
            else if (e.PropertyName == RadialProgressControl.MinimumProperty.PropertyName)
            {
                progressView.MinValue = Element.Minimum;
            }
            else if (e.PropertyName == RadialProgressControl.ValueProperty.PropertyName)
            {
                progressView.Value = Element.Value;
            }
            else if (e.PropertyName == RadialProgressControl.LabelHiddenProperty.PropertyName)
            {
                progressView.LabelHidden = Element.LabelHidden;
            }
            else if (e.PropertyName == RadialProgressControl.ProgressColorProperty.PropertyName)
            {
                progressView.ProgressColor = Element.ProgressColor.ToAndroid();
            }
        }
    }

Öncelikle controlümüzü tanımlıyoruz. Constructor içerisinde controlümüzü tanımlayıp controlü sahiplenecek Context i tanımlıyoruz. Ardından OnElementChanged içerisinde Render edilen control e bizim tanımladığımız property değerlerini Native control e set ediyoruz. Aynı zamanda property değerleri değiştiğinde bu propertylerin değişmesi içinde Element_PropertyChanged  event i içerisinde değişen property değerlerini set ediyoruz.  Böylelikle Android tarafındaki Custom Renderer ı tamamlamış oluyoruz. Sıra geldi iOS için.

[assembly: ExportRenderer(typeof(RadialProgressControl),typeof(RadialProgressControlRenderer))]
namespace RadialProgressExample.iOS.Renderers
***

Yine aynı işlemleri iOS tarafına da yapıyoruz. Ufak değişikliklerle iOS Custom Renderer ı tamamlayalım.

public class RadialProgressControlRenderer : ViewRenderer<RadialProgressControl,RadialProgressView>
    {
        RadialProgressView progressView;
        public RadialProgressControlRenderer()
        {
            progressView = new RadialProgressView();
        }

        protected override void OnElementChanged(ElementChangedEventArgs<RadialProgressControl> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null || this.Element == null)
                return;

            if (e.OldElement != null)
                e.OldElement.PropertyChanged -= Element_PropertyChanged;

            if (this.Element != null)
                this.Element.PropertyChanged += Element_PropertyChanged; ;

            var element = (RadialProgressControl)Element;

            progressView.Value = element.Value;
            progressView.MaxValue = element.Maximum;
            progressView.MinValue = element.Minimum;
            progressView.LabelHidden = element.LabelHidden;
            progressView.ProgressColor = element.ProgressColor.ToUIColor();

            SetNativeControl(progressView);
        }

        private void Element_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == RadialProgressControl.MaximumProperty.PropertyName)
            {
                progressView.MaxValue = Element.Maximum;
            }
            else if (e.PropertyName == RadialProgressControl.MinimumProperty.PropertyName)
            {
                progressView.MinValue = Element.Minimum;
            }
            else if (e.PropertyName == RadialProgressControl.ValueProperty.PropertyName)
            {
                progressView.Value = Element.Value;
            }
            else if (e.PropertyName == RadialProgressControl.LabelHiddenProperty.PropertyName)
            {
                progressView.LabelHidden = Element.LabelHidden;
            }
            else if (e.PropertyName == RadialProgressControl.ProgressColorProperty.PropertyName)
            {
                progressView.ProgressColor = Element.ProgressColor.ToAndroid();
            }
        }
    }

Android Custom Renderer ile arasında iki tane fark var. Bunardan bir tanesi Constructor da control ü tanımlarken herhangi bir Context veya iOS için ViewController istemedi. Bir diğeri ise ProgressColor u convert ederken Android için .ToAndroid derken iOS için .ToUIColor olarak değişiklik yaptık.

Artık controlümüzü XAML sayfamıza ekleyebiliriz. Öncelikle XAML sayfamızda ContentPage e bir namespace alanı tanımlamamız gerekir. Namespace i tanımlayarak ilgili control ü tanımlayabileceğiz.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:RadialProgressExample"
             ***xmlns:controls="clr-namespace:RadialProgressExample.Controls"***
             
x:Class="RadialProgressExample.MainPage">

Yıldızlar içerisine aldığım satır bizim tanımladığımız namespace satırı. Şimdi biz controls altından RadialProgressControl ü alabiliriz.

    <controls:RadialProgressControl Minimum="0" 
                                    Maximum="100" 
                                    HorizontalOptions="CenterAndExpand"
                                    VerticalOptions="CenterAndExpand"
                                    LabelHidden="True"
                                    ProgressColor="Red"
                                    HeightRequest="100"
                                    WidthRequest="100
                                    "/>

Controlümüzü sayfaya tanımladık. Bu controlümüzü bir StackLayout içerisine koyalım. İçerisine de bir Start butonu ekleyelim ve güzel bir loading yapalım. Button ve RadialProgressControl e isim verelim.

    <StackLayout Orientation="Vertical"
                 HorizontalOptions="FillAndExpand"
                 VerticalOptions="CenterAndExpand">
        <controls:RadialProgressControl x:Name="radialProgress"
                                    Minimum="0" 
                                    Maximum="100"
                                    Value="0"
                                    HorizontalOptions="CenterAndExpand"
                                    VerticalOptions="CenterAndExpand"
                                    LabelHidden="False"
                                    ProgressColor="Red"
                                    HeightRequest="100"
                                    WidthRequest="100
                                    "/>
        <Button x:Name="btnStart"
                Text="Start"
                HorizontalOptions="CenterAndExpand"
                VerticalOptions="CenterAndExpand"
                Clicked="Button_Clicked"/>
        
    </StackLayout>

XAML sayfamızı bu şekilde hazırladık. Şimdi XAML Codebehind a yani cs dosyasına giderek butonun click event ine bir Timer ekleyelim ve loading yapalım.

        private void Button_Clicked(object sender, EventArgs e)
        {
            btnStart.IsEnabled = false;
            Device.StartTimer(TimeSpan.FromMilliseconds(100), () =>
            {
                if (radialProgress.Maximum == radialProgress.Value)
                {
                    btnStart.IsEnabled = true;
                    return false;
                }
                radialProgress.Value++;
                return true;
            });
        }

Evet butona ilk tıklandığında buttonu devre dışı bırakıyoruz. Ardından Timer ımız başlıyor. Eğer Value değeri Maximum a eşit olursa butonumuzu aktif ediyoruz ve Timer ı durduruyoruz. Eğer tam tersi ise RadialProgress ın Value sunu arttırıyoruz ve Timer devam ediyor. Böylelikle güzel bir Loading oluşturmuş olduk.

Kodları Gtihub üzerinden bulabilirsiniz arkadaşalr. Xamarin ile ilgili yaptığım tüm örnek projeler aşağıdaki XamarinSamples repomda bulabilirsiniz

https://github.com/peacecwz/XamarinSamples

Bu yazımda da benden bu kadar. Umarım yararlı bir yazı olmuştur. Bir sonraki yazı ile görüşmek üzere :)