diff --git a/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj b/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj index e381520..b12e500 100644 --- a/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj +++ b/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj @@ -27,6 +27,7 @@ 4 None + 8.0 true @@ -39,6 +40,7 @@ true false 1591 + 8.0 @@ -207,6 +209,7 @@ + diff --git a/AiForms.Effects.Droid/GradientPlatformEffect.cs b/AiForms.Effects.Droid/GradientPlatformEffect.cs new file mode 100644 index 0000000..9f20175 --- /dev/null +++ b/AiForms.Effects.Droid/GradientPlatformEffect.cs @@ -0,0 +1,105 @@ +using System; +using System.ComponentModel; +using System.Linq; +using AiForms.Effects; +using AiForms.Effects.Droid; +using Android.Graphics.Drawables; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportEffect(typeof(GradientPlatformEffect), nameof(Gradient))] +namespace AiForms.Effects.Droid +{ + public class GradientPlatformEffect:AiEffectBase + { + Android.Views.View _view; + GradientDrawable _gradient; + Drawable _orgDrawable; + + protected override void OnAttachedOverride() + { + _view = Container ?? Control; + + _gradient = new GradientDrawable(); + _orgDrawable = _view.Background; + + UpdateGradient(); + } + + protected override void OnDetachedOverride() + { + if(!IsDisposed) + { + _view.Background = _orgDrawable; + _view.ClipToOutline = false; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); + } + + _gradient?.Dispose(); + _gradient = null; + _view = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); + } + + protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) + { + base.OnElementPropertyChanged(args); + + if (!IsSupportedByApi) + return; + + if (IsDisposed) + { + return; + } + + if (args.PropertyName == Gradient.ColorsProperty.PropertyName || + args.PropertyName == Gradient.OrientationProperty.PropertyName) + { + UpdateGradient(); + } + } + + void UpdateGradient() + { + var colors = Gradient.GetColors(Element); + if(colors == null) + { + return; + } + + _gradient.SetColors(colors.Select(x => (int)x.ToAndroid()).ToArray()); + _gradient.SetOrientation(ConvertOrientation()); + + _view.ClipToOutline = true; //not to overflow children + _view.SetBackground(_gradient); + } + + GradientDrawable.Orientation ConvertOrientation() + { + var orientation = Gradient.GetOrientation(Element); + + switch (orientation) + { + case GradientOrientation.LeftRight: + return GradientDrawable.Orientation.LeftRight; + case GradientOrientation.BlTr: + return GradientDrawable.Orientation.BlTr; + case GradientOrientation.BottomTop: + return GradientDrawable.Orientation.BottomTop; + case GradientOrientation.BrTl: + return GradientDrawable.Orientation.BrTl; + case GradientOrientation.RightLeft: + return GradientDrawable.Orientation.RightLeft; + case GradientOrientation.TrBl: + return GradientDrawable.Orientation.TrBl; + case GradientOrientation.TopBottom: + return GradientDrawable.Orientation.TopBottom; + case GradientOrientation.TlBr: + return GradientDrawable.Orientation.TlBr; + default: + return GradientDrawable.Orientation.LeftRight; + } + } + } +} diff --git a/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj b/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj index c1e20d6..fbfa80c 100644 --- a/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj +++ b/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj @@ -28,6 +28,7 @@ + 8.0 true @@ -43,6 +44,7 @@ 1591 + 8.0 @@ -89,6 +91,7 @@ + diff --git a/AiForms.Effects.iOS/GradientPlatformEffect.cs b/AiForms.Effects.iOS/GradientPlatformEffect.cs new file mode 100644 index 0000000..006d2fe --- /dev/null +++ b/AiForms.Effects.iOS/GradientPlatformEffect.cs @@ -0,0 +1,125 @@ +using System; +using System.ComponentModel; +using System.Linq; +using AiForms.Effects; +using AiForms.Effects.iOS; +using CoreAnimation; +using CoreGraphics; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportEffect(typeof(GradientPlatformEffect), nameof(Gradient))] +namespace AiForms.Effects.iOS +{ + public class GradientPlatformEffect:PlatformEffect + { + UIView _view; + VisualElement _visualElement; + CAGradientLayer _layer; + bool _clipsToBounds; + + protected override void OnAttached() + { + _view = Control ?? Container; + _visualElement = Element as VisualElement; + if(_visualElement == null) + { + Device.BeginInvokeOnMainThread(() => Gradient.SetOn(Element, false)); + return; + } + + if (Element is Label) + { + _view = Container; + } + + _clipsToBounds = _view.ClipsToBounds; + _visualElement.SizeChanged += OnSizeChanged; + UpdateGradient(); + } + + protected override void OnDetached() + { + if(_visualElement != null) + { + _visualElement.SizeChanged -= OnSizeChanged; + } + + if(_view != null) + { + _view.ClipsToBounds = _clipsToBounds; + } + + _layer?.RemoveFromSuperLayer(); + _layer?.Dispose(); + _layer = null; + + _view = null; + _visualElement = null; + + System.Diagnostics.Debug.WriteLine($"Detached {GetType().Name} from {Element.GetType().FullName}"); + } + + protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) + { + base.OnElementPropertyChanged(args); + if(args.PropertyName == Gradient.ColorsProperty.PropertyName || + args.PropertyName == Gradient.OrientationProperty.PropertyName) + { + UpdateGradient(); + } + } + + private void OnSizeChanged(object sender, EventArgs e) + { + UpdateGradient(); + } + + void UpdateGradient() + { + var colors = Gradient.GetColors(Element); + if(colors == null) + { + return; + } + + _layer?.RemoveFromSuperLayer(); + _layer?.Dispose(); + + _layer = new CAGradientLayer(); + _layer.Frame = new CGRect(0, 0, _visualElement.Width, _visualElement.Height); + _layer.Colors = colors.Select(x => x.ToCGColor()).ToArray(); + (_layer.StartPoint,_layer.EndPoint) = ConvertPoint(); + _view.Layer.InsertSublayer(_layer, 0); + _view.ClipsToBounds = true; + } + + (CGPoint start,CGPoint end) ConvertPoint() + { + var orientation = Gradient.GetOrientation(Element); + + switch(orientation) + { + case GradientOrientation.LeftRight: + return (new CGPoint(0, 0.5), new CGPoint(1, 0.5)); + case GradientOrientation.BlTr: + return (new CGPoint(0, 1.0), new CGPoint(1, 0)); + case GradientOrientation.BottomTop: + return (new CGPoint(0.5, 1), new CGPoint(0.5, 0)); + case GradientOrientation.BrTl: + return (new CGPoint(1, 1), new CGPoint(0, 0)); + case GradientOrientation.RightLeft: + return (new CGPoint(1, 0.5), new CGPoint(0, 0.5)); + case GradientOrientation.TrBl: + return (new CGPoint(1, 0), new CGPoint(0, 1)); + case GradientOrientation.TopBottom: + return (new CGPoint(0.5, 0), new CGPoint(0.5, 1)); + case GradientOrientation.TlBr: + return (new CGPoint(0, 0), new CGPoint(1, 1)); + default: + return (new CGPoint(0, 0.5), new CGPoint(1, 0.5)); + } + } + } +} diff --git a/AiForms.Effects/AiForms.Effects.csproj b/AiForms.Effects/AiForms.Effects.csproj index 3a452e6..d12e02a 100644 --- a/AiForms.Effects/AiForms.Effects.csproj +++ b/AiForms.Effects/AiForms.Effects.csproj @@ -2,13 +2,17 @@ netstandard2.0 - 1.1.2 + 0.0.5-pre true bin\Release\netstandard2.0\AiForms.Effects.xml 1701;1702;1591 + 8.0 + + + 8.0 diff --git a/AiForms.Effects/Gradient.cs b/AiForms.Effects/Gradient.cs new file mode 100644 index 0000000..d3e247c --- /dev/null +++ b/AiForms.Effects/Gradient.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace AiForms.Effects +{ + /// + /// GradientOrientation + /// + [TypeConverter(typeof(GradientOrientationTypeConverter))] + public enum GradientOrientation + { + /// + /// Left to Right + /// + LeftRight = 0, + /// + /// Bottom Left to Top Right + /// + BlTr = 45, + /// + /// Bottom to Top + /// + BottomTop = 90, + /// + /// Bottom Right to Top Left + /// + BrTl = 135, + /// + /// Right to Left + /// + RightLeft = 180, + /// + /// Top Right to Bottom Left + /// + TrBl = 225, + /// + /// Top to Bottom + /// + TopBottom = 270, + /// + /// Top Left to Bottom Right + /// + TlBr = 315, + } + + /// + /// GradientOrientationTypeConverter + /// + [TypeConversion(typeof(GradientOrientation))] + public class GradientOrientationTypeConverter:TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + if (Enum.TryParse(value, true, out GradientOrientation orientation)) + return orientation; + } + throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(GradientOrientation)}"); + } + } + + /// + /// GradientColors + /// + [TypeConverter(typeof(ColorsTypeConverter))] + public class GradientColors:List + { + public GradientColors() : base() { } + public GradientColors(IEnumerable colors) : base(colors) { } + } + + /// + /// ColorsTypeConverter + /// + [TypeConversion(typeof(GradientColors))] + public class ColorsTypeConverter : TypeConverter + { + public override object ConvertFromInvariantString(string value) + { + if (value != null) + { + var colors = value.Split(','); + var conv = new ColorTypeConverter(); + + return new GradientColors(colors.Select(x => (Color)conv.ConvertFromInvariantString(x))); + } + + throw new InvalidOperationException($"Cannot convert \"{value}\" into {typeof(GradientColors)}"); + } + } + + /// + /// Gradient + /// + public static class Gradient + { + /// + /// The on property. + /// + public static readonly BindableProperty OnProperty = + BindableProperty.CreateAttached( + propertyName: "On", + returnType: typeof(bool?), + declaringType: typeof(Gradient), + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler + ); + + /// + /// Sets the on. + /// + /// View. + /// Value. + public static void SetOn(BindableObject view, bool? value) + { + view.SetValue(OnProperty, value); + } + + /// + /// Gets the on. + /// + /// The on. + /// View. + public static bool? GetOn(BindableObject view) + { + return (bool?)view.GetValue(OnProperty); + } + + public static readonly BindableProperty ColorsProperty = + BindableProperty.CreateAttached( + "Colors", + typeof(GradientColors), + typeof(Gradient), + default(GradientColors), + propertyChanged: AiRoutingEffectBase.AddEffectHandler + ); + + public static void SetColors(BindableObject view, GradientColors value) + { + view.SetValue(ColorsProperty, value); + } + + public static GradientColors GetColors(BindableObject view) + { + return (GradientColors)view.GetValue(ColorsProperty); + } + + public static readonly BindableProperty OrientationProperty = + BindableProperty.CreateAttached( + "Orientation", + typeof(GradientOrientation), + typeof(Gradient), + default(GradientOrientation) + ); + + public static void SetOrientation(BindableObject view, GradientOrientation value) + { + view.SetValue(OrientationProperty, value); + } + + public static GradientOrientation GetOrientation(BindableObject view) + { + return (GradientOrientation)view.GetValue(OrientationProperty); + } + } + + internal class GradientRoutingEffect : AiRoutingEffectBase + { + public GradientRoutingEffect() : base("AiForms." + nameof(Gradient)) { } + } +} diff --git a/README-ja.md b/README-ja.md index 415a789..71d1c7d 100644 --- a/README-ja.md +++ b/README-ja.md @@ -5,10 +5,12 @@ AiForms.Effect は Android と iOS に特化することにより、標準のコ ![Build status](https://kamusoft.visualstudio.com/NugetCI/_apis/build/status/AiForms.Effects) ## 機能 +* [Gradient](#gradient) + * グラデーション背景を設定する。 * [Floating](#floating) - * ページの前面の任意の場所に複数のフローティングな要素(FABなど)を配置します。 + * ページの前面の任意の場所に複数のフローティングな要素(FABなど)を配置する。 * [Feedback](#feedback) - * タッチフィードバック効果(色やシステム音)を追加。コマンドは含みません。 + * タッチフィードバック効果(色やシステム音)を追加。コマンドは含まない。 * [AddTouch](#addtouch) * 各種タッチイベントを追加 * [SizeToFit](#sizetofit) @@ -106,6 +108,33 @@ protected override void OnCreate(Bundle bundle) { } ``` +## Gradient + +任意のレイアウト要素にグラデーション効果をつけるEffectです。 +レイアウト以外のコントロールにも使用できますが、完全ではありません。 + +### Properties + +* On + * Effect On/Off (true is On) +* Colors (trigger) + * グラデーションに使う色を設定します。 + * Xamlではカンマ区切りで複数設定でき、順番にグラデーションに適用されます。 + * c# では GradientColorsクラスのインスタンスを指定します。 +* Orientation + * グラデーションの方向を指定します。 + * 8方向の指定が可能です。 + +### 使い方 + +```xml + + +``` + ## Floating ページの上の任意の場所に複数のフローティングView (Floating Action Buttonなど) を配置するEffectです。 diff --git a/README.md b/README.md index 0edcc7e..16b000a 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ AiForms.Effects is the effects library that provides you with more flexible func ![Build status](https://kamusoft.visualstudio.com/NugetCI/_apis/build/status/AiForms.Effects) ## Features +* [Gradient](#gradient) + * set a gradient background. * [Floating](#floating) * arrange some floating views (e.g. FAB) at any place over a page. * [Feedback](#feedback) @@ -113,6 +115,33 @@ protected override void OnCreate(Bundle bundle) { } ``` +## Gradient + +This is the effect that the gradient background is set to a Layout Element. +Although It can be set to the controls except with Layouts, its implementation is not complete. + +### Properties + +* On + * Effect On/Off (true is On) +* Colors (trigger) + * The colors of gradient. + * Can set multiple colors with comma-separated in Xaml. + * Specify the instance of the GradientColors class in c#. +* Orientation + * Specify the direction for the gradient. + * Can select from 8 directions. + +### How to write with XAML + +```xml + + +``` + ## Floating This is the effect that arranges some floating views (e.g. FAB) at any place on a page. diff --git a/Tests/AiEffects.TestApp/AiEffects.TestApp/ViewModels/GradientPageViewModel.cs b/Tests/AiEffects.TestApp/AiEffects.TestApp/ViewModels/GradientPageViewModel.cs new file mode 100644 index 0000000..312ce95 --- /dev/null +++ b/Tests/AiEffects.TestApp/AiEffects.TestApp/ViewModels/GradientPageViewModel.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using AiForms.Effects; +using Prism.Mvvm; +using Reactive.Bindings; +using Xamarin.Forms; + +namespace AiEffects.TestApp.ViewModels +{ + public class GradientPageViewModel:BindableBase + { + public ReactiveProperty EffectOn { get; } = new ReactiveProperty(false); + public ReactivePropertySlim Colors { get; } = new ReactivePropertySlim(); + public ReactivePropertySlim Orientation { get; } = new ReactivePropertySlim(); + + public ReactiveCommand ChangeColorsCommand { get; } = new ReactiveCommand(); + public ReactiveCommand ChangeOrientationCommand { get; } = new ReactiveCommand(); + + GradientColors _colors1 = new GradientColors(new List + { + Color.Blue,Color.LightGray + }); + GradientColors _colors2 = new GradientColors(new List + { + Color.Red, Color.Green,Color.LightGray + }); + + public GradientPageViewModel() + { + Colors.Value = _colors1; + Orientation.Value = GradientOrientation.LeftRight; + + ChangeColorsCommand.Subscribe(x => + { + var p = int.Parse(x); + Colors.Value = p == 2 ? _colors1 : _colors2; + }); + + ChangeOrientationCommand.Subscribe(x => + { + var p = int.Parse(x); + Orientation.Value = (GradientOrientation)p; + }); + } + } +} diff --git a/Tests/AiEffects.TestApp/AiEffects.TestApp/Views/GradientPage.xaml b/Tests/AiEffects.TestApp/AiEffects.TestApp/Views/GradientPage.xaml new file mode 100644 index 0000000..76bb85c --- /dev/null +++ b/Tests/AiEffects.TestApp/AiEffects.TestApp/Views/GradientPage.xaml @@ -0,0 +1,240 @@ + + + + + + + + + + + +