diff --git a/Pixiview.Android/Pixiview.Android.csproj b/Pixiview.Android/Pixiview.Android.csproj
index fc31ec3..c0e89aa 100644
--- a/Pixiview.Android/Pixiview.Android.csproj
+++ b/Pixiview.Android/Pixiview.Android.csproj
@@ -34,14 +34,15 @@
None
+
true
bin\Release
prompt
4
true
false
-true
-arm64-v8a
+ true
+ arm64-v8a
@@ -70,6 +71,14 @@
+
+
+
+
+
+
+
+
@@ -181,6 +190,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -193,6 +218,8 @@
+
+
diff --git a/Pixiview.Android/Renderers/AppShellRenderer.cs b/Pixiview.Android/Renderers/AppShellRenderer.cs
new file mode 100644
index 0000000..55d5bf9
--- /dev/null
+++ b/Pixiview.Android/Renderers/AppShellRenderer.cs
@@ -0,0 +1,23 @@
+#if TODO
+using Android.Content;
+using Pixiview.Droid.Renderers;
+using Pixiview.Droid.Renderers.AppShellSection;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(Shell), typeof(AppShellRenderer))]
+namespace Pixiview.Droid.Renderers
+{
+ public class AppShellRenderer : ShellRenderer
+ {
+ public AppShellRenderer(Context context) : base(context)
+ {
+ }
+
+ protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
+ {
+ return new AppShellBottomNavViewAppearanceTracker(this, shellItem);
+ }
+ }
+}
+#endif
diff --git a/Pixiview.Android/Renderers/AppShellSection/AppColorChangeRevealDrawable.cs b/Pixiview.Android/Renderers/AppShellSection/AppColorChangeRevealDrawable.cs
new file mode 100644
index 0000000..a09acf8
--- /dev/null
+++ b/Pixiview.Android/Renderers/AppShellSection/AppColorChangeRevealDrawable.cs
@@ -0,0 +1,98 @@
+using Android.Animation;
+using Android.Graphics;
+using Android.Graphics.Drawables;
+using System;
+using AColor = Android.Graphics.Color;
+
+namespace Pixiview.Droid.Renderers.AppShellSection
+{
+ public class AppColorChangeRevealDrawable : AnimationDrawable
+ {
+ readonly Point _center;
+ float _progress;
+ bool _disposed;
+ ValueAnimator _animator;
+
+ internal AColor StartColor { get; }
+ internal AColor EndColor { get; }
+
+ public AppColorChangeRevealDrawable(AColor startColor, AColor endColor, Point center) : base()
+ {
+ StartColor = startColor;
+ EndColor = endColor;
+
+ if (StartColor != EndColor)
+ {
+ _animator = ValueAnimator.OfFloat(0, 1);
+ _animator.SetInterpolator(new Android.Views.Animations.DecelerateInterpolator());
+ _animator.SetDuration(500);
+ _animator.Update += OnUpdate;
+ _animator.Start();
+ _center = center;
+ }
+ else
+ {
+ _progress = 1;
+ }
+ }
+
+ public override void Draw(Canvas canvas)
+ {
+ if (_disposed)
+ return;
+
+ if (_progress == 1)
+ {
+ canvas.DrawColor(EndColor);
+ return;
+ }
+
+ canvas.DrawColor(StartColor);
+ var bounds = Bounds;
+ float centerX = _center.X;
+ float centerY = _center.Y;
+
+ float width = bounds.Width();
+ float distanceFromCenter = Math.Abs(width / 2 - _center.X);
+ float radius = (width / 2 + distanceFromCenter) * 1.1f;
+
+ var paint = new Paint
+ {
+ Color = EndColor
+ };
+
+ canvas.DrawCircle(centerX, centerY, radius * _progress, paint);
+ }
+
+ void OnUpdate(object sender, ValueAnimator.AnimatorUpdateEventArgs e)
+ {
+ _progress = (float)e.Animation.AnimatedValue;
+
+ InvalidateSelf();
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ if (_animator != null)
+ {
+ _animator.Update -= OnUpdate;
+
+ _animator.Cancel();
+
+ _animator.Dispose();
+
+ _animator = null;
+ }
+ }
+
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/Pixiview.Android/Renderers/AppShellSection/AppShellBottomNavViewAppearanceTracker.cs b/Pixiview.Android/Renderers/AppShellSection/AppShellBottomNavViewAppearanceTracker.cs
new file mode 100644
index 0000000..1b4be95
--- /dev/null
+++ b/Pixiview.Android/Renderers/AppShellSection/AppShellBottomNavViewAppearanceTracker.cs
@@ -0,0 +1,170 @@
+using Android.Content.Res;
+using Android.Graphics.Drawables;
+#if __ANDROID_29__
+using AndroidX.Core.Widget;
+using Google.Android.Material.BottomNavigation;
+#else
+using Android.Support.Design.Internal;
+using Android.Support.Design.Widget;
+#endif
+using System;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+using AColor = Android.Graphics.Color;
+using R = Android.Resource;
+
+namespace Pixiview.Droid.Renderers.AppShellSection
+{
+ public class AppShellBottomNavViewAppearanceTracker : IShellBottomNavViewAppearanceTracker
+ {
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "")]
+ IShellContext _shellContext;
+ ShellItem _shellItem;
+ ColorStateList _defaultList;
+ bool _disposed;
+ ColorStateList _colorStateList;
+
+ public AppShellBottomNavViewAppearanceTracker(IShellContext shellContext, ShellItem shellItem)
+ {
+ _shellItem = shellItem;
+ _shellContext = shellContext;
+ }
+
+ public virtual void ResetAppearance(BottomNavigationView bottomView)
+ {
+ if (_defaultList != null)
+ {
+ bottomView.ItemTextColor = _defaultList;
+ bottomView.ItemIconTintList = _defaultList;
+ }
+
+ SetBackgroundColor(bottomView, Color.White);
+ }
+
+ public virtual void SetAppearance(BottomNavigationView bottomView, IShellAppearanceElement appearance)
+ {
+ IShellAppearanceElement controller = appearance;
+ var backgroundColor = controller.EffectiveTabBarBackgroundColor;
+ var foregroundColor = controller.EffectiveTabBarForegroundColor;
+ var disabledColor = controller.EffectiveTabBarDisabledColor;
+ var unselectedColor = controller.EffectiveTabBarUnselectedColor; // currently unused
+ var titleColor = controller.EffectiveTabBarTitleColor;
+
+ if (_defaultList == null)
+ {
+#if __ANDROID_28__
+ _defaultList = bottomView.ItemTextColor ?? bottomView.ItemIconTintList
+ ?? MakeColorStateList(titleColor.ToAndroid().ToArgb(), disabledColor.ToAndroid().ToArgb(), foregroundColor.ToAndroid().ToArgb());
+#else
+ _defaultList = bottomView.ItemTextColor ?? bottomView.ItemIconTintList;
+#endif
+ }
+
+ _colorStateList = MakeColorStateList(titleColor, disabledColor, foregroundColor);
+ bottomView.ItemTextColor = _colorStateList;
+ bottomView.ItemIconTintList = _colorStateList;
+
+ SetBackgroundColor(bottomView, backgroundColor);
+ }
+
+ protected virtual void SetBackgroundColor(BottomNavigationView bottomView, Color color)
+ {
+ var oldBackground = bottomView.Background;
+ var colorDrawable = oldBackground as ColorDrawable;
+ var colorChangeRevealDrawable = oldBackground as AppColorChangeRevealDrawable;
+ AColor lastColor = colorChangeRevealDrawable?.EndColor ?? colorDrawable?.Color ?? Color.Default.ToAndroid();
+ AColor newColor;
+
+ if (color == Color.Default)
+ newColor = Color.White.ToAndroid();
+ else
+ newColor = color.ToAndroid();
+
+ if (!(bottomView.GetChildAt(0) is BottomNavigationMenuView menuView))
+ {
+ if (colorDrawable != null && lastColor == newColor)
+ return;
+
+ if (lastColor != newColor || colorDrawable == null)
+ {
+ bottomView.SetBackground(new ColorDrawable(newColor));
+ }
+ }
+ else
+ {
+ if (colorChangeRevealDrawable != null && lastColor == newColor)
+ return;
+
+ var index = ((IShellItemController)_shellItem).GetItems().IndexOf(_shellItem.CurrentItem);
+ var menu = bottomView.Menu;
+ index = Math.Min(index, menu.Size() - 1);
+
+ var child = menuView.GetChildAt(index);
+ if (child == null)
+ return;
+
+ var touchPoint = new Android.Graphics.Point(child.Left + (child.Right - child.Left) / 2, child.Top + (child.Bottom - child.Top) / 2);
+
+ bottomView.SetBackground(new AppColorChangeRevealDrawable(lastColor, newColor, touchPoint));
+ }
+ }
+
+ ColorStateList MakeColorStateList(Color titleColor, Color disabledColor, Color unselectedColor)
+ {
+ var disabledInt = disabledColor.IsDefault ?
+ _defaultList.GetColorForState(new[] { -R.Attribute.StateEnabled }, AColor.Gray) :
+ disabledColor.ToAndroid().ToArgb();
+
+ var checkedInt = titleColor.IsDefault ?
+ _defaultList.GetColorForState(new[] { R.Attribute.StateChecked }, AColor.Black) :
+ titleColor.ToAndroid().ToArgb();
+
+ var defaultColor = unselectedColor.IsDefault ?
+ _defaultList.DefaultColor :
+ unselectedColor.ToAndroid().ToArgb();
+
+ return MakeColorStateList(checkedInt, disabledInt, defaultColor);
+ }
+
+ ColorStateList MakeColorStateList(int titleColorInt, int disabledColorInt, int defaultColor)
+ {
+ var states = new int[][] {
+ new int[] { -R.Attribute.StateEnabled },
+ new int[] {R.Attribute.StateChecked },
+ new int[] { }
+ };
+
+ var colors = new[] { disabledColorInt, titleColorInt, defaultColor };
+
+ return new ColorStateList(states, colors);
+ }
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+
+ if (disposing)
+ {
+ _defaultList?.Dispose();
+ _colorStateList?.Dispose();
+
+ _shellItem = null;
+ _shellContext = null;
+ _defaultList = null;
+ _colorStateList = null;
+ }
+ }
+
+ #endregion IDisposable
+ }
+}
diff --git a/Pixiview.Android/Renderers/BlurryPanelRenderer.cs b/Pixiview.Android/Renderers/BlurryPanelRenderer.cs
index a449b31..5e455d3 100644
--- a/Pixiview.Android/Renderers/BlurryPanelRenderer.cs
+++ b/Pixiview.Android/Renderers/BlurryPanelRenderer.cs
@@ -17,7 +17,14 @@ namespace Pixiview.Droid.Renderers
{
base.OnElementChanged(e);
- SetBackgroundColor(Color.Black.MultiplyAlpha(.92).ToAndroid());
+ if (e.NewElement != null)
+ {
+ var color = e.NewElement.BackgroundColor;
+ if (!color.IsDefault)
+ {
+ SetBackgroundColor(color.MultiplyAlpha(.94).ToAndroid());
+ }
+ }
}
}
}
diff --git a/Pixiview.Android/Renderers/CardViewRenderer.cs b/Pixiview.Android/Renderers/CardViewRenderer.cs
index e9c6175..d930cc4 100644
--- a/Pixiview.Android/Renderers/CardViewRenderer.cs
+++ b/Pixiview.Android/Renderers/CardViewRenderer.cs
@@ -1,6 +1,5 @@
using Android.Content;
using Android.Graphics;
-using Android.Views;
using Pixiview.Droid.Renderers;
using Pixiview.UI;
using Xamarin.Forms;
@@ -11,8 +10,6 @@ namespace Pixiview.Droid.Renderers
{
public class CardViewRenderer : VisualElementRenderer
{
- ViewOutlineProvider original;
-
public CardViewRenderer(Context context) : base(context)
{
}
@@ -27,39 +24,19 @@ namespace Pixiview.Droid.Renderers
var radius = element.CornerRadius;
if (radius > 0)
{
- original = OutlineProvider;
- OutlineProvider = new CornerRadiusOutlineProvider(element, radius);
- ClipToOutline = true;
+ //var scale = Resources.DisplayMetrics.Density;
+ //OutlineProvider = new CornerRadiusOutlineProvider(element, radius, scale);
+ //ClipToOutline = true;
+
+ var density = Resources.DisplayMetrics.Density;
+
+ Elevation = (float)(element.ShadowOffset.Height + 2) * density;
+
+ var drawable = new RoundCornerDrawable(radius * density);
+ drawable.SetColorFilter(element.BackgroundColor.ToAndroid(), PorterDuff.Mode.Src);
+ ((Android.Views.View)this).SetBackground(drawable);
}
}
}
-
- protected override void OnDetachedFromWindow()
- {
- OutlineProvider = original;
- base.OnDetachedFromWindow();
- }
-
- private class CornerRadiusOutlineProvider : ViewOutlineProvider
- {
- private readonly Element element;
- private readonly float radius;
-
- public CornerRadiusOutlineProvider(Element from, float r)
- {
- element = from;
- radius = r;
- }
-
- public override void GetOutline(Android.Views.View view, Outline outline)
- {
- var scale = view.Resources.DisplayMetrics.Density;
- var width = (double)element.GetValue(VisualElement.WidthProperty) * scale;
- var height = (double)element.GetValue(VisualElement.HeightProperty) * scale;
-
- var rect = new Rect(0, 0, (int)width, (int)height);
- outline.SetRoundRect(rect, radius * scale);
- }
- }
}
}
diff --git a/Pixiview.Android/Renderers/OptionEntryRenderer.cs b/Pixiview.Android/Renderers/OptionEntryRenderer.cs
new file mode 100644
index 0000000..71f6527
--- /dev/null
+++ b/Pixiview.Android/Renderers/OptionEntryRenderer.cs
@@ -0,0 +1,28 @@
+using Android.Content;
+using Android.Graphics.Drawables;
+using Pixiview.Droid.Renderers;
+using Pixiview.UI;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(OptionEntry), typeof(OptionEntryRenderer))]
+namespace Pixiview.Droid.Renderers
+{
+ public class OptionEntryRenderer : EntryRenderer
+ {
+ public OptionEntryRenderer(Context context) : base(context)
+ {
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs e)
+ {
+ base.OnElementChanged(e);
+
+ if (e.NewElement != null)
+ {
+ var drawable = new ColorDrawable(e.NewElement.BackgroundColor.ToAndroid());
+ Control.SetBackground(drawable);
+ }
+ }
+ }
+}
diff --git a/Pixiview.Android/Renderers/RoundImageRenderer.cs b/Pixiview.Android/Renderers/RoundImageRenderer.cs
new file mode 100644
index 0000000..a09cb1b
--- /dev/null
+++ b/Pixiview.Android/Renderers/RoundImageRenderer.cs
@@ -0,0 +1,60 @@
+using Android.Content;
+using Android.Graphics;
+using Pixiview.Droid.Renderers;
+using Pixiview.UI;
+using Xamarin.Forms;
+
+[assembly: ExportRenderer(typeof(RoundImage), typeof(RoundImageRenderer))]
+namespace Pixiview.Droid.Renderers
+{
+ public class RoundImageRenderer : Xamarin.Forms.Platform.Android.FastRenderers.ImageRenderer
+ {
+ public RoundImageRenderer(Context context) : base(context)
+ {
+ }
+
+ public override void SetBackgroundColor(Android.Graphics.Color color)
+ {
+ // nothing
+ }
+
+ protected override void OnDraw(Canvas canvas)
+ {
+ if (Element is RoundImage image)
+ {
+ var radius = image.CornerRadius;
+ var mask = image.CornerMasks;
+ if (radius > 0 && mask != CornerMask.None)
+ {
+ var r = Resources.DisplayMetrics.Density * radius;
+
+ var radii = new float[8];
+ if ((mask & CornerMask.LeftTop) == CornerMask.LeftTop)
+ {
+ radii[0] = radii[1] = r;
+ }
+ if ((mask & CornerMask.RightTop) == CornerMask.RightTop)
+ {
+ radii[2] = radii[3] = r;
+ }
+ if ((mask & CornerMask.RightBottom) == CornerMask.RightBottom)
+ {
+ radii[4] = radii[5] = r;
+ }
+ if ((mask & CornerMask.LeftBottom) == CornerMask.LeftBottom)
+ {
+ radii[6] = radii[7] = r;
+ }
+
+ var path = new Path();
+ int width = Width;
+ int height = Height;
+ path.AddRoundRect(new RectF(0, 0, width, height), radii, Path.Direction.Cw);
+ canvas.ClipPath(path);
+ }
+ }
+
+ base.OnDraw(canvas);
+ }
+ }
+}
diff --git a/Pixiview.Android/Renderers/RoundLabelRenderer.cs b/Pixiview.Android/Renderers/RoundLabelRenderer.cs
new file mode 100644
index 0000000..b040aff
--- /dev/null
+++ b/Pixiview.Android/Renderers/RoundLabelRenderer.cs
@@ -0,0 +1,70 @@
+using Android.Content;
+using Android.Graphics;
+using Android.Graphics.Drawables;
+using Pixiview.Droid.Renderers;
+using Pixiview.UI;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.Android;
+
+[assembly: ExportRenderer(typeof(RoundLabel), typeof(RoundLabelRenderer))]
+namespace Pixiview.Droid.Renderers
+{
+ public class RoundLabelRenderer : Xamarin.Forms.Platform.Android.FastRenderers.LabelRenderer
+ {
+ public RoundLabelRenderer(Context context) : base(context)
+ {
+ }
+
+ protected override void OnElementChanged(ElementChangedEventArgs