<ActivityIndicator Color="{StaticResource PrimaryColor}" IsRunning="false" HorizontalOptions="FillAndExpand" />
</StackLayout>
- <Label Text="Custom Background Color" Margin="0,0,0,-10" />
+ <Label Text="Custom Background Color + Height 200" Margin="0,0,0,-10" />
<StackLayout Orientation="Horizontal">
- <ActivityIndicator BackgroundColor="{StaticResource SecondaryColor}" IsRunning="true" HorizontalOptions="FillAndExpand" />
- <ActivityIndicator BackgroundColor="{StaticResource SecondaryColor}" IsRunning="false" HorizontalOptions="FillAndExpand" />
+ <ActivityIndicator HeightRequest="200" BackgroundColor="{StaticResource SecondaryColor}" IsRunning="true" HorizontalOptions="FillAndExpand" />
+ <ActivityIndicator HeightRequest="200" BackgroundColor="{StaticResource SecondaryColor}" IsRunning="false" HorizontalOptions="FillAndExpand" />
</StackLayout>
- <Label Text="Custom" Margin="0,0,0,-10" />
- <StackLayout Orientation="Horizontal">
+ <Label Text="Custom + Height 144 (Max)" Margin="0,0,0,-10" />
+ <StackLayout Orientation="Horizontal" BackgroundColor="Lime">
+ <ActivityIndicator HeightRequest="144" Color="{StaticResource PrimaryColor}" BackgroundColor="{StaticResource SecondaryColor}" IsRunning="true" HorizontalOptions="FillAndExpand" />
+ <ActivityIndicator HeightRequest="144" Color="{StaticResource PrimaryColor}" BackgroundColor="{StaticResource SecondaryColor}" IsRunning="false" HorizontalOptions="FillAndExpand" />
+ </StackLayout>
+
+ <Label Text="Default size" Margin="0,0,0,-10" />
+ <StackLayout Orientation="Horizontal" BackgroundColor="Yellow">
<ActivityIndicator Color="{StaticResource PrimaryColor}" BackgroundColor="{StaticResource SecondaryColor}" IsRunning="true" HorizontalOptions="FillAndExpand" />
<ActivityIndicator Color="{StaticResource PrimaryColor}" BackgroundColor="{StaticResource SecondaryColor}" IsRunning="false" HorizontalOptions="FillAndExpand" />
</StackLayout>
-
<Label Text="Progress Bars" FontSize="Large" />
<Label Text="Animating" Margin="0,0,0,-10" />
new GalleryPageFactory(() => new PickerCoreGalleryPage(), "Picker Gallery"),
new GalleryPageFactory(() => new ProgressBarCoreGalleryPage(), "ProgressBar Gallery"),
new GalleryPageFactory(() => new MaterialProgressBarGallery(), "[Material] ProgressBar & Slider Gallery"),
+ new GalleryPageFactory(() => new MaterialActivityIndicatorGallery(), "[Material] ActivityIndicator Gallery"),
new GalleryPageFactory(() => new ScrollGallery(), "ScrollView Gallery"),
new GalleryPageFactory(() => new ScrollGallery(ScrollOrientation.Horizontal), "ScrollView Gallery Horizontal"),
new GalleryPageFactory(() => new ScrollGallery(ScrollOrientation.Both), "ScrollView Gallery 2D"),
--- /dev/null
+namespace Xamarin.Forms.Controls
+{
+ public class MaterialActivityIndicatorGallery : ContentPage
+ {
+ public MaterialActivityIndicatorGallery()
+ {
+ Visual = VisualMarker.Material;
+
+ var activityIndicator = new ActivityIndicator()
+ {
+ IsRunning = false,
+ BackgroundColor = Color.Red,
+ HeightRequest = 50
+ };
+
+ var IsRunGrid = new Grid
+ {
+ Padding = 0,
+ ColumnSpacing = 6,
+ RowSpacing = 6,
+ ColumnDefinitions =
+ {
+ new ColumnDefinition { Width = GridLength.Star },
+ new ColumnDefinition { Width = 50 }
+ }
+ };
+
+ IsRunGrid.AddChild(new Label { Text = "Is Running" }, 0, 0);
+ var isRunSwitch = new Switch {
+ IsToggled = activityIndicator.IsRunning,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center
+ };
+ isRunSwitch.Toggled += (_, e) => activityIndicator.IsRunning = e.Value;
+ IsRunGrid.AddChild(isRunSwitch, 1, 0);
+
+ var primaryPicker = new ColorPicker { Title = "Primary Color", Color = activityIndicator.Color };
+ primaryPicker.ColorPicked += (_, e) =>
+ {
+ activityIndicator.Color = e.Color;
+ };
+ var backgroundPicker = new ColorPicker { Title = "Background Color", Color = activityIndicator.BackgroundColor };
+ backgroundPicker.ColorPicked += (_, e) => activityIndicator.BackgroundColor = e.Color;
+
+ var heightPicker = MaterialProgressBarGallery.CreateValuePicker("Height", value => activityIndicator.HeightRequest = value);
+
+ var content = new StackLayout
+ {
+ Children = { activityIndicator },
+ BackgroundColor = Color.Blue
+ };
+
+ var backgroundPanelPicker = new ColorPicker { Title = "Back panel Color", Color = content.BackgroundColor };
+ backgroundPanelPicker.ColorPicked += (_, e) => content.BackgroundColor = e.Color;
+
+ Content = new StackLayout
+ {
+ Padding = 10,
+ Spacing = 10,
+ Children =
+ {
+ new ScrollView
+ {
+ Margin = new Thickness(-10, 0),
+ Content = new StackLayout
+ {
+ Padding = 10,
+ Spacing = 10,
+ Children =
+ {
+ IsRunGrid,
+ primaryPicker,
+ backgroundPicker,
+ backgroundPanelPicker,
+ heightPicker,
+ }
+ }
+ },
+
+ new BoxView
+ {
+ HeightRequest = 1,
+ Margin = new Thickness(-10, 0),
+ Color = Color.Black
+ },
+
+ content
+ }
+ };
+ }
+ }
+}
};
}
- Grid CreateValuePicker(string title, Action<double> changed)
+ internal static Grid CreateValuePicker(string title, Action<double> changed)
{
// 50%
Slider slider = new Slider(0, 100, 50);
+using System;
using System.ComponentModel;
using CoreAnimation;
using CoreGraphics;
{
public class MaterialActivityIndicatorRenderer : ViewRenderer<ActivityIndicator, MActivityIndicator>
{
+ // by Material spec the stroke width is 1/12 of the diameter,
+ // but Android's native progress indicator is 1/10 of the diameter.
+ const float _strokeRatio = 10;
+ const float _defaultRadius = 22;
+ const float _defaultStrokeWidth = 4;
+ const float _defaultSize = 2 * _defaultRadius + _defaultStrokeWidth;
+
SemanticColorScheme _defaultColorScheme;
SemanticColorScheme _colorScheme;
CAShapeLayer _backgroundLayer;
- CGPoint _center;
public MaterialActivityIndicatorRenderer()
{
_backgroundLayer = new CAShapeLayer
{
- LineWidth = 4,
+ LineWidth = Control.StrokeWidth,
FillColor = UIColor.Clear.CGColor,
Hidden = true
};
UpdateColor();
UpdateIsRunning();
+ SetBackgroundColor(Element.BackgroundColor);
ApplyTheme();
}
return new MActivityIndicator
{
IndicatorMode = ActivityIndicatorMode.Indeterminate,
- StrokeWidth = 4,
- Radius = 24
+ StrokeWidth = _defaultStrokeWidth,
+ Radius = _defaultRadius
};
}
{
base.LayoutSubviews();
- if (_center != Control.Center)
- {
- _center = Control.Center;
- _backgroundLayer.Path = UIBezierPath.FromArc(_center, Control.Radius - 2, 0, 360, true).CGPath;
- }
- SetBackgroundColor(Element.BackgroundColor);
+ // try get the radius for this size
+ var min = NMath.Min(Control.Bounds.Width, Control.Bounds.Height);
+ var stroke = min / _strokeRatio;
+ var radius = min / 2;
+
+ // but, in the end use the limit set by the control
+ Control.Radius = radius;
+ Control.StrokeWidth = Control.Radius / (_strokeRatio / 2);
+
+ _backgroundLayer.LineWidth = Control.StrokeWidth;
+ _backgroundLayer.Path = UIBezierPath.FromArc(Control.Center, Control.Radius - Control.StrokeWidth / 2, 0, 360, true).CGPath;
+ }
+
+ public override CGSize SizeThatFits(CGSize size)
+ {
+ if (nfloat.IsInfinity(size.Width))
+ size.Width = _defaultSize;
+ if (nfloat.IsInfinity(size.Height))
+ size.Height = _defaultSize;
+ var min = NMath.Min(size.Width, size.Height);
+ size.Width = size.Height = min;
+ return size;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
using System;
using System.ComponentModel;
using Android.Content;
-using Android.Content.Res;
-using Android.Graphics.Drawables;
using Android.Support.V4.View;
using Android.Views;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android.FastRenderers;
using Xamarin.Forms.Platform.Android.Material;
-using AColor = Android.Graphics.Color;
using AProgressBar = Android.Widget.ProgressBar;
using AView = Android.Views.View;
bool _disposed;
ActivityIndicator _element;
- AProgressBar _control;
+ CircularProgress _control;
VisualElementTracker _visualElementTracker;
VisualElementRenderer _visualElementRenderer;
{
VisualElement.VerifyVisualFlagEnabled();
- _control = new AProgressBar(new ContextThemeWrapper(context, Resource.Style.XamarinFormsMaterialProgressBarCircular), null, Resource.Style.XamarinFormsMaterialProgressBarCircular);
- _control.Indeterminate = true;
- AddView(_control);
+ _control = new CircularProgress(new ContextThemeWrapper(context, Resource.Style.XamarinFormsMaterialProgressBarCircular), null, Resource.Style.XamarinFormsMaterialProgressBarCircular)
+ {
+ // limiting size to compare iOS realization
+ // https://github.com/material-components/material-components-ios/blob/develop/components/ActivityIndicator/src/MDCActivityIndicator.m#L425
+ MinSize = (int)Context.ToPixels(10),
+ MaxSize = (int)Context.ToPixels(144),
+ DefaultColor = MaterialColors.Light.PrimaryColor
+ };
+ AddView(Control);
_visualElementRenderer = new VisualElementRenderer(this);
_motionEventHelper = new MotionEventHelper();
e.NewElement.PropertyChanged += OnElementPropertyChanged;
- UpdateColorsAndRuning();
+ UpdateColor();
+ UpdateBackgroundColor();
+ UpdateIsRunning();
ElevationHelper.SetElevation(this, e.NewElement);
}
{
ElementPropertyChanged?.Invoke(this, e);
- if (e.IsOneOf(ActivityIndicator.IsRunningProperty, ActivityIndicator.ColorProperty, VisualElement.BackgroundColorProperty))
- UpdateColorsAndRuning();
+ if (e.Is(ActivityIndicator.IsRunningProperty))
+ UpdateIsRunning();
+ else if (e.Is(ActivityIndicator.ColorProperty))
+ UpdateColor();
+ else if (e.Is(VisualElement.BackgroundColorProperty))
+ UpdateBackgroundColor();
}
public override bool OnTouchEvent(MotionEvent e)
return _motionEventHelper.HandleMotionEvent(Parent, e);
}
- void UpdateColorsAndRuning()
+ void UpdateIsRunning()
{
- if (Element == null || Control == null)
- return;
+ if (Element != null && _control != null)
+ _control.IsRunning = Element.IsRunning;
+ }
- var background = Element.BackgroundColor.IsDefault
- ? AColor.Transparent
- : Element.BackgroundColor.ToAndroid();
- (_control.Background as GradientDrawable)?.SetColor(background);
+ void UpdateColor()
+ {
+ if (Element != null && _control != null)
+ _control.SetColor(Element.Color);
+ }
- if (Element.IsRunning)
- {
- var progress = Element.Color.IsDefault
- ? MaterialColors.Light.PrimaryColor
- : Element.Color.ToAndroid();
- _control.IndeterminateTintList = ColorStateList.ValueOf(progress);
- }
- else
- {
- _control.Visibility = Element.BackgroundColor.IsDefault
- ? ViewStates.Gone
- : ViewStates.Visible;
- _control.IndeterminateTintList = ColorStateList.ValueOf(AColor.Transparent);
- }
+ void UpdateBackgroundColor()
+ {
+ if (Element != null && _control != null)
+ _control.SetBackgroundColor(Element.BackgroundColor);
}
// IVisualElementRenderer
--- /dev/null
+using System;
+using Android.Content;
+using Android.OS;
+using Android.Util;
+using AProgressBar = Android.Widget.ProgressBar;
+using Android.Graphics.Drawables;
+using AColor = Android.Graphics.Color;
+using Android.Content.Res;
+using Android.Views;
+using Android.Graphics;
+
+namespace Xamarin.Forms.Platform.Android
+{
+ internal class CircularProgress : AProgressBar
+ {
+ public int MaxSize { get; set; } = int.MaxValue;
+
+ public int MinSize { get; set; } = 0;
+
+ public AColor DefaultColor { get; set; }
+
+ const int _paddingRatio = 10;
+
+ const int _paddingRatio23 = 14;
+
+ bool _isRunning;
+
+ AColor _backgroudColor;
+
+ public CircularProgress(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
+ {
+ Indeterminate = true;
+ }
+
+ public override void Draw(Canvas canvas)
+ {
+ base.Draw(canvas);
+ if (_isRunning != IsRunning)
+ IsRunning = _isRunning;
+ }
+
+ public void SetColor(Color color)
+ {
+ var progress = color.IsDefault ? DefaultColor : color.ToAndroid();
+ IndeterminateTintList = ColorStateList.ValueOf(progress);
+ }
+
+ public void SetBackgroundColor(Color color)
+ {
+ _backgroudColor = color.IsDefault ? AColor.Transparent : color.ToAndroid();
+ (Background as GradientDrawable)?.SetColor(_backgroudColor);
+ }
+
+ AnimatedVectorDrawable CurrentDrawable => IndeterminateDrawable.Current as AnimatedVectorDrawable;
+
+ public bool IsRunning
+ {
+ get => CurrentDrawable?.IsRunning ?? false;
+ set
+ {
+ if (CurrentDrawable == null)
+ return;
+
+ _isRunning = value;
+ if (_isRunning && !CurrentDrawable.IsRunning)
+ CurrentDrawable.Start();
+ else if (CurrentDrawable.IsRunning)
+ CurrentDrawable.Stop();
+
+ PostInvalidate();
+ }
+ }
+
+ public override void Layout(int l, int t, int r, int b)
+ {
+ var width = r - l;
+ var height = b - t;
+ var squareSize = Math.Min(Math.Max(Math.Min(width, height), MinSize), MaxSize);
+ l += (width - squareSize) / 2;
+ t += (height - squareSize) / 2;
+ int strokeWidth;
+ if (Build.VERSION.SdkInt < BuildVersionCodes.N)
+ strokeWidth = squareSize / _paddingRatio23;
+ else
+ strokeWidth = squareSize / _paddingRatio;
+
+ squareSize += strokeWidth;
+ base.Layout(l - strokeWidth, t - strokeWidth, l + squareSize, t + squareSize);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- thickness = width / thicknessRatio = 48 / 12 = 4
+ thicknessRatio = width / thickness = 48 / 4 = 12 -->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="ring"
+ android:thicknessRatio="12"
+ android:innerRadiusRatio="3"
+ android:useLevel="false">
+ <solid android:color="#000" />
+</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
-<!-- innerRadius = (diameter / 2) - (thickness * 2) = 16dp -->
+<!-- API23 and less
+magic innerRadiusRatio = 90% pi -->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
- android:innerRadius="16dp"
- android:thickness="4dp"
+ android:thicknessRatio="12"
+ android:innerRadiusRatio="2.8274333882308139146163790449516"
android:useLevel="false">
<solid android:color="#000" />
</shape>
\ No newline at end of file
<Compile Include="Material\MaterialEntryRenderer.cs" />
<Compile Include="Material\MaterialProgressBarRenderer.cs" />
<Compile Include="IPickerRenderer.cs" />
+ <Compile Include="Renderers\CircularProgress.cs" />
<Compile Include="Renderers\PickerEditText.cs" />
<Compile Include="Renderers\FontImageSourceHandler.cs" />
<Compile Include="Renderers\FormsWebViewClient.cs" />
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
- <ItemGroup />
<ItemGroup>
<AndroidResource Include="Resources\drawable\MaterialActivityIndicatorBackground.xml">
+ <Generator>MSBuild:UpdateGeneratedFiles</Generator>
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
- <AndroidResource Include="Resources\drawable\MaterialProgressBar.xml">
+ <AndroidResource Include="Resources\drawable-v24\MaterialActivityIndicatorBackground.xml">
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
- <Folder Include="Resources\color\" />
+ <AndroidResource Include="Resources\drawable\MaterialProgressBar.xml">
+ <SubType>Designer</SubType>
+ </AndroidResource>
</ItemGroup>
+ <ItemGroup />
<Target Name="BeforeBuild" Condition=" '$(CreateAllAndroidTargets)' == 'true' ">
<!-- create 8.1 binaries-->
<MSBuild Targets="Restore" Projects="@(ProjectToBuild)">