關於如何通過自定義渲染器在Xamarin Forms中製作Satellite菜單的集成
首先我創建了一些適合我應用程序的動畫菜單,我認爲這對你們有用。
Xamarin動畫菜單效果圖:
它是完整的C#代碼,沒有XAML,Page級爲ContentPage,並且是完全可編輯的。代碼有些雜亂,如果你想修改它或使用它,只要修改你想要的那部分即可:
Xamarin Forms動畫菜單
代碼由 Julien Chateau提供
源碼開放歡迎大家進行優化
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace DynamicMenus
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class DynamicMenus : ContentPage
{
Button[] Buttons;
Button menubutton;
Label texte;
Label titre;
DateTime beginTime;
AbsoluteLayout absoluteLayout;
bool isCurrentPage;
bool okMove = false;
float each = 0.0f;
float eachinv = 0.0f;
double seconds = 0;
double offset = 0;
double RatioScreen;
double delta = frac;
static double frac = 0.25; // Fraction de seconde to expand.
string TypeMenu = "Dropdown"; // DropDown, Arc, Side ...
public DynamicMenus()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, false);
Buttons = new Button[] {
new Button{ Text = "a", Image = "MenuDeroulant/icon1.png", },
new Button{ Text = "b", Image = "MenuDeroulant/icon2.png", },
new Button{ Text = "c", Image = "MenuDeroulant/icon3.png", },
new Button{ Text = "d", Image = "MenuDeroulant/icon4.png", },
new Button{ Text = "e", Image = "MenuDeroulant/icon5.png", }
};
//* Each button click refers to OnButtonClicked *//s
for (int i = 0; i < Buttons.Length; i++)
{
Buttons[i].Clicked += OnButtonClicked;
}
texte = new Label
{
Text = "Dropdown",
Font = Font.SystemFontOfSize(NamedSize.Large),
TextColor = Color.Maroon,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand,
HeightRequest = 200
};
titre = new Label
{
Text = "Animated Buttons",
Font = Font.SystemFontOfSize(NamedSize.Large),
HorizontalOptions = LayoutOptions.Center,
TextColor = Color.Maroon,
VerticalOptions = LayoutOptions.Start,
HeightRequest = 30
};
menubutton = new Button
{
Text = "+",
Font = Font.SystemFontOfSize(NamedSize.Medium),
FontAttributes = FontAttributes.Bold,
BackgroundColor = Color.Transparent,
//BorderWidth = 2,
//BorderColor = Color.White,
TextColor = Color.White,
Image = "MenuDeroulant/menu.png",
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
Opacity = 0.95,
};
menubutton.Clicked += OnButtonClicked;
absoluteLayout = new AbsoluteLayout
{
BackgroundColor = Color.Blue.WithLuminosity(0.8),
VerticalOptions = LayoutOptions.FillAndExpand
};
foreach (Button but in Buttons)
{
absoluteLayout.Children.Add(but);
AbsoluteLayout.SetLayoutFlags(but,
AbsoluteLayoutFlags.PositionProportional);
}
absoluteLayout.Children.Add(menubutton);
AbsoluteLayout.SetLayoutFlags(menubutton,
AbsoluteLayoutFlags.PositionProportional);
absoluteLayout.Children.Add(texte);
AbsoluteLayout.SetLayoutFlags(texte,
AbsoluteLayoutFlags.PositionProportional);
AbsoluteLayout.SetLayoutBounds(texte,
new Rectangle(0.5, 0.5,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
this.Content = new StackLayout
{
Children = {
titre,
absoluteLayout
}
};
}
void OnButtonClicked(object sender, EventArgs e)
{
isCurrentPage = true;
//* Button Menu *//
if (sender == menubutton)
{
//* Sub buttons of the Menu *//
//Just an example of action
}
else if (sender == Buttons[4])
{
delta = frac;
menubutton.Text = "g";
TypeMenu = "Dropdown";
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(1, 0,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
else if (sender == Buttons[3])
{
delta = frac;
menubutton.Text = "s";
TypeMenu = "Side";
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(1, 0,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
else if (sender == Buttons[2])
{
delta = frac;
menubutton.Text = "i";
TypeMenu = "Arc";
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(1, 0,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
else if (sender == Buttons[1])
{
delta = frac;
menubutton.Text = "d";
TypeMenu = "DemiCircle";
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(0.5, 1,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
else if (sender == Buttons[0])
{
delta = frac;
menubutton.Text = "s";
TypeMenu = "FlatBottom";
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(0.5, 1,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
texte.Text = TypeMenu;
ExpandContractMenu();
}
void ExpandContractMenu()
{
RatioScreen = absoluteLayout.Height / absoluteLayout.Width;
Debug.WriteLine("\nH : " + absoluteLayout.Height + "\nW : " + absoluteLayout.Width + "\nRatio : " + RatioScreen);
if (!okMove)
{
okMove = true;
//* delta start value (here 0,25) is half the time required to expand or contract *//
if (delta == 0)
{
delta = frac;
}
else
{
delta = 0;
}
seconds = 0;
offset = 0;
beginTime = DateTime.Now;
Device.StartTimer(TimeSpan.FromSeconds(1.0 / 60), () => {
if (okMove && seconds < frac)
{
if (TypeMenu == "Dropdown")
{
seconds = Math.Min((DateTime.Now - beginTime).TotalSeconds, frac);
offset = seconds + delta;
//* convert linear transition to sinusoid *//
//* Multiply PI by twice the time required to expand or contract *//
//* substract half a delta from offset *//
//* divide the whole thing by twice the screen height division you want (2 for the whole screen, 4 for half, 8 for a quarter...) *//
// http://www.abhortsoft.hu/functionvisualizer/functionvisualizer.html
double offsetB = (Math.Sin(4 * Math.PI * (offset - 0.125)) + 1) / 4;
for (int i = 0; i < Buttons.Length; i++)
{
float div = 1 / (float)Buttons.Length; // Gives a fraction of distance.
each = (i * div); // Assign that fraction multiplied by each place in the collection
eachinv = Math.Abs(each - Buttons.Length); // reversed direction
AbsoluteLayout.SetLayoutBounds(Buttons[i],
new Rectangle(1, offsetB - ((double)each * offsetB),
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
}
else if (TypeMenu == "Side")
{
seconds = Math.Min((DateTime.Now - beginTime).TotalSeconds, frac);
offset = seconds + delta;
//* convert linear transition to sinusoid *//
double offsetB = (Math.Sin(4 * Math.PI * (offset - 0.125)) + 1) / 4;
for (int i = 0; i < Buttons.Length; i++)
{
float div = 1 / (float)Buttons.Length;
each = (i * div);
eachinv = Math.Abs(each - Buttons.Length);
AbsoluteLayout.SetLayoutBounds(Buttons[i],
new Rectangle(1 - (2 * (offsetB - ((double)each * offsetB))), 0,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
}
else if (TypeMenu == "Arc")
{
seconds = Math.Min((DateTime.Now - beginTime).TotalSeconds, frac);
offset = seconds + delta;
double arkmult = 0.0;
if (delta == frac)
arkmult = Math.Abs(frac - seconds); // arkmult multiply the arker, so when
if (delta == 0)
arkmult = seconds;
//* convert linear transition to sinusoid *//
double offsetB = (Math.Sin(4 * Math.PI * (offset - 0.125)) + 1) / 8;
for (int i = 0; i < Buttons.Length; i++)
{
float j = ((float)Math.Sin(0.75f * ((float)i - 2.0f)) + 1.0f) * 2.0f; // really fake radiality of the linear 0 1 2 3 4
double arker = (0.3 * (Math.Sin(Math.Acos((0.5 * j) - 1)))); // Draws an Arc for values 0 to 4 (adjust the 0,5 double to fit your collection size
float div = 1 / (float)Buttons.Length;
each = ((j + 2 * i) / 3 * div) + (1 / (float)Buttons.Length);
eachinv = Math.Abs(((j + 2 * i) / 3 * div) - 1);
AbsoluteLayout.SetLayoutBounds(Buttons[i],
new Rectangle((1 - (RatioScreen * (offsetB - ((double)each * offsetB)))) - ((arker) * arkmult), (offsetB - ((double)eachinv * offsetB)) + ((arker / RatioScreen) * arkmult),
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
}
else if (TypeMenu == "DemiCircle")
{
seconds = Math.Min((DateTime.Now - beginTime).TotalSeconds, frac);
offset = seconds + delta;
double arkmult = 0.0;
if (delta == frac)
arkmult = Math.Abs(frac - seconds); // arkmult multiply the arker, so when
if (delta == 0)
arkmult = seconds;
//* convert linear transition to sinusoid *//
double offsetA = (Math.Sin(4 * Math.PI * (offset - 0.125)) + 1) / 8;
for (int i = 0; i < Buttons.Length; i++)
{
float j = ((float)Math.Sin(0.75f * ((float)i - 2.0f)) + 1.0f) * 2.0f; // really fake radiality of the linear 0 1 2 3 4
double arkerY = 0.14 * Math.Sin(Math.Acos((j * 0.5 * (arkmult / frac)) - 1)); // Demi circle equation
float lifter = (float)Math.Min(0.0f, 5.0f * (float)Math.Pow((5.0f * (float)arkmult - 0.5f), 5.0f));
float div = 1.0f / (float)Buttons.Length;
each = ((j + i) / 2 * div);
eachinv = Math.Abs(((j + i) / 2 * div) - 1);
AbsoluteLayout.SetLayoutBounds(Buttons[i],
new Rectangle(div + (3 * (offsetA - ((double)eachinv * offsetA))), 1 - (arkerY) - (double)lifter, // the 0,5 in X is to center horizontally
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
}
else if (TypeMenu == "FlatBottom")
{ // Button 3 stays behind the menu button
seconds = Math.Min((DateTime.Now - beginTime).TotalSeconds, frac);
offset = seconds + delta;
double arkmult = 0.0;
if (delta == frac)
arkmult = Math.Abs(frac - seconds); // arkmult multiply the arker, so when
if (delta == 0)
arkmult = seconds;
for (int i = 0; i < Buttons.Length; i++)
{
float deltX = (Buttons.Length - 1.0f) / 5.0f; // really not sure about that, it works at 5 items but would explode with any other number
double arkerX = (deltX * i) - (2 * deltX); // moves the buttons on x
Debug.WriteLine(i + " : " + arkerX);
float div = 0.5f / (float)Buttons.Length;
each = (i * div);
eachinv = Math.Abs((i * div) - 1);
AbsoluteLayout.SetLayoutBounds(Buttons[i],
new Rectangle(0.5 + (arkerX * arkmult), 1, // the 0,5 in X is to center horizontally
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
}
return isCurrentPage;
}
else
{
okMove = false;
isCurrentPage = false;
return isCurrentPage;
}
});
}
}
protected override void OnAppearing()
{
base.OnAppearing();
isCurrentPage = true;
beginTime = DateTime.Now;
for (int i = 0; i < Buttons.Length; i++)
{
AbsoluteLayout.SetLayoutBounds(Buttons[i],
new Rectangle(1, -1,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(1, 0,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
if (TypeMenu == "DemiCircle" || TypeMenu == "Flatbottom")
{
AbsoluteLayout.SetLayoutBounds(menubutton,
new Rectangle(0.5, 1,
AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize));
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
isCurrentPage = false;
}
}
}