diff --git a/AiForms.Effects.Droid/AddCommandPlatformEffect.cs b/AiForms.Effects.Droid/AddCommandPlatformEffect.cs index cdb67dc..f6b50d9 100644 --- a/AiForms.Effects.Droid/AddCommandPlatformEffect.cs +++ b/AiForms.Effects.Droid/AddCommandPlatformEffect.cs @@ -3,35 +3,16 @@ using System.Windows.Input; using AiForms.Effects; using AiForms.Effects.Droid; -using Android.Content; -using Android.Content.Res; -using Android.Graphics.Drawables; -using Android.Media; using Android.Views; -using Android.Widget; using Xamarin.Forms; -using Xamarin.Forms.Platform.Android; [assembly: ResolutionGroupName("AiForms")] [assembly: ExportEffect(typeof(AddCommandPlatformEffect), nameof(AddCommand))] namespace AiForms.Effects.Droid { - public class AddCommandPlatformEffect : AiEffectBase + [Android.Runtime.Preserve(AllMembers =true)] + public class AddCommandPlatformEffect : FeedbackPlatformEffect { - public static SoundEffect PlaySoundEffect = SoundEffect.KeyClick; - public static Type[] TapSoundEffectElementType = { - typeof(ContentPresenter), - typeof(ContentView), - typeof(Frame), - typeof(Cell), - typeof(Xamarin.Forms.ScrollView), - typeof(TemplatedView), - typeof(Xamarin.Forms.AbsoluteLayout), - typeof(Grid), - typeof(Xamarin.Forms.RelativeLayout), - typeof(StackLayout) - }; - public static float DisabledAlpha = 0.3f; //if necessary, change this value. private static Type[] DisableEffectTargetType = { typeof(ActivityIndicator), @@ -52,22 +33,16 @@ public class AddCommandPlatformEffect : AiEffectBase private ICommand _longCommand; private object _longCommandParameter; private Android.Views.View _view; - private RippleDrawable _ripple; - private Drawable _orgDrawable; - private bool _useRipple; - private FrameLayout _rippleOverlay; - private ContainerOnLayoutChangeListener _rippleListener; - private FastRendererOnLayoutChangeListener _fastListener; - private bool _enableSound; private bool _isTapTargetSoundEffect; private bool _isDisableEffectTarget; - private AudioManager _audioManager; private bool _syncCanExecute; private GestureDetector _gestureDetector; protected override void OnAttached() { + base.OnAttached(); + _view = Control ?? Container; if (Control is Android.Widget.ListView || Control is Android.Widget.ScrollView) { @@ -78,10 +53,6 @@ protected override void OnAttached() _isTapTargetSoundEffect = TapSoundEffectElementType.Any(x => x == Element.GetType()); - if (_audioManager == null) { - _audioManager = (AudioManager)_view.Context.GetSystemService(Context.AudioService); - } - _gestureDetector = new GestureDetector(_view.Context, new ViewGestureListener(this)); _gestureDetector.IsLongpressEnabled = true; @@ -91,24 +62,16 @@ protected override void OnAttached() UpdateSyncCanExecute(); UpdateCommandParameter(); UpdateLongCommandParameter(); - UpdateEnableSound(); _view.Touch += _view_Touch; - - UpdateEnableRipple(); } protected override void OnDetached() { + base.OnDetached(); + System.Diagnostics.Debug.WriteLine(Element.GetType().FullName); if (!IsDisposed) { - if (_useRipple) { - RemoveRipple(); - } - if (_rippleOverlay != null) { - _rippleOverlay.Touch -= _view_Touch; - _rippleOverlay?.Dispose(); - } _view.Touch -= _view_Touch; } @@ -116,29 +79,19 @@ protected override void OnDetached() _command.CanExecuteChanged -= CommandCanExecuteChanged; _command = null; } + if (_longCommand != null) { _longCommand.CanExecuteChanged -= CommandCanExecuteChanged; _longCommand = null; } + _commandParameter = null; _longCommandParameter = null; - _orgDrawable = null; - _view = null; - - _rippleListener?.Dispose(); - _rippleListener = null; - - _rippleOverlay = null; - - _ripple?.Dispose(); - _ripple = null; - _useRipple = false; _gestureDetector?.Dispose(); _gestureDetector = null; - _fastListener?.Dispose(); - _fastListener = null; + _view = null; } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) @@ -155,35 +108,31 @@ protected override void OnElementPropertyChanged(System.ComponentModel.PropertyC else if (e.PropertyName == AddCommand.CommandParameterProperty.PropertyName) { UpdateCommandParameter(); } - else if (e.PropertyName == AddCommand.EffectColorProperty.PropertyName) { - UpdateEffectColor(); - } else if (e.PropertyName == AddCommand.LongCommandProperty.PropertyName) { UpdateLongCommand(); } else if (e.PropertyName == AddCommand.LongCommandParameterProperty.PropertyName) { UpdateLongCommandParameter(); } - else if (e.PropertyName == AddCommand.EnableRippleProperty.PropertyName) { - UpdateEnableRipple(); - } - else if (e.PropertyName == AddCommand.EnableSoundProperty.PropertyName) { - UpdateEnableSound(); - } else if (e.PropertyName == AddCommand.SyncCanExecuteProperty.PropertyName) { UpdateSyncCanExecute(); } } + protected override Color GetEffectColor() + { + return AddCommand.GetEffectColor(Element); + } + + protected override bool GetEnableSound() + { + return AddCommand.GetEnableSound(Element); + } + void _view_Touch(object sender, Android.Views.View.TouchEventArgs e) { _gestureDetector.OnTouchEvent(e.Event); - // for any reason depending type of element, Handled value must be changed. - // I don't know the reason. - if (!_useRipple && !IsClickable) { - e.Handled = true; - return; - } + e.Handled = false; } @@ -202,8 +151,7 @@ bool DisableEffectCheck() if (Element is Label) { //when it was setted TextColor or BackgroundColor,it will not be turn disabled color. var label = Element as Label; - return label.TextColor != Xamarin.Forms.Color.Default || - label.BackgroundColor != Xamarin.Forms.Color.Default; + return !label.TextColor.IsDefault || !label.BackgroundColor.IsDefault; } return DisableEffectTargetType.Any(x => x == Element.GetType()); @@ -235,18 +183,12 @@ void CommandCanExecuteChanged(object sender, System.EventArgs e) if (_isDisableEffectTarget) { forms.FadeTo(DisabledAlpha); } - if (IsFastRenderer) { - _view.Enabled = false; - } } else { forms.IsEnabled = true; if (_isDisableEffectTarget) { forms.FadeTo(1f); } - if (IsFastRenderer) { - _view.Enabled = true; - } } } @@ -289,154 +231,13 @@ void UpdateLongCommand() _longCommand.CanExecuteChanged += CommandCanExecuteChanged; CommandCanExecuteChanged(_longCommand, System.EventArgs.Empty); } - } + void UpdateLongCommandParameter() { _longCommandParameter = AddCommand.GetLongCommandParameter(Element); } - void UpdateEffectColor() - { - if (_useRipple) - { - var color = AddCommand.GetEffectColor(Element); - if (color == Xamarin.Forms.Color.Default) { - color = Xamarin.Forms.Color.Accent; - } - - _ripple?.SetColor(getPressedColorSelector(color.ToAndroid())); - } - - } - - void UpdateEnableRipple() - { - _useRipple = AddCommand.GetEnableRipple(Element); - - if (_useRipple) { - AddRipple(); - } - else { - RemoveRipple(); - } - - UpdateEffectColor(); - } - - void UpdateEnableSound() - { - _enableSound = AddCommand.GetEnableSound(Element); - } - - void AddRipple() - { - if (_ripple != null) { - return; - } - - if (Element is Layout || Element is BoxView) { - _rippleOverlay = new FrameLayout(_view.Context); - _rippleOverlay.LayoutParameters = new ViewGroup.LayoutParams(-1, -1); - - _rippleListener = new ContainerOnLayoutChangeListener(_rippleOverlay); - _view.AddOnLayoutChangeListener(_rippleListener); - - (_view as ViewGroup).AddView(_rippleOverlay); - - _rippleOverlay.BringToFront(); - - _rippleOverlay.Foreground = CreateRipple(Color.Accent.ToAndroid()); - _rippleOverlay.Clickable = true; - _rippleOverlay.LongClickable = true; - - _view.Touch -= _view_Touch; - _rippleOverlay.Touch += _view_Touch; - } - else if (IsFastRenderer) { - if (_fastListener == null) { - _fastListener = new FastRendererOnLayoutChangeListener(this); - _view.AddOnLayoutChangeListener(_fastListener); - _view.RequestLayout(); - return; - } - _view.Foreground = CreateRipple(Color.Accent.ToAndroid()); - _view.Touch += _view_Touch; - } - else { - _orgDrawable = _view.Background; - _view.Background = CreateRipple(Color.Accent.ToAndroid()); - } - } - - void RemoveRipple() - { - if (_ripple == null) { - return; - } - - if (Element is Layout || Element is BoxView) { - _view.Touch += _view_Touch; - _rippleOverlay.Touch -= _view_Touch; - - var viewgrp = _view as ViewGroup; - - viewgrp.RemoveOnLayoutChangeListener(_rippleListener); - _rippleListener.Dispose(); - - viewgrp.RemoveView(_rippleOverlay); - _rippleOverlay.Dispose(); - - _rippleOverlay = null; - } - else if (IsFastRenderer) { - _view.Touch -= _view_Touch; - Control.RemoveOnLayoutChangeListener(_fastListener); - _view = Control; - _fastListener.CleanUp(); - _fastListener.Dispose(); - _fastListener = null; - _view.Touch += _view_Touch; - } - else { - _view.Background = _orgDrawable; - _orgDrawable = null; - } - _ripple?.Dispose(); - _ripple = null; - } - - - RippleDrawable CreateRipple(Android.Graphics.Color color) - { - if (Element is Layout || Element is BoxView) { - var mask = new ColorDrawable(Android.Graphics.Color.White); - return _ripple = new RippleDrawable(getPressedColorSelector(color), null, mask); - } - - var back = _view.Background; - if (back == null) { - var mask = new ColorDrawable(Android.Graphics.Color.White); - return _ripple = new RippleDrawable(getPressedColorSelector(color), null, mask); - } - else { - return _ripple = new RippleDrawable(getPressedColorSelector(color), back, null); - } - } - - ColorStateList getPressedColorSelector(int pressedColor) - { - return new ColorStateList( - new int[][] - { - new int[]{} - }, - new int[] - { - pressedColor, - }); - } - class ViewGestureListener : Java.Lang.Object, GestureDetector.IOnGestureListener { AddCommandPlatformEffect _effect; @@ -474,10 +275,7 @@ public void OnLongPress(MotionEvent e) if (!_effect._longCommand.CanExecute(_effect._longCommandParameter)) return; - - if (_effect._enableSound) { - _effect._audioManager?.PlaySoundEffect(PlaySoundEffect); - } + _effect._longCommand.Execute(_effect._longCommandParameter ?? _effect.Element); } @@ -497,96 +295,11 @@ public bool OnSingleTapUp(MotionEvent e) if (!_effect._command.CanExecute(_effect._commandParameter)) return false; - if (_effect._isTapTargetSoundEffect && _effect._enableSound) { - _effect._audioManager?.PlaySoundEffect(PlaySoundEffect); - } - _effect._command.Execute(_effect._commandParameter ?? _effect.Element); return false; } - - - } - - internal class FastRendererOnLayoutChangeListener : Java.Lang.Object, Android.Views.View.IOnLayoutChangeListener - { - bool _alreadyGotParent = false; - AddCommandPlatformEffect _effect; - Android.Views.ViewGroup _parent; - FrameLayout _overlay; - - //public FastRendererOnLayoutChangeListener(IntPtr handle, JniHandleOwnership transfer):base(handle, transfer) - //{ - - //} - - public FastRendererOnLayoutChangeListener(AddCommandPlatformEffect effect) - { - _effect = effect; - _overlay = new FrameLayout(_effect._view.Context); - _overlay.Clickable = true; - _overlay.LongClickable = true; - } - - // Because FastRenderer of Label or Image can't be set ClickListener, - // insert FrameLayout with same position and same size on the view. - public void OnLayoutChange(Android.Views.View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) - { - _overlay.Layout(v.Left, v.Top, v.Right, v.Bottom); - - if (_alreadyGotParent) { - return; - } - - _parent = _effect.Control.Parent as Android.Views.ViewGroup; - _alreadyGotParent = true; - - _parent.AddView(_overlay); - - _overlay.BringToFront(); - - _effect._view.Touch -= _effect._view_Touch; - _effect._view = _overlay; - _effect.UpdateEnableRipple(); - } - - public void CleanUp() - { - _parent.RemoveView(_overlay); - _overlay.Dispose(); - } - - protected override void Dispose(bool disposing) - { - if (disposing) { - _effect = null; - _parent = null; - _overlay = null; - } - base.Dispose(disposing); - } - } - } - - - internal class ContainerOnLayoutChangeListener : Java.Lang.Object, Android.Views.View.IOnLayoutChangeListener - { - private Android.Widget.FrameLayout _layout; - - public ContainerOnLayoutChangeListener(Android.Widget.FrameLayout layout) - { - _layout = layout; - } - - //have to decide children size when OnLayoutChange. - public void OnLayoutChange(Android.Views.View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) - { - _layout.Right = v.Width; - _layout.Bottom = v.Height; } } - - } diff --git a/AiForms.Effects.Droid/AddDatePickerPlatformEffect.cs b/AiForms.Effects.Droid/AddDatePickerPlatformEffect.cs index 817b071..e03f100 100644 --- a/AiForms.Effects.Droid/AddDatePickerPlatformEffect.cs +++ b/AiForms.Effects.Droid/AddDatePickerPlatformEffect.cs @@ -9,6 +9,7 @@ [assembly: ExportEffect(typeof(AddDatePickerPlatformEffect), nameof(AddDatePicker))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AddDatePickerPlatformEffect : AiEffectBase { Android.Views.View _view; @@ -17,6 +18,8 @@ public class AddDatePickerPlatformEffect : AiEffectBase protected override void OnAttached() { + base.OnAttached(); + _view = Control ?? Container; _view.Touch += _view_Touch; @@ -29,6 +32,7 @@ protected override void OnDetached() var renderer = Container as IVisualElementRenderer; if (!IsDisposed) { _view.Touch -= _view_Touch; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } if (_dialog != null) { _dialog.Dispose(); @@ -36,6 +40,7 @@ protected override void OnDetached() } _view = null; _command = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) diff --git a/AiForms.Effects.Droid/AddNumberPickerPlatform.cs b/AiForms.Effects.Droid/AddNumberPickerPlatform.cs index af71106..044c328 100644 --- a/AiForms.Effects.Droid/AddNumberPickerPlatform.cs +++ b/AiForms.Effects.Droid/AddNumberPickerPlatform.cs @@ -11,6 +11,7 @@ [assembly: ExportEffect(typeof(AddNumberPickerPlatform), nameof(AddNumberPicker))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AddNumberPickerPlatform : AiEffectBase { private AlertDialog _dialog; @@ -22,6 +23,8 @@ public class AddNumberPickerPlatform : AiEffectBase protected override void OnAttached() { + base.OnAttached(); + _view = Control ?? Container; _view.Touch += _view_Touch; @@ -41,6 +44,7 @@ protected override void OnDetached() { if (!IsDisposed) { _view.Touch -= _view_Touch; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } if (_dialog != null) { _dialog.Dispose(); @@ -48,6 +52,7 @@ protected override void OnDetached() } _view = null; _command = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) diff --git a/AiForms.Effects.Droid/AddTextPlatformEffect.cs b/AiForms.Effects.Droid/AddTextPlatformEffect.cs index ab81dba..af95625 100644 --- a/AiForms.Effects.Droid/AddTextPlatformEffect.cs +++ b/AiForms.Effects.Droid/AddTextPlatformEffect.cs @@ -10,6 +10,7 @@ [assembly: ExportEffect(typeof(AddTextPlatformEffect), nameof(AddText))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AddTextPlatformEffect : AiEffectBase { private TextView _textView; @@ -20,6 +21,8 @@ public class AddTextPlatformEffect : AiEffectBase protected override void OnAttached() { + base.OnAttached(); + _container = Container; _textView = new TextView(_context); @@ -59,6 +62,7 @@ protected override void OnDetached() Control.RemoveOnLayoutChangeListener(_fastListener); _fastListener.CleanUp(); } + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } _listener?.Dispose(); @@ -69,6 +73,7 @@ protected override void OnDetached() _fastListener?.Dispose(); _fastListener = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) diff --git a/AiForms.Effects.Droid/AddTimePickerPlatformEffect.cs b/AiForms.Effects.Droid/AddTimePickerPlatformEffect.cs index fb2a1e0..bd4c664 100644 --- a/AiForms.Effects.Droid/AddTimePickerPlatformEffect.cs +++ b/AiForms.Effects.Droid/AddTimePickerPlatformEffect.cs @@ -10,6 +10,7 @@ [assembly: ExportEffect(typeof(AddTimePickerPlatformEffect), nameof(AddTimePicker))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AddTimePickerPlatformEffect : AiEffectBase { Android.Views.View _view; @@ -19,6 +20,8 @@ public class AddTimePickerPlatformEffect : AiEffectBase protected override void OnAttached() { + base.OnAttached(); + _view = Control ?? Container; _view.Touch += _view_Touch; @@ -31,6 +34,7 @@ protected override void OnDetached() { if (!IsDisposed) { _view.Touch -= _view_Touch; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } if (_dialog != null) { _dialog.Dispose(); @@ -38,6 +42,7 @@ protected override void OnDetached() } _view = null; _command = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) diff --git a/AiForms.Effects.Droid/AddTouchPlatformEffect.cs b/AiForms.Effects.Droid/AddTouchPlatformEffect.cs index fb1c3dd..bb7fc7e 100644 --- a/AiForms.Effects.Droid/AddTouchPlatformEffect.cs +++ b/AiForms.Effects.Droid/AddTouchPlatformEffect.cs @@ -9,7 +9,8 @@ [assembly: ExportEffect(typeof(AddTouchPlatformEffect), nameof(AddTouch))] namespace AiForms.Effects.Droid { - public class AddTouchPlatformEffect:PlatformEffect + [Android.Runtime.Preserve(AllMembers = true)] + public class AddTouchPlatformEffect:AiEffectBase { WeakReference _viewRef; TouchRecognizer _recognizer; @@ -17,6 +18,8 @@ public class AddTouchPlatformEffect:PlatformEffect protected override void OnAttached() { + base.OnAttached(); + _viewRef = new WeakReference(Control ?? Container); @@ -62,9 +65,19 @@ void _view_Touch(object sender, Android.Views.View.TouchEventArgs e) protected override void OnDetached() { + if(!IsDisposed) + { + if (_viewRef.TryGetTarget(out var view)) + { + view.Touch -= _view_Touch; + } + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); + } + Element.ClearValue(AddTouch.RecognizerProperty); _context = null; _recognizer = null; _viewRef = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } } diff --git a/AiForms.Effects.Droid/AiEffectBase.cs b/AiForms.Effects.Droid/AiEffectBase.cs index e2e06e6..440ef2e 100644 --- a/AiForms.Effects.Droid/AiEffectBase.cs +++ b/AiForms.Effects.Droid/AiEffectBase.cs @@ -3,15 +3,19 @@ using System.Linq.Expressions; using System.Reflection; using System.Linq; +using Xamarin.Forms; namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public abstract class AiEffectBase : PlatformEffect { public static bool IsFastRenderers = global::Xamarin.Forms.Forms.Flags.Any(x => x == "FastRenderers_Experimental"); IVisualElementRenderer _renderer; bool _isDisposed = false; + WeakReference _navigationRef; + protected bool IsDisposed { get { if (_isDisposed) { @@ -30,6 +34,21 @@ protected bool IsDisposed { } } + protected override void OnAttached() + { + var visual = Element as VisualElement; + var naviCandidate = visual.Navigation.NavigationStack.FirstOrDefault()?.Parent as NavigationPage; + if(naviCandidate != null) + { + naviCandidate.Popped += PagePopped; + _navigationRef = new WeakReference(naviCandidate); + } + + // Use not Popped but Popping because it is too late. + Xamarin.Forms.Application.Current.ModalPopping += ModalPopping; + } + + // whether Element is FastRenderer.(Exept Button) protected bool IsFastRenderer{ get{ @@ -82,5 +101,34 @@ Func CreateGetField(Type t) return lambda.Compile(); } + + void PagePopped(object sender, NavigationEventArgs e) + { + Clear(); + } + + void ModalPopping(object sender, ModalPoppingEventArgs e) + { + Clear(); + } + + void Clear() + { + if (_navigationRef != null && _navigationRef.TryGetTarget(out var navi)) + { + navi.Popped -= PagePopped; + } + Xamarin.Forms.Application.Current.ModalPopping -= ModalPopping; + _navigationRef = null; + + // For Android, when a page is popped, OnDetached is automatically not called. (when iOS, it is called) + // So, made the Popped & ModalPopped event subscribe in advance + // and make the effect manually removed when the page is popped. + if (IsAttached && !IsDisposed) + { + var toRemove = Element.Effects.OfType().FirstOrDefault(x => x.EffectId == ResolveId); + Element.Effects.Remove(toRemove); + } + } } } diff --git a/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj b/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj index 6c1fa9c..7d3df14 100644 --- a/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj +++ b/AiForms.Effects.Droid/AiForms.Effects.Droid.csproj @@ -1,6 +1,6 @@ - + Debug AnyCPU @@ -9,7 +9,7 @@ Library AiForms.Effects.Droid AiForms.Effects.Droid - v8.1 + v9.0 Resources\Resource.designer.cs Resource Resources @@ -45,67 +45,76 @@ - ..\packages\Xamarin.Android.Support.Annotations.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Annotations.dll + ..\packages\Xamarin.Android.Support.Annotations.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Annotations.dll + + + ..\packages\Xamarin.Android.Arch.Core.Common.1.0.0\lib\MonoAndroid80\Xamarin.Android.Arch.Core.Common.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Common.1.0.3\lib\MonoAndroid80\Xamarin.Android.Arch.Lifecycle.Common.dll + + + ..\packages\Xamarin.Android.Arch.Lifecycle.Runtime.1.0.3\lib\MonoAndroid80\Xamarin.Android.Arch.Lifecycle.Runtime.dll - ..\packages\Xamarin.Android.Support.Compat.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Compat.dll + ..\packages\Xamarin.Android.Support.Compat.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Compat.dll - ..\packages\Xamarin.Android.Support.Core.UI.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Core.UI.dll + ..\packages\Xamarin.Android.Support.Core.UI.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Core.UI.dll - ..\packages\Xamarin.Android.Support.Core.Utils.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Core.Utils.dll - - - ..\packages\Xamarin.Android.Support.Media.Compat.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Media.Compat.dll + ..\packages\Xamarin.Android.Support.Core.Utils.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Core.Utils.dll - ..\packages\Xamarin.Android.Support.Fragment.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Fragment.dll + ..\packages\Xamarin.Android.Support.Fragment.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Fragment.dll + + + ..\packages\Xamarin.Android.Support.Media.Compat.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Media.Compat.dll - ..\packages\Xamarin.Android.Support.Transition.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Transition.dll + ..\packages\Xamarin.Android.Support.Transition.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Transition.dll - ..\packages\Xamarin.Android.Support.v4.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v4.dll + ..\packages\Xamarin.Android.Support.v4.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v4.dll - ..\packages\Xamarin.Android.Support.v7.CardView.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.CardView.dll + ..\packages\Xamarin.Android.Support.v7.CardView.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.CardView.dll - ..\packages\Xamarin.Android.Support.v7.Palette.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.Palette.dll + ..\packages\Xamarin.Android.Support.v7.Palette.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.Palette.dll - ..\packages\Xamarin.Android.Support.v7.RecyclerView.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.RecyclerView.dll + ..\packages\Xamarin.Android.Support.v7.RecyclerView.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.RecyclerView.dll - ..\packages\Xamarin.Android.Support.Vector.Drawable.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Vector.Drawable.dll + ..\packages\Xamarin.Android.Support.Vector.Drawable.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Vector.Drawable.dll - ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Animated.Vector.Drawable.dll + ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Animated.Vector.Drawable.dll - ..\packages\Xamarin.Android.Support.v7.AppCompat.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.AppCompat.dll + ..\packages\Xamarin.Android.Support.v7.AppCompat.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.AppCompat.dll - ..\packages\Xamarin.Android.Support.Design.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Design.dll + ..\packages\Xamarin.Android.Support.Design.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.Design.dll - ..\packages\Xamarin.Android.Support.v7.MediaRouter.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.MediaRouter.dll + ..\packages\Xamarin.Android.Support.v7.MediaRouter.27.0.2\lib\MonoAndroid81\Xamarin.Android.Support.v7.MediaRouter.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\MonoAndroid10\FormsViewGroup.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\MonoAndroid10\FormsViewGroup.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\MonoAndroid10\Xamarin.Forms.Core.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\MonoAndroid10\Xamarin.Forms.Core.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\MonoAndroid10\Xamarin.Forms.Platform.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\MonoAndroid10\Xamarin.Forms.Platform.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll @@ -131,6 +140,10 @@ + + + + @@ -146,20 +159,24 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AiForms.Effects.Droid/AlterColorPlatformEffect.cs b/AiForms.Effects.Droid/AlterColorPlatformEffect.cs index 6ec567b..4ef11ef 100644 --- a/AiForms.Effects.Droid/AlterColorPlatformEffect.cs +++ b/AiForms.Effects.Droid/AlterColorPlatformEffect.cs @@ -7,12 +7,15 @@ [assembly: ExportEffect(typeof(AlterColorPlatformEffect), nameof(AlterColor))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AlterColorPlatformEffect : AiEffectBase { IAiEffectDroid _effect; protected override void OnAttached() { + base.OnAttached(); + if (Element is Slider) { _effect = new AlterColorSlider(Control as SeekBar, Element); } @@ -37,9 +40,11 @@ protected override void OnDetached() { if (!IsDisposed) { _effect?.OnDetachedIfNotDisposed(); + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } _effect?.OnDetached(); _effect = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) diff --git a/AiForms.Effects.Droid/AlterColorSlider.cs b/AiForms.Effects.Droid/AlterColorSlider.cs index 82c787a..db2a37c 100644 --- a/AiForms.Effects.Droid/AlterColorSlider.cs +++ b/AiForms.Effects.Droid/AlterColorSlider.cs @@ -6,6 +6,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AlterColorSlider : IAiEffectDroid { SeekBar _seekbar; diff --git a/AiForms.Effects.Droid/AlterColorStatusbar.cs b/AiForms.Effects.Droid/AlterColorStatusbar.cs index 70baa32..b588bc6 100644 --- a/AiForms.Effects.Droid/AlterColorStatusbar.cs +++ b/AiForms.Effects.Droid/AlterColorStatusbar.cs @@ -6,6 +6,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AlterColorStatusbar : IAiEffectDroid { Window _window; diff --git a/AiForms.Effects.Droid/AlterColorSwitch.cs b/AiForms.Effects.Droid/AlterColorSwitch.cs index 5abd1e4..6398db3 100644 --- a/AiForms.Effects.Droid/AlterColorSwitch.cs +++ b/AiForms.Effects.Droid/AlterColorSwitch.cs @@ -7,6 +7,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AlterColorSwitch : IAiEffectDroid { SwitchCompat _aSwitch; diff --git a/AiForms.Effects.Droid/AlterColorTextView.cs b/AiForms.Effects.Droid/AlterColorTextView.cs index 076ab00..13a3823 100644 --- a/AiForms.Effects.Droid/AlterColorTextView.cs +++ b/AiForms.Effects.Droid/AlterColorTextView.cs @@ -7,6 +7,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AlterColorTextView : IAiEffectDroid { TextView _textview; diff --git a/AiForms.Effects.Droid/AlterLineHeightPlatformEffect.cs b/AiForms.Effects.Droid/AlterLineHeightPlatformEffect.cs index de23f4f..c7c9538 100644 --- a/AiForms.Effects.Droid/AlterLineHeightPlatformEffect.cs +++ b/AiForms.Effects.Droid/AlterLineHeightPlatformEffect.cs @@ -5,12 +5,15 @@ [assembly: ExportEffect(typeof(AlterLineHeightPlatformEffect), nameof(AlterLineHeight))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class AlterLineHeightPlatformEffect : AiEffectBase { private IAiEffectDroid _effect; protected override void OnAttached() { + base.OnAttached(); + if (Element is Label) { _effect = new LineHeightForTextView(Container, Control, Element); } @@ -26,9 +29,11 @@ protected override void OnDetached() { if (!IsDisposed) { _effect.OnDetachedIfNotDisposed(); + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } _effect?.OnDetached(); _effect = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } diff --git a/AiForms.Effects.Droid/BorderPlatformEffect.cs b/AiForms.Effects.Droid/BorderPlatformEffect.cs index b3d1177..9af452c 100644 --- a/AiForms.Effects.Droid/BorderPlatformEffect.cs +++ b/AiForms.Effects.Droid/BorderPlatformEffect.cs @@ -8,6 +8,7 @@ [assembly: ExportEffect(typeof(BorderPlatformEffect), nameof(Border))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class BorderPlatformEffect : AiEffectBase { Android.Views.View _view; @@ -19,7 +20,9 @@ public class BorderPlatformEffect : AiEffectBase protected override void OnAttached() { - _view = Control ?? Container; + base.OnAttached(); + + _view = Container ?? Control; _border = new GradientDrawable(); _orgDrawable = _view.Background; @@ -34,14 +37,16 @@ protected override void OnDetached() { if (!IsDisposed) { // Check disposed _view.Background = _orgDrawable; - if (Control == null) { - _view.SetPadding(0, 0, 0, 0); - _view.ClipToOutline = false; - } + + _view.SetPadding(0, 0, 0, 0); + _view.ClipToOutline = false; + + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } _border?.Dispose(); _border = null; _view = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) @@ -98,10 +103,9 @@ void UpdateBorder() } } - if (Control == null) { - _view.SetPadding(_width, _width, _width, _width); - _view.ClipToOutline = true; //not to overflow children - } + _view.SetPadding(_width, _width, _width, _width); + _view.ClipToOutline = true; //not to overflow children + _view.SetBackground(_border); } diff --git a/AiForms.Effects.Droid/FeedbackPlatformEffect.cs b/AiForms.Effects.Droid/FeedbackPlatformEffect.cs new file mode 100644 index 0000000..5c609bc --- /dev/null +++ b/AiForms.Effects.Droid/FeedbackPlatformEffect.cs @@ -0,0 +1,289 @@ +using System; +using System.Linq; +using AiForms.Effects; +using AiForms.Effects.Droid; +using Android.Content; +using Android.Content.Res; +using Android.Graphics.Drawables; +using Android.Media; +using Android.Views; +using Android.Widget; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportEffect(typeof(FeedbackPlatformEffect), nameof(Feedback))] +namespace AiForms.Effects.Droid +{ + [Android.Runtime.Preserve(AllMembers = true)] + public class FeedbackPlatformEffect:AiEffectBase + { + public static SoundEffect PlaySoundEffect = SoundEffect.KeyClick; + public static Type[] TapSoundEffectElementType = { + typeof(ContentPresenter), + typeof(ContentView), + typeof(Frame), + typeof(Cell), + typeof(Xamarin.Forms.ScrollView), + typeof(TemplatedView), + typeof(Xamarin.Forms.AbsoluteLayout), + typeof(Grid), + typeof(Xamarin.Forms.RelativeLayout), + typeof(StackLayout) + }; + + private Android.Views.View _view; + private RippleDrawable _ripple; + private Drawable _orgDrawable; + private FrameLayout _rippleOverlay; + private FastRendererOnLayoutChangeListener _fastListener; + private bool _enableSound; + private bool _isTapTargetSoundEffect; + private AudioManager _audioManager; + + + protected override void OnAttached() + { + base.OnAttached(); + + _view = Control ?? Container; + + _isTapTargetSoundEffect = TapSoundEffectElementType.Any(x => x == Element.GetType()); + + if (_audioManager == null) + { + _audioManager = (AudioManager)_view.Context.GetSystemService(Context.AudioService); + } + + _view.Clickable = true; + _view.LongClickable = true; + + SetUpRipple(); + + UpdateEnableSound(); + + if(IsClickable) + { + _view.Touch += OnViewTouch; + } + + UpdateEffectColor(); + UpdateIsEnabled(); + } + + protected override void OnDetached() + { + System.Diagnostics.Debug.WriteLine(Element.GetType().FullName); + if (!IsDisposed) + { + if (!IsClickable) + { + _view.Touch -= OnOverlayTouch; + _view.RemoveOnLayoutChangeListener(_fastListener); + _rippleOverlay.RemoveFromParent(); + _fastListener.Dispose(); + _fastListener = null; + _rippleOverlay.Dispose(); + _rippleOverlay = null; + } + else + { + _view.Touch -= OnViewTouch; + _view.Background = _orgDrawable; + _orgDrawable = null; + } + } + + _ripple?.Dispose(); + _ripple = null; + + _audioManager = null; + _view = null; + } + + protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(e); + + if (IsDisposed) + { + return; + } + + if (e.PropertyName == Feedback.EffectColorProperty.PropertyName) + { + UpdateEffectColor(); + } + else if (e.PropertyName == Feedback.EnableSoundProperty.PropertyName) + { + UpdateEnableSound(); + } + else if(e.PropertyName == VisualElement.IsEnabledProperty.PropertyName) + { + UpdateIsEnabled(); + } + } + + void OnViewTouch(object sender, Android.Views.View.TouchEventArgs e) + { + if(e.Event.Action == MotionEventActions.Down) + { + PlaySound(); + } + + e.Handled = false; + } + + void OnOverlayTouch(object sender, Android.Views.View.TouchEventArgs e) + { + if (e.Event.Action == MotionEventActions.Down) + { + PlaySound(); + } + + _view.DispatchTouchEvent(e.Event); + + e.Handled = false; + } + + void PlaySound() + { + if (_isTapTargetSoundEffect && _enableSound) + { + _audioManager?.PlaySoundEffect(PlaySoundEffect); + } + } + + protected virtual Color GetEffectColor() + { + return Feedback.GetEffectColor(Element); + } + + protected virtual bool GetEnableSound() + { + return Feedback.GetEnableSound(Element); + } + + void UpdateEffectColor() + { + var color = GetEffectColor(); + _ripple?.SetColor(getPressedColorSelector(color.ToAndroid())); + } + + void UpdateEnableSound() + { + _enableSound = GetEnableSound(); + } + + void UpdateIsEnabled() + { + if(!IsClickable) + { + _rippleOverlay.Enabled = (Element as VisualElement).IsEnabled; + } + } + + void SetUpRipple() + { + _ripple = CreateRipple(Android.Graphics.Color.Transparent); + + if (!IsClickable) + { + _rippleOverlay = new FrameLayout(_view.Context) + { + Clickable = true, + LongClickable = true, + Foreground = _ripple + }; + _fastListener = new FastRendererOnLayoutChangeListener(this); + _view.AddOnLayoutChangeListener(_fastListener); + _view.RequestLayout(); + } + else + { + _orgDrawable = _view.Background; + _view.Background = _ripple; + } + } + + void SetUpOverlay() + { + var parent = _view.Parent as Android.Views.ViewGroup; + + parent.AddView(_rippleOverlay); + + _rippleOverlay.BringToFront(); + _rippleOverlay.Touch += OnOverlayTouch; + } + + + RippleDrawable CreateRipple(Android.Graphics.Color color) + { + if (!IsClickable) + { + var mask = new ColorDrawable(Android.Graphics.Color.White); + return new RippleDrawable(getPressedColorSelector(color), null, mask); + } + + var back = _view.Background; + if (back == null) + { + var mask = new ColorDrawable(Android.Graphics.Color.White); + return new RippleDrawable(getPressedColorSelector(color), null, mask); + } + else + { + return new RippleDrawable(getPressedColorSelector(color), back, null); + } + } + + ColorStateList getPressedColorSelector(int pressedColor) + { + return new ColorStateList( + new int[][] + { + new int[]{} + }, + new int[] + { + pressedColor, + }); + } + + internal class FastRendererOnLayoutChangeListener : Java.Lang.Object, Android.Views.View.IOnLayoutChangeListener + { + bool _alreadyGotParent = false; + FeedbackPlatformEffect _effect; + + public FastRendererOnLayoutChangeListener(FeedbackPlatformEffect effect) + { + _effect = effect; + } + + // Because FastRenderer of Label or Image can't be set ClickListener, + // insert FrameLayout with same position and same size on the view. + public void OnLayoutChange(Android.Views.View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) + { + _effect._rippleOverlay.Layout(v.Left, v.Top, v.Right, v.Bottom); + + if (_alreadyGotParent) + { + return; + } + + _alreadyGotParent = true; + + // FastRenderer Control's parent can be got at only this timing. + _effect.SetUpOverlay(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _effect = null; + } + base.Dispose(disposing); + } + } + } +} diff --git a/AiForms.Effects.Droid/FloatingLayoutRenderer.cs b/AiForms.Effects.Droid/FloatingLayoutRenderer.cs new file mode 100644 index 0000000..567940d --- /dev/null +++ b/AiForms.Effects.Droid/FloatingLayoutRenderer.cs @@ -0,0 +1,196 @@ +using System; +using XF = Xamarin.Forms; +using AiForms.Effects; +using AiForms.Effects.Droid; +using Xamarin.Forms.Platform.Android; +using Android.Widget; +using Android.Content; +using Android.Views; +using Xamarin.Forms; +using System.Threading.Tasks; + +[assembly: XF.ExportRenderer(typeof(FloatingLayout), typeof(FloatingLayoutRenderer))] +namespace AiForms.Effects.Droid +{ + [Android.Runtime.Preserve(AllMembers = true)] + public class FloatingLayoutRenderer:ViewRenderer + { + XF.Page _page; + Action OnceInitializeAction; + + public FloatingLayoutRenderer(Context context):base(context){} + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + if(e.NewElement != null) + { + OnceInitializeAction = Initialize; + + var layout = new FrameLayout(Context); + layout.SetClipChildren(false); + layout.SetClipToPadding(false); + + SetNativeControl(layout); + + _page = Element.Parent as XF.Page; + _page.SizeChanged += PageSizeChanged; + } + } + + protected override void Dispose(bool disposing) + { + if(disposing) + { + Element.Parent = null; + _page.SizeChanged -= PageSizeChanged; + foreach (var child in Element) + { + PlatformUtility.DisposeModelAndChildrenRenderers(child); + } + RootView.RemoveFromParent(); + _page = null; + } + base.Dispose(disposing); + } + + void PageSizeChanged(object sender, EventArgs e) + { + if (OnceInitializeAction == null) + { + Element.Layout(_page.Bounds); + Element.LayoutChildren(); + Layout(0, 0, (int)Context.ToPixels(_page.Bounds.Width), (int)Context.ToPixels(_page.Bounds.Height)); + } + else + { + OnceInitializeAction.Invoke(); + } + } + + void Initialize() + { + OnceInitializeAction = null; + + Element.Layout(_page.Bounds); + + foreach (var child in Element) + { + SetChildLayout(child); + } + + Layout(0, 0, (int)Context.ToPixels(_page.Bounds.Width), (int)Context.ToPixels(_page.Bounds.Height)); + + var pageView = Platform.GetRenderer(_page)?.View as ViewGroup; + using (var param = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent)) + { + pageView.AddView(RootView, param); + } + } + + void SetChildLayout(FloatingView child) + { + var renderer = PlatformUtility.GetOrCreateNativeView(child, Context); + + var nativeChild = renderer.View; + + Element.LayoutChild(child); + + int width = -1; + int height = -1; + + if (child.HorizontalLayoutAlignment != XF.LayoutAlignment.Fill) + { + width = (int)Context.ToPixels(child.Bounds.Width); + } + + if (child.VerticalLayoutAlignment != XF.LayoutAlignment.Fill) + { + height = (int)Context.ToPixels(child.Bounds.Height); + } + + + using (var param = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent) + { + Width = width, + Height = height, + Gravity = GetGravity(child), + }) + { + SetOffsetMargin(param, child); + Control.AddView(nativeChild, param); + } + } + + internal void SetOffsetMargin(FrameLayout.LayoutParams layoutParams, FloatingView view) + { + var offsetX = (int)Context.ToPixels(view.OffsetX); + if(view.HorizontalLayoutAlignment == LayoutAlignment.Fill) + { + offsetX = 0; + } + var offsetY = (int)Context.ToPixels(view.OffsetY); + if(view.VerticalLayoutAlignment == LayoutAlignment.Fill) + { + offsetY = 0; + } + + // the offset direction is reversed when GravityFlags contains Left or Bottom. + if (view.HorizontalLayoutAlignment == XF.LayoutAlignment.End) + { + layoutParams.RightMargin = offsetX * -1; + } + else + { + layoutParams.LeftMargin = offsetX; + } + + if (view.VerticalLayoutAlignment == XF.LayoutAlignment.End) + { + layoutParams.BottomMargin = offsetY * -1; + } + else + { + layoutParams.TopMargin = offsetY; + } + } + + internal GravityFlags GetGravity(FloatingView view) + { + GravityFlags gravity = GravityFlags.NoGravity; + switch (view.VerticalLayoutAlignment) + { + case XF.LayoutAlignment.Start: + gravity |= GravityFlags.Top; + break; + case XF.LayoutAlignment.End: + gravity |= GravityFlags.Bottom; + break; + case XF.LayoutAlignment.Center: + gravity |= GravityFlags.CenterVertical; + break; + default: + gravity |= GravityFlags.FillVertical; + break; + } + + switch (view.HorizontalLayoutAlignment) + { + case XF.LayoutAlignment.Start: + gravity |= GravityFlags.Left; + break; + case XF.LayoutAlignment.End: + gravity |= GravityFlags.Right; + break; + case XF.LayoutAlignment.Center: + gravity |= GravityFlags.CenterHorizontal; + break; + default: + gravity |= GravityFlags.FillHorizontal; + break; + } + + return gravity; + } + } +} diff --git a/AiForms.Effects.Droid/FloatingPlatformEffect.cs b/AiForms.Effects.Droid/FloatingPlatformEffect.cs new file mode 100644 index 0000000..5274b57 --- /dev/null +++ b/AiForms.Effects.Droid/FloatingPlatformEffect.cs @@ -0,0 +1,34 @@ +using System; +using XF = Xamarin.Forms; +using Android.Views; +using Xamarin.Forms.Platform.Android; +using System.Runtime.InteropServices; +using AiForms.Effects.Droid; +using AiForms.Effects; +using Android.Widget; + +[assembly: XF.ExportEffect(typeof(FloatingPlatformEffect), nameof(Floating))] +namespace AiForms.Effects.Droid +{ + [Android.Runtime.Preserve(AllMembers =true)] + public class FloatingPlatformEffect : AiEffectBase + { + protected override void OnAttached() + { + if (!(Element is XF.Page)) return; + + var layout = Floating.GetContent(Element); + layout.Parent = Element; + + // All following process is done on the side of FloatingLayoutRenderer. + Platform.CreateRendererWithContext(layout, Container.Context); + } + + protected override void OnDetached() + { + // The FloatingLayout renderer is automatically disposed when the parent page is popped. + } + } + + +} diff --git a/AiForms.Effects.Droid/IAiEffectDroid.cs b/AiForms.Effects.Droid/IAiEffectDroid.cs index cad46d8..27c5548 100644 --- a/AiForms.Effects.Droid/IAiEffectDroid.cs +++ b/AiForms.Effects.Droid/IAiEffectDroid.cs @@ -3,6 +3,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public interface IAiEffectDroid : IAiEffect { void OnDetachedIfNotDisposed(); diff --git a/AiForms.Effects.Droid/LineHeightForEditText.cs b/AiForms.Effects.Droid/LineHeightForEditText.cs index c9153f2..85e9077 100644 --- a/AiForms.Effects.Droid/LineHeightForEditText.cs +++ b/AiForms.Effects.Droid/LineHeightForEditText.cs @@ -5,6 +5,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class LineHeightForEditText : IAiEffectDroid { private Android.Views.ViewGroup _container; diff --git a/AiForms.Effects.Droid/LineHeightForTextView.cs b/AiForms.Effects.Droid/LineHeightForTextView.cs index 46b96b0..d389164 100644 --- a/AiForms.Effects.Droid/LineHeightForTextView.cs +++ b/AiForms.Effects.Droid/LineHeightForTextView.cs @@ -4,6 +4,7 @@ namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class LineHeightForTextView : IAiEffectDroid { private Android.Views.View _container; @@ -31,8 +32,7 @@ public void OnDetachedIfNotDisposed() _textView.SetLineSpacing(1f, _orgMultiple); if (!_isFixedHeight) { var size = _formsElement.Height * (_orgMultiple / _multiple); - _formsElement.HeightRequest = size; - _formsElement.HeightRequest = -1; //再Attacheされた時の為に初期値に戻しておく + _formsElement.Layout(new Rectangle(_formsElement.Bounds.X, _formsElement.Bounds.Y, _formsElement.Bounds.Width, size)); } } @@ -50,7 +50,7 @@ public void Update() if (!_isFixedHeight && _formsElement.Height >= 0) { var size = _formsElement.Height * (_multiple / _preMultiple); - _formsElement.HeightRequest = size; + _formsElement.Layout(new Rectangle(_formsElement.Bounds.X, _formsElement.Bounds.Y, _formsElement.Bounds.Width, size)); } _preMultiple = _multiple; diff --git a/AiForms.Effects.Droid/PlaceholderPlatformEffect.cs b/AiForms.Effects.Droid/PlaceholderPlatformEffect.cs index 1889344..054cfd0 100644 --- a/AiForms.Effects.Droid/PlaceholderPlatformEffect.cs +++ b/AiForms.Effects.Droid/PlaceholderPlatformEffect.cs @@ -8,12 +8,15 @@ [assembly: ExportEffect(typeof(PlaceholderPlatformEffect), nameof(Placeholder))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class PlaceholderPlatformEffect : AiEffectBase { EditText _editText; protected override void OnAttached() { + base.OnAttached(); + _editText = Control as EditText; UpdateText(); @@ -24,8 +27,10 @@ protected override void OnDetached() { if (!IsDisposed) { _editText.Hint = string.Empty; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } _editText = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) diff --git a/AiForms.Effects.Droid/PlatformUtility.cs b/AiForms.Effects.Droid/PlatformUtility.cs new file mode 100644 index 0000000..fd7c184 --- /dev/null +++ b/AiForms.Effects.Droid/PlatformUtility.cs @@ -0,0 +1,51 @@ +using System; +using Xamarin.Forms.Platform.Android; +using XF = Xamarin.Forms; +using Android.Content; +using Xamarin.Forms; + +namespace AiForms.Effects.Droid +{ + public static class PlatformUtility + { + static BindableProperty RendererProperty = (BindableProperty)typeof(Platform).GetField("RendererProperty", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(null); + + internal static IVisualElementRenderer GetOrCreateNativeView(XF.View view,Context context) + { + IVisualElementRenderer renderer = Platform.GetRenderer(view); + if (renderer == null) + { + renderer = Platform.CreateRendererWithContext(view, context); + Platform.SetRenderer(view, renderer); + } + + return renderer; + } + + // From internal Platform class + internal static void DisposeModelAndChildrenRenderers(XF.Element view) + { + IVisualElementRenderer renderer; + foreach (XF.VisualElement child in view.Descendants()) + { + renderer = Platform.GetRenderer(child); + child.ClearValue(RendererProperty); + + if (renderer != null) + { + renderer.View.RemoveFromParent(); + renderer.Dispose(); + } + } + + renderer = Platform.GetRenderer((XF.VisualElement)view); + if (renderer != null) + { + renderer.View.RemoveFromParent(); + renderer.Dispose(); + } + + view.ClearValue(RendererProperty); + } + } +} diff --git a/AiForms.Effects.Droid/SizeToFitPlatformEffect.cs b/AiForms.Effects.Droid/SizeToFitPlatformEffect.cs index 1ad4234..c6d7ea7 100644 --- a/AiForms.Effects.Droid/SizeToFitPlatformEffect.cs +++ b/AiForms.Effects.Droid/SizeToFitPlatformEffect.cs @@ -11,6 +11,7 @@ [assembly: ExportEffect(typeof(SizeToFitPlatformEffect), nameof(SizeToFit))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class SizeToFitPlatformEffect : AiEffectBase { FormsTextView _view; @@ -18,6 +19,8 @@ public class SizeToFitPlatformEffect : AiEffectBase protected override void OnAttached() { + base.OnAttached(); + _view = Control as FormsTextView; _orgFontSize = _view.TextSize; @@ -28,8 +31,10 @@ protected override void OnDetached() { if(!IsDisposed){ _view.SetTextSize(ComplexUnitType.Px, _orgFontSize); + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } _view = null; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) diff --git a/AiForms.Effects.Droid/ToFlatButtonPlatformEffect.cs b/AiForms.Effects.Droid/ToFlatButtonPlatformEffect.cs index 2a5390e..42c30ad 100644 --- a/AiForms.Effects.Droid/ToFlatButtonPlatformEffect.cs +++ b/AiForms.Effects.Droid/ToFlatButtonPlatformEffect.cs @@ -11,6 +11,7 @@ [assembly: ExportEffect(typeof(ToFlatButtonPlatformEffect), nameof(ToFlatButton))] namespace AiForms.Effects.Droid { + [Android.Runtime.Preserve(AllMembers = true)] public class ToFlatButtonPlatformEffect : AiEffectBase { private ColorStateList Colors; @@ -26,6 +27,8 @@ public class ToFlatButtonPlatformEffect : AiEffectBase protected override void OnAttached() { + base.OnAttached(); + NativeButton = Control as AppCompatButton; if (NativeButton == null) return; @@ -54,6 +57,7 @@ protected override void OnDetached() if (!IsDisposed) { NativeButton.Background = OrgBackground; NativeButton.StateListAnimator = OrgStateListAnimator; + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached Disposing"); } Colors.Dispose(); Shape.Dispose(); @@ -69,7 +73,7 @@ protected override void OnDetached() Shape = null; Ripple = null; Inset = null; - + System.Diagnostics.Debug.WriteLine($"{this.GetType().FullName} Detached completely"); } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) diff --git a/AiForms.Effects.Droid/packages.config b/AiForms.Effects.Droid/packages.config index 1cd6825..77ea94a 100644 --- a/AiForms.Effects.Droid/packages.config +++ b/AiForms.Effects.Droid/packages.config @@ -1,20 +1,23 @@  - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AiForms.Effects.iOS/AddCommandPlatformEffect.cs b/AiForms.Effects.iOS/AddCommandPlatformEffect.cs index 6ce2016..498c107 100644 --- a/AiForms.Effects.iOS/AddCommandPlatformEffect.cs +++ b/AiForms.Effects.iOS/AddCommandPlatformEffect.cs @@ -1,23 +1,19 @@ -using System.Threading.Tasks; +using System; +using System.Linq; using System.Windows.Input; using AiForms.Effects; using AiForms.Effects.iOS; -using CoreGraphics; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; -using AudioToolbox; -using System; -using System.Linq; [assembly: ResolutionGroupName("AiForms")] [assembly: ExportEffect(typeof(AddCommandPlatformEffect), nameof(AddCommand))] namespace AiForms.Effects.iOS { - public class AddCommandPlatformEffect : PlatformEffect + [Foundation.Preserve(AllMembers =true)] + public class AddCommandPlatformEffect : FeedbackPlatformEffect { - public static uint PlaySoundNo = 1306; - private static Type[] ExceptDisableEffectTargetType = { typeof(Button), typeof(Picker), @@ -31,31 +27,24 @@ public class AddCommandPlatformEffect : PlatformEffect private object _longCommandParameter; private UITapGestureRecognizer _tapGesture; private UILongPressGestureRecognizer _longTapGesture; - private bool _enableSound; private UIView _view; - private UIView _layer; - private double _alpha; - private SystemSound _clickSound; private bool _syncCanExecute; private bool _isDisableEffectTarget; private readonly float _disabledAlpha = 0.3f; protected override void OnAttached() { + base.OnAttached(); + _view = Control ?? Container; - _tapGesture = new UITapGestureRecognizer(async (obj) => { + _tapGesture = new UITapGestureRecognizer((obj) => { if (_command == null) return; if (!_command.CanExecute(_commandParameter)) return; - await TapAnimation(0.3, _alpha, 0); - - if (_enableSound) - PlayClickSound(); - _command.Execute(_commandParameter ?? Element); }); @@ -66,23 +55,20 @@ protected override void OnAttached() UpdateSyncCanExecute(); UpdateCommandParameter(); UpdateLongCommandParameter(); - UpdateEffectColor(); - UpdateEnableSound(); } protected override void OnDetached() { + base.OnDetached(); + _view.RemoveGestureRecognizer(_tapGesture); _tapGesture.Dispose(); + _tapGesture = null; if (_longTapGesture != null) { _view.RemoveGestureRecognizer(_longTapGesture); _longTapGesture.Dispose(); - } - - if (_layer != null) { - _layer.Dispose(); - _layer = null; + _longTapGesture = null; } if (_command != null) { @@ -93,7 +79,12 @@ protected override void OnDetached() _longCommand.CanExecuteChanged -= CommandCanExecuteChanged; } - _clickSound?.Dispose(); + _command = null; + _longCommand = null; + _commandParameter = null; + _longCommandParameter = null; + + _view = null; } protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) @@ -106,23 +97,27 @@ protected override void OnElementPropertyChanged(System.ComponentModel.PropertyC else if (e.PropertyName == AddCommand.CommandParameterProperty.PropertyName) { UpdateCommandParameter(); } - else if (e.PropertyName == AddCommand.EffectColorProperty.PropertyName) { - UpdateEffectColor(); - } else if (e.PropertyName == AddCommand.LongCommandProperty.PropertyName) { UpdateLongCommand(); } else if (e.PropertyName == AddCommand.LongCommandParameterProperty.PropertyName) { UpdateLongCommandParameter(); } - else if (e.PropertyName == AddCommand.EnableSoundProperty.PropertyName) { - UpdateEnableSound(); - } else if (e.PropertyName == AddCommand.SyncCanExecuteProperty.PropertyName) { UpdateSyncCanExecute(); } } + protected override Color GetEffectColor() + { + return AddCommand.GetEffectColor(Element); + } + + protected override bool GetEnableSound() + { + return AddCommand.GetEnableSound(Element); + } + void UpdateSyncCanExecute() { _syncCanExecute = AddCommand.GetSyncCanExecute(Element); @@ -211,24 +206,15 @@ void UpdateLongCommand() CommandCanExecuteChanged(_longCommand, System.EventArgs.Empty); } - _longTapGesture = new UILongPressGestureRecognizer(async (obj) => { + _longTapGesture = new UILongPressGestureRecognizer((obj) => { if (!_longCommand.CanExecute(_longCommandParameter)) { return; } if (obj.State == UIGestureRecognizerState.Began) { - if (_enableSound) - PlayClickSound(); _longCommand?.Execute(_longCommandParameter ?? Element); - await TapAnimation(0.5, 0, _alpha, false); - } - else if (obj.State == UIGestureRecognizerState.Ended || - obj.State == UIGestureRecognizerState.Cancelled || - obj.State == UIGestureRecognizerState.Failed) { - - await TapAnimation(0.5, _alpha, 0); } }); _view.AddGestureRecognizer(_longTapGesture); @@ -239,55 +225,6 @@ void UpdateLongCommandParameter() { _longCommandParameter = AddCommand.GetLongCommandParameter(Element); } - - void UpdateEffectColor() - { - - if (_layer != null) { - _layer.Dispose(); - _layer = null; - } - - var color = AddCommand.GetEffectColor(Element); - if (color == Xamarin.Forms.Color.Default) { - return; - } - _alpha = color.A < 1.0 ? 1 : 0.3; - - _layer = new UIView(); - _layer.BackgroundColor = color.ToUIColor(); - - } - - void UpdateEnableSound() - { - _enableSound = AddCommand.GetEnableSound(Element); - } - - async Task TapAnimation(double duration, double start = 1, double end = 0, bool remove = true) - { - if (_layer != null) { - _layer.Frame = new CGRect(0, 0, Container.Bounds.Width, Container.Bounds.Height); - Container.AddSubview(_layer); - Container.BringSubviewToFront(_layer); - _layer.Alpha = (float)start; - await UIView.AnimateAsync(duration, () => { - _layer.Alpha = (float)end; - }); - if (remove) { - _layer.RemoveFromSuperview(); - } - } - } - - void PlayClickSound() - { - if (_clickSound == null) - _clickSound = new SystemSound(PlaySoundNo); - - _clickSound.PlaySystemSoundAsync(); - } - } } diff --git a/AiForms.Effects.iOS/AddDatePickerPlatformEffect.cs b/AiForms.Effects.iOS/AddDatePickerPlatformEffect.cs index 96385c9..eb0456a 100644 --- a/AiForms.Effects.iOS/AddDatePickerPlatformEffect.cs +++ b/AiForms.Effects.iOS/AddDatePickerPlatformEffect.cs @@ -11,6 +11,7 @@ [assembly: ExportEffect(typeof(AddDatePickerPlatformEffect), nameof(AddDatePicker))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AddDatePickerPlatformEffect : PlatformEffect { UIView _view; diff --git a/AiForms.Effects.iOS/AddNumberPickerPlatform.cs b/AiForms.Effects.iOS/AddNumberPickerPlatform.cs index 0280844..6cd296b 100644 --- a/AiForms.Effects.iOS/AddNumberPickerPlatform.cs +++ b/AiForms.Effects.iOS/AddNumberPickerPlatform.cs @@ -9,6 +9,7 @@ [assembly: ExportEffect(typeof(NumberPickerPlatform), nameof(AddNumberPicker))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class NumberPickerPlatform : PlatformEffect { private UIPickerView _picker; diff --git a/AiForms.Effects.iOS/AddTimePickerPlatformEffect.cs b/AiForms.Effects.iOS/AddTimePickerPlatformEffect.cs index 5431678..f73a8cb 100644 --- a/AiForms.Effects.iOS/AddTimePickerPlatformEffect.cs +++ b/AiForms.Effects.iOS/AddTimePickerPlatformEffect.cs @@ -11,6 +11,7 @@ [assembly: ExportEffect(typeof(AddTimePickerPlatformEffect), nameof(AddTimePicker))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AddTimePickerPlatformEffect : PlatformEffect { UIDatePicker _picker; diff --git a/AiForms.Effects.iOS/AddTouchPlatformEffect.cs b/AiForms.Effects.iOS/AddTouchPlatformEffect.cs index 91f0fbb..2db038a 100644 --- a/AiForms.Effects.iOS/AddTouchPlatformEffect.cs +++ b/AiForms.Effects.iOS/AddTouchPlatformEffect.cs @@ -7,6 +7,7 @@ [assembly: ExportEffect(typeof(AddTouchPlatformEffect), nameof(AddTouch))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AddTouchPlatformEffect : PlatformEffect { UIView _view; @@ -23,6 +24,7 @@ protected override void OnAttached() protected override void OnDetached() { + Element.ClearValue(AddTouch.RecognizerProperty); _view?.RemoveGestureRecognizer(_recognizer); _view = null; _recognizer?.Dispose(); diff --git a/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj b/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj index 90567da..f4548df 100644 --- a/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj +++ b/AiForms.Effects.iOS/AiForms.Effects.iOS.csproj @@ -1,6 +1,6 @@ - + Debug AnyCPU @@ -47,16 +47,16 @@ - ..\packages\Xamarin.Forms.3.0.0.446417\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll - ..\packages\Xamarin.Forms.3.0.0.446417\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll + ..\packages\Xamarin.Forms.3.3.0.912540\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll @@ -83,6 +83,9 @@ + + + @@ -94,5 +97,5 @@ - + \ No newline at end of file diff --git a/AiForms.Effects.iOS/AlterColorPlatformEffect.cs b/AiForms.Effects.iOS/AlterColorPlatformEffect.cs index 25541f8..149d2f0 100644 --- a/AiForms.Effects.iOS/AlterColorPlatformEffect.cs +++ b/AiForms.Effects.iOS/AlterColorPlatformEffect.cs @@ -8,6 +8,7 @@ [assembly: ExportEffect(typeof(AlterColorPlatformEffect), nameof(AlterColor))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AlterColorPlatformEffect : PlatformEffect { IAiEffect _effect; diff --git a/AiForms.Effects.iOS/AlterColorSlider.cs b/AiForms.Effects.iOS/AlterColorSlider.cs index b5389af..c5a25bc 100644 --- a/AiForms.Effects.iOS/AlterColorSlider.cs +++ b/AiForms.Effects.iOS/AlterColorSlider.cs @@ -4,6 +4,7 @@ using Xamarin.Forms.Platform.iOS; namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AlterColorSlider : IAiEffect { UISlider _slider; diff --git a/AiForms.Effects.iOS/AlterColorSwitch.cs b/AiForms.Effects.iOS/AlterColorSwitch.cs index 51d8361..d0ad23a 100644 --- a/AiForms.Effects.iOS/AlterColorSwitch.cs +++ b/AiForms.Effects.iOS/AlterColorSwitch.cs @@ -4,6 +4,7 @@ using Xamarin.Forms.Platform.iOS; namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AlterColorSwitch : IAiEffect { UISwitch _uiswitch; diff --git a/AiForms.Effects.iOS/AlterLineHeightPlatformEffect.cs b/AiForms.Effects.iOS/AlterLineHeightPlatformEffect.cs index 31512e4..81c14cb 100644 --- a/AiForms.Effects.iOS/AlterLineHeightPlatformEffect.cs +++ b/AiForms.Effects.iOS/AlterLineHeightPlatformEffect.cs @@ -6,6 +6,7 @@ [assembly: ExportEffect(typeof(AlterLineHeightPlatformEffect), nameof(AlterLineHeight))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class AlterLineHeightPlatformEffect : PlatformEffect { diff --git a/AiForms.Effects.iOS/BorderPlatformEffect.cs b/AiForms.Effects.iOS/BorderPlatformEffect.cs index 9ce1eb8..b492e46 100644 --- a/AiForms.Effects.iOS/BorderPlatformEffect.cs +++ b/AiForms.Effects.iOS/BorderPlatformEffect.cs @@ -9,6 +9,7 @@ [assembly: ExportEffect(typeof(BorderPlatformEffect), nameof(Border))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class BorderPlatformEffect : PlatformEffect { UIView _view; @@ -24,6 +25,12 @@ protected override void OnAttached() { _view = Control ?? Container; + if(Element is Label) + { + // If Control is used, the effect doesn't have an effect on Background's border and radius. + _view = Container; + } + _clipsToBounds = _view.ClipsToBounds; if (hasBorderTypes.Any(x => x == Element.GetType())) { var textfield = _view as UITextField; @@ -42,8 +49,12 @@ protected override void OnDetached() textfield.BorderStyle = UITextBorderStyle.RoundedRect; } _view.ClipsToBounds = _clipsToBounds; - _view.Layer.CornerRadius = 0f; - _view.Layer.BorderWidth = 0; + if(_view.Layer != null) + { + _view.Layer.CornerRadius = 0f; + _view.Layer.BorderWidth = 0; + } + _view = null; } diff --git a/AiForms.Effects.iOS/FeedbackPlatformEffect.cs b/AiForms.Effects.iOS/FeedbackPlatformEffect.cs new file mode 100644 index 0000000..87e4d12 --- /dev/null +++ b/AiForms.Effects.iOS/FeedbackPlatformEffect.cs @@ -0,0 +1,173 @@ +using System; +using AiForms.Effects; +using AiForms.Effects.iOS; +using AudioToolbox; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; +using System.Linq; + +[assembly: ExportEffect(typeof(FeedbackPlatformEffect), nameof(Feedback))] +namespace AiForms.Effects.iOS +{ + [Foundation.Preserve(AllMembers = true)] + public class FeedbackPlatformEffect:PlatformEffect + { + public static uint PlaySoundNo = 1306; + + TouchRecognizer _toucheController; + TouchEffectGestureRecognizer _touchRecognizer; + UIView _view; + UIView _layer; + bool _enableSound; + SystemSound _clickSound; + float _alpha; + VisualElement visualElement => Element as VisualElement; + + protected override void OnAttached() + { + _view = Control ?? Container; + + _view.UserInteractionEnabled = true; + + _layer = new UIView { + Alpha = 0, + Opaque = false, + UserInteractionEnabled = false + }; + _view.AddSubview(_layer); + + _layer.TranslatesAutoresizingMaskIntoConstraints = false; + + _layer.TopAnchor.ConstraintEqualTo(_view.TopAnchor).Active = true; + _layer.LeftAnchor.ConstraintEqualTo(_view.LeftAnchor).Active = true; + _layer.BottomAnchor.ConstraintEqualTo(_view.BottomAnchor).Active = true; + _layer.RightAnchor.ConstraintEqualTo(_view.RightAnchor).Active = true; + + + _view.BringSubviewToFront(_layer); + + _toucheController = new TouchRecognizer(); + _touchRecognizer = new TouchEffectGestureRecognizer(_toucheController); + _touchRecognizer.Delegate = new AlwaysSimultaneouslyGestureRecognizerDelegate(); + + _view.AddGestureRecognizer(_touchRecognizer); + + _toucheController.TouchBegin += OnTouchBegin; + _toucheController.TouchEnd += OnTouchEnd; + _toucheController.TouchCancel += OnTouchEnd; + + UpdateEffectColor(); + UpdateEnableSound(); + } + + protected override void OnDetached() + { + _toucheController.TouchBegin -= OnTouchBegin; + _toucheController.TouchEnd -= OnTouchEnd; + _toucheController.TouchCancel -= OnTouchEnd; + + _view.RemoveGestureRecognizer(_touchRecognizer); + _touchRecognizer.Delegate?.Dispose(); + _touchRecognizer.Delegate = null; + _touchRecognizer.Dispose(); + + _touchRecognizer = null; + _toucheController = null; + + _layer.RemoveFromSuperview(); + _layer.Dispose(); + _layer = null; + + _clickSound?.Dispose(); + _clickSound = null; + + _view = null; + } + + protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e) + { + base.OnElementPropertyChanged(e); + + if (e.PropertyName == Feedback.EffectColorProperty.PropertyName) + { + UpdateEffectColor(); + } + else if (e.PropertyName == Feedback.EnableSoundProperty.PropertyName) + { + UpdateEnableSound(); + } + } + + async void OnTouchBegin(object sender, TouchEventArgs e) + { + if (!visualElement.IsEnabled) return; + + // if it is a variety of Picker, make it fire. + _view.Subviews.FirstOrDefault(x => x is NoCaretField)?.BecomeFirstResponder(); + + _view.BecomeFirstResponder(); + + if (_enableSound) + { + PlayClickSound(); + } + + await UIView.AnimateAsync(0.5, () => + { + _layer.Alpha = _alpha; + }); + } + + async void OnTouchEnd(object sender, TouchEventArgs e) + { + if (!visualElement.IsEnabled) return; + + await UIView.AnimateAsync(0.5, () => + { + _layer.Alpha = 0; + }); + } + + void UpdateEffectColor() + { + var color = GetEffectColor(); + + _alpha = color.A < 1.0f ? 1f : 0.3f; + _layer.BackgroundColor = color.ToUIColor(); + } + + void UpdateEnableSound() + { + _enableSound = GetEnableSound(); + } + + protected virtual Color GetEffectColor() + { + return Feedback.GetEffectColor(Element); + } + + protected virtual bool GetEnableSound() + { + return Feedback.GetEnableSound(Element); + } + + void PlayClickSound() + { + if (_clickSound == null) + _clickSound = new SystemSound(PlaySoundNo); + + _clickSound.PlaySystemSoundAsync(); + } + + } + + public class AlwaysSimultaneouslyGestureRecognizerDelegate:UIGestureRecognizerDelegate + { + public override bool ShouldRecognizeSimultaneously(UIGestureRecognizer gestureRecognizer, UIGestureRecognizer otherGestureRecognizer) + { + // always recognize simultaneously. + return true; + } + } +} diff --git a/AiForms.Effects.iOS/FloatingPlatformEffect.cs b/AiForms.Effects.iOS/FloatingPlatformEffect.cs new file mode 100644 index 0000000..7d69d26 --- /dev/null +++ b/AiForms.Effects.iOS/FloatingPlatformEffect.cs @@ -0,0 +1,155 @@ +using System; +using Xamarin.Forms; +using AiForms.Effects; +using AiForms.Effects.iOS; +using Xamarin.Forms.Platform.iOS; +using UIKit; +using System.ComponentModel; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Linq; +using System.Diagnostics; +using CoreGraphics; + +[assembly: ExportEffect(typeof(FloatingPlatformEffect), nameof(Floating))] +namespace AiForms.Effects.iOS +{ + [Foundation.Preserve(AllMembers =true)] + public class FloatingPlatformEffect:PlatformEffect + { + Page _page; + UIView _nativePage; + FloatingLayout _formsLayout; + Action OnceInitializeAction; + UIDeviceOrientation _previousOrientation; + + protected override void OnAttached() + { + if (!(Element is Page)) return; + + OnceInitializeAction = Initialize; + + _formsLayout = Floating.GetContent(Element); + _formsLayout.Parent = Element; + + _page = Element as Page; + _page.SizeChanged += PageSizeChanged; + _page.LayoutChanged += PageLayoutChanged; + + void BindingContextChanged(object sender,EventArgs e) + { + // When the target is a Page, since OnDetached method isn't automatically called, + // manually make it call at this timing. + if (_page.BindingContext != null && !IsAttached) return; + + _page.BindingContextChanged -= BindingContextChanged; + + var toRemove = Element.Effects.OfType().FirstOrDefault(x => x.EffectId == ResolveId); + Element.Effects.Remove(toRemove); + } + + _page.BindingContextChanged += BindingContextChanged; + } + + protected override void OnDetached() + { + _page.SizeChanged -= PageSizeChanged; + _page.LayoutChanged -= PageLayoutChanged; + _formsLayout.Parent = null; + + foreach(var child in _formsLayout) + { + PlatformUtility.DisposeModelAndChildrenRenderers(child); + } + + _formsLayout = null; + _nativePage = null; + _page = null; + } + + + void PageLayoutChanged(object sender, EventArgs e) + { + if (OnceInitializeAction == null && _previousOrientation != UIDevice.CurrentDevice.Orientation) + { + _formsLayout.Layout(_nativePage.Bounds.ToRectangle()); + _formsLayout.LayoutChildren(); + } + _previousOrientation = UIDevice.CurrentDevice.Orientation; + } + + + void PageSizeChanged(object sender, EventArgs e) + { + OnceInitializeAction?.Invoke(); + } + + protected override void OnElementPropertyChanged(PropertyChangedEventArgs args) + { + base.OnElementPropertyChanged(args); + } + + void Initialize() + { + OnceInitializeAction = null; + + _nativePage = Container; + + _formsLayout.Layout(_nativePage.Bounds.ToRectangle()); + + foreach (var child in _formsLayout) + { + var renderer = PlatformUtility.GetOrCreateNativeView(child); + _formsLayout.LayoutChild(child); + SetLayoutAlignment(renderer.NativeView, _nativePage, child); + } + } + + internal static void SetLayoutAlignment(UIView targetView, UIView parentView, FloatingView floating) + { + targetView.TranslatesAutoresizingMaskIntoConstraints = false; + parentView.AddSubview(targetView); + + if(floating.HorizontalLayoutAlignment != LayoutAlignment.Fill) + { + targetView.WidthAnchor.ConstraintEqualTo((System.nfloat)floating.Bounds.Width).Active = true; + } + if(floating.VerticalLayoutAlignment != LayoutAlignment.Fill) + { + targetView.HeightAnchor.ConstraintEqualTo((System.nfloat)floating.Bounds.Height).Active = true; + } + + switch (floating.VerticalLayoutAlignment) + { + case Xamarin.Forms.LayoutAlignment.Start: + targetView.TopAnchor.ConstraintEqualTo(parentView.TopAnchor, floating.OffsetY).Active = true; + break; + case Xamarin.Forms.LayoutAlignment.End: + targetView.BottomAnchor.ConstraintEqualTo(parentView.BottomAnchor, floating.OffsetY).Active = true; + break; + case Xamarin.Forms.LayoutAlignment.Center: + targetView.CenterYAnchor.ConstraintEqualTo(parentView.CenterYAnchor, floating.OffsetY).Active = true; + break; + default: + targetView.HeightAnchor.ConstraintEqualTo(parentView.HeightAnchor).Active = true; + break; + } + + switch (floating.HorizontalLayoutAlignment) + { + case Xamarin.Forms.LayoutAlignment.Start: + targetView.LeftAnchor.ConstraintEqualTo(parentView.LeftAnchor, floating.OffsetX).Active = true; + break; + case Xamarin.Forms.LayoutAlignment.End: + targetView.RightAnchor.ConstraintEqualTo(parentView.RightAnchor, floating.OffsetX).Active = true; + break; + case Xamarin.Forms.LayoutAlignment.Center: + targetView.CenterXAnchor.ConstraintEqualTo(parentView.CenterXAnchor, floating.OffsetX).Active = true; + break; + default: + targetView.WidthAnchor.ConstraintEqualTo(parentView.WidthAnchor).Active = true; + break; + } + } + } +} diff --git a/AiForms.Effects.iOS/Initialize.cs b/AiForms.Effects.iOS/Initialize.cs index e4c66b1..5afa259 100644 --- a/AiForms.Effects.iOS/Initialize.cs +++ b/AiForms.Effects.iOS/Initialize.cs @@ -1,5 +1,6 @@ namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public static class Effects { public static void Init() { } diff --git a/AiForms.Effects.iOS/LineHeightForLabel.cs b/AiForms.Effects.iOS/LineHeightForLabel.cs index 0d2d629..8802c58 100644 --- a/AiForms.Effects.iOS/LineHeightForLabel.cs +++ b/AiForms.Effects.iOS/LineHeightForLabel.cs @@ -5,6 +5,7 @@ namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class LineHeightForLabel : IAiEffect { private UIView _container; @@ -68,12 +69,11 @@ void ChangeSize() if (NeedToChangeSize()) { var size = _nativeLabel.SizeThatFits(_container.Frame.Size); - _formsLabel.HeightRequest = size.Height; - _formsLabel.HeightRequest = -1; //再Attacheされた時の為に初期値に戻しておく + _formsLabel.Layout(new Rectangle(_formsLabel.Bounds.X, _formsLabel.Bounds.Y, size.Width, size.Height)); } else { var render = Platform.GetRenderer(_formsLabel) as LabelRenderer; - render.LayoutSubviews(); + render?.LayoutSubviews(); } } diff --git a/AiForms.Effects.iOS/LineHeightForTextView.cs b/AiForms.Effects.iOS/LineHeightForTextView.cs index 1c245ea..04b636e 100644 --- a/AiForms.Effects.iOS/LineHeightForTextView.cs +++ b/AiForms.Effects.iOS/LineHeightForTextView.cs @@ -5,6 +5,7 @@ namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class LineHeightForTextView : IAiEffect { private UITextView _nativeTextView; diff --git a/AiForms.Effects.iOS/NoCaretField.cs b/AiForms.Effects.iOS/NoCaretField.cs index d7bb0e7..4974105 100644 --- a/AiForms.Effects.iOS/NoCaretField.cs +++ b/AiForms.Effects.iOS/NoCaretField.cs @@ -4,6 +4,7 @@ namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] internal class NoCaretField : UITextField { public NoCaretField() : base(new CGRect()) diff --git a/AiForms.Effects.iOS/NumberPickerSource.cs b/AiForms.Effects.iOS/NumberPickerSource.cs index 8b6340d..05475d3 100644 --- a/AiForms.Effects.iOS/NumberPickerSource.cs +++ b/AiForms.Effects.iOS/NumberPickerSource.cs @@ -5,6 +5,7 @@ namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] internal class NumberPickerSource : UIPickerViewModel { public IList Items { get; private set; } diff --git a/AiForms.Effects.iOS/PlaceholderPlatformEffect.cs b/AiForms.Effects.iOS/PlaceholderPlatformEffect.cs index a886ec0..bf11cdf 100644 --- a/AiForms.Effects.iOS/PlaceholderPlatformEffect.cs +++ b/AiForms.Effects.iOS/PlaceholderPlatformEffect.cs @@ -8,6 +8,7 @@ [assembly: ExportEffect(typeof(PlaceholderPlatformEffect), nameof(Placeholder))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class PlaceholderPlatformEffect : PlatformEffect { UITextView _textView; diff --git a/AiForms.Effects.iOS/PlatformUtility.cs b/AiForms.Effects.iOS/PlatformUtility.cs new file mode 100644 index 0000000..9ae1ef5 --- /dev/null +++ b/AiForms.Effects.iOS/PlatformUtility.cs @@ -0,0 +1,67 @@ +using System; +using System.Reflection; +using UIKit; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +namespace AiForms.Effects.iOS +{ + public static class PlatformUtility + { + // Get internal members + static BindableProperty RendererProperty = (BindableProperty)typeof(Platform).GetField("RendererProperty", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(null); + static Type DefaultRenderer = typeof(Platform).Assembly.GetType("Xamarin.Forms.Platform.iOS.Platform+DefaultRenderer"); + static Type ModalWrapper = typeof(Platform).Assembly.GetType("Xamarin.Forms.Platform.iOS.ModalWrapper"); + static MethodInfo ModalWapperDispose = ModalWrapper.GetMethod("Dispose"); + + internal static IVisualElementRenderer GetOrCreateNativeView(VisualElement view) + { + IVisualElementRenderer renderer = Platform.GetRenderer(view); + if (renderer == null) + { + renderer = Platform.CreateRenderer(view); + Platform.SetRenderer(view, renderer); + } + + renderer.NativeView.AutoresizingMask = UIViewAutoresizing.All; + renderer.NativeView.ContentMode = UIViewContentMode.ScaleToFill; + + return renderer; + } + + // From internal Platform class + internal static void DisposeModelAndChildrenRenderers(Element view) + { + IVisualElementRenderer renderer; + foreach (VisualElement child in view.Descendants()) + { + renderer = Platform.GetRenderer(child); + child.ClearValue(RendererProperty); + + if (renderer != null) + { + renderer.NativeView.RemoveFromSuperview(); + renderer.Dispose(); + } + } + + renderer = Platform.GetRenderer((VisualElement)view); + if (renderer != null) + { + if (renderer.ViewController != null) + { + if (renderer.ViewController.ParentViewController.GetType() == ModalWrapper) + { + var modalWrapper = Convert.ChangeType(renderer.ViewController.ParentViewController, ModalWrapper); + ModalWapperDispose.Invoke(modalWrapper, new object[] { }); + } + } + + renderer.NativeView.RemoveFromSuperview(); + renderer.Dispose(); + } + + view.ClearValue(RendererProperty); + } + } +} diff --git a/AiForms.Effects.iOS/SizeToFitPlatformEffect.cs b/AiForms.Effects.iOS/SizeToFitPlatformEffect.cs index 03774d7..1f11970 100644 --- a/AiForms.Effects.iOS/SizeToFitPlatformEffect.cs +++ b/AiForms.Effects.iOS/SizeToFitPlatformEffect.cs @@ -11,6 +11,7 @@ [assembly: ExportEffect(typeof(SizeToFitPlatformEffect), nameof(SizeToFit))] namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class SizeToFitPlatformEffect : PlatformEffect { UILabel _view; diff --git a/AiForms.Effects.iOS/TouchEffectGestureRecognizer.cs b/AiForms.Effects.iOS/TouchEffectGestureRecognizer.cs index b2438de..b91383d 100644 --- a/AiForms.Effects.iOS/TouchEffectGestureRecognizer.cs +++ b/AiForms.Effects.iOS/TouchEffectGestureRecognizer.cs @@ -3,6 +3,7 @@ namespace AiForms.Effects.iOS { + [Foundation.Preserve(AllMembers = true)] public class TouchEffectGestureRecognizer : UIGestureRecognizer { diff --git a/AiForms.Effects.iOS/packages.config b/AiForms.Effects.iOS/packages.config index a268790..8ca5088 100644 --- a/AiForms.Effects.iOS/packages.config +++ b/AiForms.Effects.iOS/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/AiForms.Effects/AddCommand.cs b/AiForms.Effects/AddCommand.cs index 0e8d875..f2e1e9a 100644 --- a/AiForms.Effects/AddCommand.cs +++ b/AiForms.Effects/AddCommand.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Windows.Input; using Xamarin.Forms; +using System; namespace AiForms.Effects { @@ -9,36 +10,20 @@ public static class AddCommand public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(AddCommand), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); - } - - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new AddCommandRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is AddCommandRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } + return (bool?)view.GetValue(OnProperty); } public static readonly BindableProperty EnableSoundProperty = @@ -47,11 +32,12 @@ private static void OnOffChanged(BindableObject bindable, object oldValue, objec typeof(bool), typeof(AddCommand), false); - + public static void SetEnableSound(BindableObject view, bool value) { view.SetValue(EnableSoundProperty, value); } + public static bool GetEnableSound(BindableObject view) { return (bool)view.GetValue(EnableSoundProperty); @@ -62,7 +48,8 @@ public static bool GetEnableSound(BindableObject view) "Command", typeof(ICommand), typeof(AddCommand), - default(ICommand) + default(ICommand), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetCommand(BindableObject view, ICommand value) @@ -99,7 +86,8 @@ public static object GetCommandParameter(BindableObject view) "LongCommand", typeof(ICommand), typeof(AddCommand), - default(ICommand) + default(ICommand), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetLongCommand(BindableObject view, ICommand value) @@ -135,9 +123,9 @@ public static object GetLongCommandParameter(BindableObject view) "EffectColor", typeof(Color), typeof(AddCommand), - Color.Default + Color.Transparent ); - + public static void SetEffectColor(BindableObject view, Color value) { view.SetValue(EffectColorProperty, value); @@ -148,6 +136,7 @@ public static Color GetEffectColor(BindableObject view) return (Color)view.GetValue(EffectColorProperty); } + [Obsolete("This property is obsolete as of version 1.4. Ripple color is transparent by default.")] public static readonly BindableProperty EnableRippleProperty = BindableProperty.CreateAttached( "EnableRipple", @@ -156,11 +145,13 @@ public static Color GetEffectColor(BindableObject view) true ); + [Obsolete("This method is obsolete as of version 1.4. Ripple color is transparent by default.")] public static void SetEnableRipple(BindableObject view, bool value) { view.SetValue(EnableRippleProperty, value); } + [Obsolete("This method is obsolete as of version 1.4. Ripple color is transparent by default.")] public static bool GetEnableRipple(BindableObject view) { return (bool)view.GetValue(EnableRippleProperty); @@ -183,13 +174,11 @@ public static bool GetSyncCanExecute(BindableObject view) { return (bool)view.GetValue(SyncCanExecuteProperty); } - - class AddCommandRoutingEffect : RoutingEffect - { - public AddCommandRoutingEffect() : base("AiForms." + nameof(AddCommand)) { } - } - } + internal class AddCommandRoutingEffect : AiRoutingEffectBase + { + public AddCommandRoutingEffect() : base("AiForms." + nameof(AddCommand)) { } + } } diff --git a/AiForms.Effects/AddDatePicker.cs b/AiForms.Effects/AddDatePicker.cs index 819c3b2..4be70bf 100644 --- a/AiForms.Effects/AddDatePicker.cs +++ b/AiForms.Effects/AddDatePicker.cs @@ -10,36 +10,20 @@ public class AddDatePicker public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(AddDatePicker), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); - } - - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new AddDatePickerRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is AddDatePickerRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } + return (bool?)view.GetValue(OnProperty); } public static readonly BindableProperty DateProperty = @@ -48,7 +32,8 @@ private static void OnOffChanged(BindableObject bindable, object oldValue, objec typeof(DateTime), typeof(AddDatePicker), default(DateTime), - defaultBindingMode:BindingMode.TwoWay + defaultBindingMode:BindingMode.TwoWay, + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetDate(BindableObject view, DateTime value) @@ -132,14 +117,12 @@ public static string GetTodayText(BindableObject view) { return (string)view.GetValue(TodayTextProperty); } + } - class AddDatePickerRoutingEffect : RoutingEffect + internal class AddDatePickerRoutingEffect : AiRoutingEffectBase + { + public AddDatePickerRoutingEffect() : base("AiForms." + nameof(AddDatePicker)) { - public AddDatePickerRoutingEffect() : base("AiForms." + nameof(AddDatePicker)) - { - - } } - } } diff --git a/AiForms.Effects/AddNumberPicker.cs b/AiForms.Effects/AddNumberPicker.cs index 72ac52b..37c5a4b 100644 --- a/AiForms.Effects/AddNumberPicker.cs +++ b/AiForms.Effects/AddNumberPicker.cs @@ -9,36 +9,20 @@ public static class AddNumberPicker public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(AddNumberPicker), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); - } - - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new AddNumberPickerRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is AddNumberPickerRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } + return (bool?)view.GetValue(OnProperty); } public static readonly BindableProperty MaxProperty = @@ -83,7 +67,8 @@ public static int GetMin(BindableObject view) typeof(int), typeof(AddNumberPicker), default(int), - BindingMode.TwoWay + BindingMode.TwoWay, + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetNumber(BindableObject view, int value) @@ -132,15 +117,12 @@ public static ICommand GetCommand(BindableObject view) { return (ICommand)view.GetValue(CommandProperty); } + } - - - class AddNumberPickerRoutingEffect : RoutingEffect + internal class AddNumberPickerRoutingEffect : AiRoutingEffectBase + { + public AddNumberPickerRoutingEffect() : base("AiForms." + nameof(AddNumberPicker)) { - public AddNumberPickerRoutingEffect() : base("AiForms." + nameof(AddNumberPicker)) - { - - } } } } diff --git a/AiForms.Effects/AddText.cs b/AiForms.Effects/AddText.cs index e98de9c..2639268 100644 --- a/AiForms.Effects/AddText.cs +++ b/AiForms.Effects/AddText.cs @@ -9,36 +9,20 @@ public static class AddText public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( "On", - typeof(bool), + typeof(bool?), typeof(AddText), - default(bool), - propertyChanged: OnOffChanged + null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); - } - - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new AddTextRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is AddTextRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } + return (bool?)view.GetValue(OnProperty); } public static readonly BindableProperty TextProperty = @@ -46,7 +30,8 @@ private static void OnOffChanged(BindableObject bindable, object oldValue, objec "Text", typeof(string), typeof(AddText), - default(string) + default(string), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetText(BindableObject view, string value) @@ -186,10 +171,11 @@ public static TextAlignment GetVerticalAlign(BindableObject view) return (TextAlignment)view.GetValue(VerticalAlignProperty); } + } - class AddTextRoutingEffect : RoutingEffect - { - public AddTextRoutingEffect() : base("AiForms." + nameof(AddText)) { } - } + + internal class AddTextRoutingEffect : AiRoutingEffectBase + { + public AddTextRoutingEffect() : base("AiForms." + nameof(AddText)) { } } } diff --git a/AiForms.Effects/AddTimePicker.cs b/AiForms.Effects/AddTimePicker.cs index 8f57ef1..60bedde 100644 --- a/AiForms.Effects/AddTimePicker.cs +++ b/AiForms.Effects/AddTimePicker.cs @@ -10,37 +10,22 @@ public static class AddTimePicker public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(AddTimePicker), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); + return (bool?)view.GetValue(OnProperty); } - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new AddTimePickerRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is AddTimePickerRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } - } public static readonly BindableProperty TimeProperty = BindableProperty.CreateAttached( @@ -48,7 +33,8 @@ private static void OnOffChanged(BindableObject bindable, object oldValue, objec typeof(TimeSpan), typeof(AddTimePicker), default(TimeSpan), - defaultBindingMode:BindingMode.TwoWay + defaultBindingMode:BindingMode.TwoWay, + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetTime(BindableObject view, TimeSpan value) @@ -98,12 +84,12 @@ public static ICommand GetCommand(BindableObject view) return (ICommand)view.GetValue(CommandProperty); } - class AddTimePickerRoutingEffect : RoutingEffect - { - public AddTimePickerRoutingEffect() : base("AiForms." + nameof(AddTimePicker)) - { + } - } + internal class AddTimePickerRoutingEffect : AiRoutingEffectBase + { + public AddTimePickerRoutingEffect() : base("AiForms." + nameof(AddTimePicker)) + { } } } diff --git a/AiForms.Effects/AddTouch.cs b/AiForms.Effects/AddTouch.cs index 44aff99..9bfdde5 100644 --- a/AiForms.Effects/AddTouch.cs +++ b/AiForms.Effects/AddTouch.cs @@ -9,42 +9,36 @@ public static class AddTouch public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( "On", - typeof(bool), + typeof(bool?), typeof(AddTouch), - default(bool), - propertyChanged: OnOffChanged + null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler, + propertyChanging: OnOffChanging ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); + return (bool?)view.GetValue(OnProperty); } - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) + + static void OnOffChanging(BindableObject bindable, object oldValue, object newValue) { - var view = bindable as View; - if (view == null) + if (!(bindable is View view)) return; if ((bool)newValue) { SetRecognizer(view, new TouchRecognizer()); - view.Effects.Add(new AddTouchRoutingEffect()); } else { - var toRemove = view.Effects.FirstOrDefault(e => e is AddTouchRoutingEffect); - if (toRemove != null) - { - view.Effects.Remove(toRemove); - SetRecognizer(view, null); - } - + view.ClearValue(RecognizerProperty); } } @@ -65,10 +59,12 @@ public static TouchRecognizer GetRecognizer(BindableObject view) { return (TouchRecognizer)view.GetValue(RecognizerProperty); } + } - class AddTouchRoutingEffect : RoutingEffect + internal class AddTouchRoutingEffect : AiRoutingEffectBase + { + public AddTouchRoutingEffect() : base("AiForms." + nameof(AddTouch)) { - public AddTouchRoutingEffect() : base("AiForms." + nameof(AddTouch)) { } } } } diff --git a/AiForms.Effects/AiForms.Effects.csproj b/AiForms.Effects/AiForms.Effects.csproj index f00f18e..1981c29 100644 --- a/AiForms.Effects/AiForms.Effects.csproj +++ b/AiForms.Effects/AiForms.Effects.csproj @@ -2,10 +2,14 @@ netstandard2.0 + 0.0.5-pre - + + + + \ No newline at end of file diff --git a/AiForms.Effects/AiRoutingEffectBase.cs b/AiForms.Effects/AiRoutingEffectBase.cs new file mode 100644 index 0000000..b720547 --- /dev/null +++ b/AiForms.Effects/AiRoutingEffectBase.cs @@ -0,0 +1,73 @@ +using System; +using Xamarin.Forms; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Diagnostics; + +namespace AiForms.Effects +{ + public class AiRoutingEffectBase : RoutingEffect + { + internal static class InstanceCreator + { + public static Func Create { get; } = CreateInstance(); + + static Func CreateInstance() + { + return Expression.Lambda>(Expression.New(typeof(TInstance))).Compile(); + } + } + + public static void AddEffectHandler(BindableObject bindable, object oldValue, object newValue) where TRoutingEffect :AiRoutingEffectBase + { + if (!EffectConfig.EnableTriggerProperty) return; + + if (!(bindable is VisualElement element)) return; + + AddEffect(element); + } + + public static void ToggleEffectHandler(BindableObject bindable, object oldValue, object newValue) where TRoutingEffect : AiRoutingEffectBase + { + if (!(bindable is VisualElement element)) return; + + var enabled = (bool?)newValue; + + if (!enabled.HasValue) return; + + if(enabled.Value) + { + AddEffect(element); + } + else + { + RemoveEffect(element); + } + } + + static void AddEffect(Element element) where T : AiRoutingEffectBase + { + if (!element.Effects.OfType().Any()) + { + element.Effects.Add(InstanceCreator.Create()); + } + } + + static void RemoveEffect(Element element) where T : AiRoutingEffectBase + { + var remove = element.Effects.OfType().FirstOrDefault(); + if (remove != null) + { + element.Effects.Remove(remove); + } + } + + + public string EffectId { get; } + public AiRoutingEffectBase(string effectId) : base(effectId) + { + EffectId = effectId; + } + } +} diff --git a/AiForms.Effects/AlterColor.cs b/AiForms.Effects/AlterColor.cs index d18a337..2ee5e65 100644 --- a/AiForms.Effects/AlterColor.cs +++ b/AiForms.Effects/AlterColor.cs @@ -9,48 +9,30 @@ public static class AlterColor public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(AlterColor), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); + return (bool?)view.GetValue(OnProperty); } - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as Element; - if (view == null) - return; - - if (!(view is Slider || view is Switch || view is Entry || view is Editor || view is Page)) { - return; - } - - if ((bool)newValue) { - view.Effects.Add(new AlterColorRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is AlterColorRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } - } public static readonly BindableProperty AccentProperty = BindableProperty.CreateAttached( "Accent", typeof(Color), typeof(AlterColor), - default(Color) + default(Color), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetAccent(BindableObject view, Color value) @@ -63,9 +45,10 @@ public static Color GetAccent(BindableObject view) return (Color)view.GetValue(AccentProperty); } - class AlterColorRoutingEffect : RoutingEffect - { - public AlterColorRoutingEffect() : base("AiForms." + nameof(AlterColor)) { } - } + } + + internal class AlterColorRoutingEffect : AiRoutingEffectBase + { + public AlterColorRoutingEffect() : base("AiForms." + nameof(AlterColor)) { } } } diff --git a/AiForms.Effects/AlterLineHeight.cs b/AiForms.Effects/AlterLineHeight.cs index a352ec9..867ef6d 100644 --- a/AiForms.Effects/AlterLineHeight.cs +++ b/AiForms.Effects/AlterLineHeight.cs @@ -8,36 +8,21 @@ public static class AlterLineHeight public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( "On", - typeof(bool), + typeof(bool?), typeof(AlterLineHeight), - false, - propertyChanged: OnOffChanged + null, + BindingMode.OneWay, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); - } - - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new LineHeightEffectRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is LineHeightEffectRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } + return (bool?)view.GetValue(OnProperty); } public static readonly BindableProperty MultipleProperty = @@ -45,7 +30,8 @@ private static void OnOffChanged(BindableObject bindable, object oldValue, objec "Multiple", typeof(double), typeof(AlterLineHeight), - default(double) + default(double), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetMultiple(BindableObject view, double value) @@ -59,12 +45,12 @@ public static double GetMultiple(BindableObject view) } - class LineHeightEffectRoutingEffect : RoutingEffect - { - public LineHeightEffectRoutingEffect() : base("AiForms." + nameof(AlterLineHeight)) - { + } - } + internal class AlterLineHeightRoutingEffect : AiRoutingEffectBase + { + public AlterLineHeightRoutingEffect() : base("AiForms." + nameof(AlterLineHeight)) + { } } } diff --git a/AiForms.Effects/Border.cs b/AiForms.Effects/Border.cs index f5d176e..d8c7f5d 100644 --- a/AiForms.Effects/Border.cs +++ b/AiForms.Effects/Border.cs @@ -9,44 +9,30 @@ public static class Border public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(Border), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); + return (bool?)view.GetValue(OnProperty); } - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - - if ((bool)newValue) { - view.Effects.Add(new BorderRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is BorderRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } - } public static readonly BindableProperty RadiusProperty = BindableProperty.CreateAttached( "Radius", typeof(double), typeof(Border), - default(double) + default(double), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetRadius(BindableObject view, double value) @@ -64,7 +50,8 @@ public static double GetRadius(BindableObject view) "Width", typeof(double), typeof(Border), - default(double) + default(double), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetWidth(BindableObject view, double value) @@ -95,11 +82,10 @@ public static Color GetColor(BindableObject view) return (Color)view.GetValue(ColorProperty); } - class BorderRoutingEffect : RoutingEffect - { - public BorderRoutingEffect() : base("AiForms." + nameof(Border)) { } - } - + } + internal class BorderRoutingEffect : AiRoutingEffectBase + { + public BorderRoutingEffect() : base("AiForms." + nameof(Border)) { } } } diff --git a/AiForms.Effects/EffectConfig.cs b/AiForms.Effects/EffectConfig.cs new file mode 100644 index 0000000..223441c --- /dev/null +++ b/AiForms.Effects/EffectConfig.cs @@ -0,0 +1,8 @@ +using System; +namespace AiForms.Effects +{ + public static class EffectConfig + { + public static bool EnableTriggerProperty { get; set; } = true; + } +} diff --git a/AiForms.Effects/Feedback.cs b/AiForms.Effects/Feedback.cs new file mode 100644 index 0000000..c524660 --- /dev/null +++ b/AiForms.Effects/Feedback.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using Xamarin.Forms; + +namespace AiForms.Effects +{ + public static class Feedback + { + public static readonly BindableProperty OnProperty = + BindableProperty.CreateAttached( + propertyName: "On", + returnType: typeof(bool?), + declaringType: typeof(Feedback), + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler + ); + + public static void SetOn(BindableObject view, bool? value) + { + view.SetValue(OnProperty, value); + } + + public static bool? GetOn(BindableObject view) + { + return (bool?)view.GetValue(OnProperty); + } + + public static readonly BindableProperty EffectColorProperty = + BindableProperty.CreateAttached( + "EffectColor", + typeof(Color), + typeof(Feedback), + Color.Transparent, + propertyChanged: AiRoutingEffectBase.AddEffectHandler + ); + + public static void SetEffectColor(BindableObject view, Color value) + { + view.SetValue(EffectColorProperty, value); + } + + public static Color GetEffectColor(BindableObject view) + { + return (Color)view.GetValue(EffectColorProperty); + } + + public static readonly BindableProperty EnableSoundProperty = + BindableProperty.CreateAttached( + "EnableSound", + typeof(bool), + typeof(Feedback), + false, + propertyChanged: AiRoutingEffectBase.AddEffectHandler + ); + + public static void SetEnableSound(BindableObject view, bool value) + { + view.SetValue(EnableSoundProperty, value); + } + public static bool GetEnableSound(BindableObject view) + { + return (bool)view.GetValue(EnableSoundProperty); + } + + } + + internal class FeedbackRoutingEffect : AiRoutingEffectBase + { + public FeedbackRoutingEffect() : base("AiForms." + nameof(Feedback)) + { + } + } +} diff --git a/AiForms.Effects/Floating.cs b/AiForms.Effects/Floating.cs new file mode 100644 index 0000000..847adbe --- /dev/null +++ b/AiForms.Effects/Floating.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Xamarin.Forms; + +namespace AiForms.Effects +{ + public static class Floating + { + public static readonly BindableProperty ContentProperty = + BindableProperty.CreateAttached( + "Content", + typeof(FloatingLayout), + typeof(Floating), + default(FloatingLayout), + propertyChanged: AiRoutingEffectBase.AddEffectHandler + ); + + public static void SetContent(BindableObject view, FloatingLayout value) + { + view.SetValue(ContentProperty, value); + } + + public static FloatingLayout GetContent(BindableObject view) + { + return (FloatingLayout)view.GetValue(ContentProperty); + } + } + + internal class FloatingRoutingEffect : AiRoutingEffectBase + { + public FloatingRoutingEffect() : base("AiForms." + nameof(Floating)) + { + } + } +} diff --git a/AiForms.Effects/FloatingLayout.cs b/AiForms.Effects/FloatingLayout.cs new file mode 100644 index 0000000..7ba9299 --- /dev/null +++ b/AiForms.Effects/FloatingLayout.cs @@ -0,0 +1,110 @@ +using System; +using Xamarin.Forms; +using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Collections; + +namespace AiForms.Effects +{ + public class FloatingLayout :View, IList + { + public FloatingLayout() + { + } + + protected override void OnParentSet() + { + base.OnParentSet(); + + foreach(var child in _children) + { + child.Parent = Parent; + } + } + + public void LayoutChildren() + { + foreach(var child in _children) + { + LayoutChild(child); + } + } + + public void LayoutChild(FloatingView child) + { + var sizeRequest = child.Measure(Width, Height); + + var finalW = child.HorizontalLayoutAlignment == LayoutAlignment.Fill ? Width : sizeRequest.Request.Width; + var finalH = child.VerticalLayoutAlignment == LayoutAlignment.Fill ? Height : sizeRequest.Request.Height; + + child.Layout(new Rectangle(0, 0, finalW,finalH)); + } + + ObservableCollection _children = new ObservableCollection(); + + public FloatingView this[int index] { + get { return _children[index]; } + set { _children[index] = value; } + } + + public int Count => _children.Count; + + public bool IsReadOnly => false; + + public void Add(FloatingView item) + { + item.Parent = Parent; + _children.Add(item); + } + + public void Clear() + { + _children.Clear(); + } + + public bool Contains(FloatingView item) + { + return _children.Contains(item); + } + + public void CopyTo(FloatingView[] array, int arrayIndex) + { + _children.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return _children.GetEnumerator(); + } + + public int IndexOf(FloatingView item) + { + return _children.IndexOf(item); + } + + public void Insert(int index, FloatingView item) + { + item.Parent = Parent; + _children.Insert(index, item); + } + + public bool Remove(FloatingView item) + { + item.Parent = null; + return _children.Remove(item); + } + + public void RemoveAt(int index) + { + _children[index].Parent = null; + _children.RemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _children.GetEnumerator(); + } + + + } +} diff --git a/AiForms.Effects/FloatingView.cs b/AiForms.Effects/FloatingView.cs new file mode 100644 index 0000000..a78b231 --- /dev/null +++ b/AiForms.Effects/FloatingView.cs @@ -0,0 +1,96 @@ +using System; +using Xamarin.Forms; + +namespace AiForms.Effects +{ + public class FloatingView : ContentView + { + public FloatingView() + { + CompressedLayout.SetIsHeadless(this, true); + } + + public static BindableProperty VerticalLayoutAlignmentProperty = + BindableProperty.Create( + nameof(VerticalLayoutAlignment), + typeof(LayoutAlignment), + typeof(FloatingView), + LayoutAlignment.Center, + defaultBindingMode: BindingMode.OneWay + ); + + public LayoutAlignment VerticalLayoutAlignment + { + get { return (LayoutAlignment)GetValue(VerticalLayoutAlignmentProperty); } + set { SetValue(VerticalLayoutAlignmentProperty, value); } + } + + public static BindableProperty HorizontalLayoutAlignmentProperty = + BindableProperty.Create( + nameof(HorizontalLayoutAlignment), + typeof(LayoutAlignment), + typeof(FloatingView), + LayoutAlignment.Center, + defaultBindingMode: BindingMode.OneWay + ); + + public LayoutAlignment HorizontalLayoutAlignment + { + get { return (LayoutAlignment)GetValue(HorizontalLayoutAlignmentProperty); } + set { SetValue(HorizontalLayoutAlignmentProperty, value); } + } + + public static BindableProperty OffsetXProperty = + BindableProperty.Create( + nameof(OffsetX), + typeof(int), + typeof(FloatingView), + default(int), + defaultBindingMode: BindingMode.OneWay + ); + + public int OffsetX + { + get { return (int)GetValue(OffsetXProperty); } + set { SetValue(OffsetXProperty, value); } + } + + public static BindableProperty OffsetYProperty = + BindableProperty.Create( + nameof(OffsetY), + typeof(int), + typeof(FloatingView), + default(int), + defaultBindingMode: BindingMode.OneWay + ); + + public int OffsetY + { + get { return (int)GetValue(OffsetYProperty); } + set { SetValue(OffsetYProperty, value); } + } + + public static BindableProperty HiddenProperty = + BindableProperty.Create( + nameof(Hidden), + typeof(bool), + typeof(FloatingView), + default(bool), + defaultBindingMode: BindingMode.OneWay, + propertyChanged: (bindable, oldValue, newValue) => { + bindable.SetValue(OpacityProperty, (bool)newValue ? 0 : 1); + bindable.SetValue(InputTransparentProperty, (bool)newValue); + } + ); + + public bool Hidden + { + get { return (bool)GetValue(HiddenProperty); } + set { SetValue(HiddenProperty, value); } + } + + // kill exists properties. + private new LayoutOptions VerticalOptions { get; set; } + private new LayoutOptions HorizontalOptions { get; set; } + } +} diff --git a/AiForms.Effects/Placeholder.cs b/AiForms.Effects/Placeholder.cs index 3ec3f77..dafd614 100644 --- a/AiForms.Effects/Placeholder.cs +++ b/AiForms.Effects/Placeholder.cs @@ -9,46 +9,30 @@ public static class Placeholder public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(Placeholder), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); + return (bool?)view.GetValue(OnProperty); } - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - if(!(view is Editor)) - return; - - if ((bool)newValue) { - view.Effects.Add(new PlaceholderRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is PlaceholderRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } - } public static readonly BindableProperty TextProperty = BindableProperty.CreateAttached( "Text", typeof(string), typeof(Placeholder), - default(string) + default(string), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetText(BindableObject view, string value) @@ -78,10 +62,10 @@ public static Color GetColor(BindableObject view) { return (Color)view.GetValue(ColorProperty); } + } - class PlaceholderRoutingEffect : RoutingEffect - { - public PlaceholderRoutingEffect() : base("AiForms." + nameof(Placeholder)) { } - } + internal class PlaceholderRoutingEffect : AiRoutingEffectBase + { + public PlaceholderRoutingEffect() : base("AiForms." + nameof(Placeholder)) { } } } diff --git a/AiForms.Effects/Properties/AssemblyInfo.cs b/AiForms.Effects/Properties/AssemblyInfo.cs index 1e2d556..4148333 100644 --- a/AiForms.Effects/Properties/AssemblyInfo.cs +++ b/AiForms.Effects/Properties/AssemblyInfo.cs @@ -2,4 +2,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("AiForms.Effects.iOS")] -[assembly: InternalsVisibleTo("AiForms.Effects.Droid")] \ No newline at end of file +[assembly: InternalsVisibleTo("AiForms.Effects.Droid")] +[assembly: InternalsVisibleTo("AiEffects.TestApp")] \ No newline at end of file diff --git a/AiForms.Effects/SizeToFit.cs b/AiForms.Effects/SizeToFit.cs index e4997a2..dbcffd7 100644 --- a/AiForms.Effects/SizeToFit.cs +++ b/AiForms.Effects/SizeToFit.cs @@ -9,40 +9,20 @@ public static class SizeToFit public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( "On", - typeof(bool), + typeof(bool?), typeof(SizeToFit), - default(bool), - propertyChanged: OnOffChanged + null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); - } - - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - if (!(view is Label)) - return; - - if ((bool)newValue) - { - view.Effects.Add(new SizeToFitRoutingEffect()); - } - else - { - var toRemove = view.Effects.FirstOrDefault(e => e is SizeToFitRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } + return (bool?)view.GetValue(OnProperty); } public static readonly BindableProperty CanExpandProperty = @@ -63,9 +43,10 @@ public static bool GetCanExpand(BindableObject view) return (bool)view.GetValue(CanExpandProperty); } - class SizeToFitRoutingEffect : RoutingEffect - { - public SizeToFitRoutingEffect() : base("AiForms." + nameof(SizeToFit)) { } - } + } + + internal class SizeToFitRoutingEffect : AiRoutingEffectBase + { + public SizeToFitRoutingEffect() : base("AiForms." + nameof(SizeToFit)) { } } } diff --git a/AiForms.Effects/ToFlatButton.cs b/AiForms.Effects/ToFlatButton.cs index 6c5a642..4f23cb5 100644 --- a/AiForms.Effects/ToFlatButton.cs +++ b/AiForms.Effects/ToFlatButton.cs @@ -8,20 +8,20 @@ public static class ToFlatButton public static readonly BindableProperty OnProperty = BindableProperty.CreateAttached( propertyName: "On", - returnType: typeof(bool), + returnType: typeof(bool?), declaringType: typeof(ToFlatButton), - defaultValue: false, - propertyChanged: OnOffChanged + defaultValue: null, + propertyChanged: AiRoutingEffectBase.ToggleEffectHandler ); - public static void SetOn(BindableObject view, bool value) + public static void SetOn(BindableObject view, bool? value) { view.SetValue(OnProperty, value); } - public static bool GetOn(BindableObject view) + public static bool? GetOn(BindableObject view) { - return (bool)view.GetValue(OnProperty); + return (bool?)view.GetValue(OnProperty); } @@ -30,7 +30,8 @@ public static bool GetOn(BindableObject view) "RippleColor", typeof(Color), typeof(ToFlatButton), - default(Color) + default(Color), + propertyChanged: AiRoutingEffectBase.AddEffectHandler ); public static void SetRippleColor(BindableObject view, Color value) @@ -43,27 +44,10 @@ public static Color GetRippleColor(BindableObject view) return (Color)view.GetValue(RippleColorProperty); } - private static void OnOffChanged(BindableObject bindable, object oldValue, object newValue) - { - var view = bindable as View; - if (view == null) - return; - if (!(view is Button)) - return; - - if ((bool)newValue) { - view.Effects.Add(new ToFlatButtonRoutingEffect()); - } - else { - var toRemove = view.Effects.FirstOrDefault(e => e is ToFlatButtonRoutingEffect); - if (toRemove != null) - view.Effects.Remove(toRemove); - } - } + } - class ToFlatButtonRoutingEffect : RoutingEffect - { - public ToFlatButtonRoutingEffect() : base("AiForms." + nameof(ToFlatButton)) { } - } + internal class ToFlatButtonRoutingEffect : AiRoutingEffectBase + { + public ToFlatButtonRoutingEffect() : base("AiForms." + nameof(ToFlatButton)) { } } } diff --git a/README-ja.md b/README-ja.md index 8964c58..54fcbdf 100644 --- a/README-ja.md +++ b/README-ja.md @@ -3,14 +3,16 @@ AiForms.Effect は Android と iOS に特化することにより、標準のコントロールをより便利にするための機能を提供する Xamarin.Forms の Effectsライブラリです。 ## 機能 +* [Floating](#floating) + * ページの前面の任意の場所に複数のフローティングな要素(FABなど)を配置します。 +* [Feedback](#feedback) + * タッチフィードバック効果(色やシステム音)を追加。コマンドは含みません。 * [AddTouch](#addtouch) * 各種タッチイベントを追加 * [SizeToFit](#sizetofit) * フォントサイズをLabelの大きさに調整 * [Border](#border) * 罫線の追加. -* [Placeholder](#placeholder) - * Editor に Placeholder を追加. * [ToFlatButton](#toflatbutton) * ボタンをフラットにする (Android) * [AddText](#addtext) @@ -27,7 +29,38 @@ AiForms.Effect は Android と iOS に特化することにより、標準のコ * LabelとEditorの行の高さを変更 * [AlterColor](#altercolor) * 通常変えられない箇所の色を変える +* [Placeholder](#placeholder) + * Editor に Placeholder を追加. + +## **トリガープロパティ (1.4.0~)** + +EffectのOn・OffはそれぞれのOnプロパティで操作していましたが、ver.1.4.0よりEffectの主要なプロパティを設定するだけで起動できるようになりました。 +このプロパティはトリガープロパティとします。 +例えば、AddCommandの場合は Command や LongCommand がトリガープロパティになります。このドキュメントに該当のプロパティには trigger と記載しています。 + +### 旧 (~1.3.1) + +```xml +