feature: Android renderers

This commit is contained in:
2020-05-14 11:13:23 +08:00
parent a4caf325b0
commit f6dbec2fda
14 changed files with 557 additions and 43 deletions

View File

@ -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);
}
}
}

View File

@ -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 = "<Pending>")]
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
}
}