200 lines of code in the spread button effect Xamarin.Form

Contents are as follows

  1. Demonstration effect
  2. The relevant code
  3. Precautions

First, the effect of the presentation
Here Insert Picture Description
I wrote a while ago with a small application Xamarin.Forms, which used such a spread of clicking a button control. Here record and share the implementation process, the desire to help more new partners into the small pit.
Note that only supports female button 90 as the center spread, if you want to 180 degrees need to be slightly modified to change the diffusion code.

Second, the relevant code
should be noted here that the custom control to xaml way to achieve, in theory, can be achieved in Xamarin.Forms across platforms. I pro-test of Android phones available, ios aspects of Apple because there is no cell phone so there is no test.
First, look at what we need to be prepared.
Here Insert Picture Description
We just need to add in the project SplashButton.xaml and the corresponding cs file can develop good habits, custom controls on Conpoments unified namespace.

SplashButton.xaml

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="SplashBtnExample.Component.SplashButton" 
             VerticalOptions="End" HorizontalOptions="Start">
    <Button x:Name="MenuBtn" HorizontalOptions="Start" VerticalOptions="End"/>
</RelativeLayout>

SplashButton.cs

namespace SplashBtnExample.Component
{
    /// <summary>
    /// 子按钮
    /// </summary>
    public class SubButton
    {
        /// <summary>
        /// 点击事件
        /// </summary>
        public Action<object, EventArgs> Action { get; set; }

        /// <summary>
        /// 图片地址
        /// </summary>
        public string ImageUrl { get; set; }
    }
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class SplashButton : RelativeLayout
    {
        private int diameter;
        /// <summary>
        /// 按钮直径(px)默认30
        /// </summary>
        public int ButtonDiameter
        {
            get { return diameter; }
            set
            {
                diameter = value;
                //MenuBtn.CornerRadius = diameter;
                MenuBtn.HeightRequest = diameter;
                MenuBtn.WidthRequest = diameter;
                this.WidthRequest = diameter;
            }
        }
        
        /// <summary>
        /// 子按钮集合
        /// </summary>
        public List<SubButton> BtnList { get; private set; }

        /// <summary>
        /// 当前是否展开
        /// </summary>
        private bool isOpen { get; set; }

        /// <summary>
        /// 子按钮的按钮实例集合
        /// </summary>
        private List<Button> subButtons;

        public SplashButton()
        {
            InitializeComponent();
            ButtonDiameter = 30;
            MenuBtn.Clicked += new EventHandler(MenuBtn_Clicked);
        }
        
        /// <summary>
        /// 设置主按钮的图片
        /// </summary>
        /// <param name="menuImgUrl">图片路径</param>
  	public void SetMenuImage(string menuImgUrl)
        {
            isOpen = false;
            MenuBtn.Image = menuImgUrl;
        }

        /// <summary>
        /// 设置子按钮
        /// </summary>
        /// <param name="subButtons">子按钮集合</param>
        public void SetMenuButton(List<SubButton> subButtons)
        {
            this.BtnList = subButtons;
            this.subButtons = new List<Button>();
            int distance = GetDisrance(diameter, subButtons.Count);
            RelativeLayout parentView = this.Parent as RelativeLayout;
            for (int i = 0; i < subButtons.Count; i++)
            {
                var item = subButtons[i];
                Button subBtn = new Button();
                subBtn.CornerRadius = diameter;
                subBtn.WidthRequest = diameter;
                subBtn.HeightRequest = diameter;
                subBtn.Clicked += MenuBtn_Clicked;
                subBtn.Clicked += new EventHandler(item.Action);
                subBtn.Image = item.ImageUrl;

                var position = GetOffset(distance, i, subButtons.Count);
                int x = position.Item1;         //当前左下角,右下角需要乘-1
                int y = -1 * position.Item2;
                subBtn.Scale = 0;
                this.subButtons.Add(subBtn);
                parentView.Children.Add(subBtn, Constraint.RelativeToView(this, (parent, sibling) => {
                    return sibling.X + x;
                }), Constraint.RelativeToView(this, (parent, sibling) => {
                    return sibling.Y + y;
                }));
            }
        }

        /// <summary>
        /// 计算每个子按钮到主按钮的距离(按钮越多距离越远)
        /// </summary>
        /// <param name="diameter">子按钮直径</param>
        /// <param name="count">子按钮数量</param>
        /// <returns></returns>
        private int GetDisrance(int diameter, int count)
        {
            int ret = 0;
            if (count <= 2)
            {
                ret = (int)(diameter * 1.2);
            }
            else
            {
                double partDegree = 0;
                partDegree = 90 / (count - 1);
                partDegree = partDegree / 2;
                ret = (int)((diameter / (2 * Math.Sin((Math.PI / 180) * partDegree))) * 1.2);
            }

            return ret;
        }

        /// <summary>
        /// 计算每个子按钮到主按钮的偏移
        /// </summary>
        /// <param name="distance">子按钮到主按钮的距离</param>
        /// <param name="position">第几个子按钮 从0开始</param>
        /// <param name="count">一共多少个子按钮</param>
        /// <returns>(x坐标偏移,y坐标偏移)</returns>
        private (int, int) GetOffset(int distance, int position, int count)
        {
            double partDegree = 0;
            if (count > 0)
            {
                partDegree = 90 / (count - 1);
            }
            double degree = position * partDegree;
            //角度转弧度再计算sin
            double temp = Math.Sin((Math.PI / 180) * degree);
            int x = (int)(temp * distance);
            //角度转弧度再计算cos
            temp = Math.Cos((Math.PI / 180) * degree);
            int y = (int)(temp * distance);
            return (x, y);
        }

        private void MenuBtn_Clicked(object sender, EventArgs e)
        {
            isOpen = !isOpen;
            Splash();
        }

        /// <summary>
        /// 散开/闭合 动画
        /// </summary>
        private async void Splash()
        {
            if (isOpen)
            {
                // animation
                foreach (var item in subButtons)
                {
                    await item.ScaleTo(1, 100);
                }
            }
            else
            {
                // animation
                foreach (var item in subButtons)
                {
                    await item.ScaleTo(0, 100);
                }
            }
        }
    }
}

These are all the code for the entire control
following is a demonstration effect in the above code, the method call is actually very simple.
1.xaml page directly add tags component (note the label's parent container must be a RelativeLayout, as explained later)
2. Set main button when the page is initialized pictures and size, set the sub-picture button and click time

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:SplashBtnExample"
             xmlns:component="clr-namespace:SplashBtnExample.Component"
             x:Class="SplashBtnExample.MainPage">
    <!--xmlns:component="...." 用于引用控件所在的命名空间-->

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="3*"/>
            <RowDefinition Height="7*"/>
        </Grid.RowDefinitions>
        <Label Text="Welcome to Xamarin.Forms!" 
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" Grid.Row="0"/>
           
        <!--控件必须包含在RelativeLayout中,且RelativeLayout的大小必须足够按钮伸展-->
        <RelativeLayout Grid.Row="1" HorizontalOptions="StartAndExpand" VerticalOptions="FillAndExpand">
            <component:SplashButton x:Name="menuBtn" RelativeLayout.YConstraint="{ConstraintExpression Type=RelativeToParent, Factor=.8,Property=Height,Constant=0}"/>
        </RelativeLayout>
    </Grid>
</ContentPage>

MainPage.cs

    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            InitMenuBtn();
        }

        /// <summary>
        /// 初始化分散按钮的设置
        /// </summary>
        private void InitMenuBtn()
        {
            // 设置子按钮
            List<SubButton> subs = new List<SubButton>();
            subs.Add(new SubButton { ImageUrl = "icon_setting.png", Action = TestMethod });
            subs.Add(new SubButton { ImageUrl = "icon_setting.png", Action = TestMethod });
            subs.Add(new SubButton { ImageUrl = "icon_setting.png", Action = TestMethod });
            subs.Add(new SubButton { ImageUrl = "icon_setting.png", Action = TestMethod });
            subs.Add(new SubButton { ImageUrl = "icon_setting.png", Action = TestMethod });

            // 设置按钮的直径为40px
            menuBtn.ButtonDiameter = 40;
            // 设置主按钮的按钮图片
            menuBtn.SetMenuImage("icon_setting.png");
            // 绑定子按钮
            menuBtn.SetMenuButton(subs);
        }

        private void TestMethod(object obj, EventArgs args)
        {
            // your code
            // 子按钮的点击事件绑定到这个方法
        }
    }

Third, note
the parent container 1. controls where it must be RelativeLayout, and the size of the parent container must be able to accommodate the size of the button after all spread out.
Why must RelativeLayout it? Because the master control button is actually used to calculate the position of the sub reference position button should appear, and then dynamically added to the parent inside the container, this process needs to be added relative layout position of the button relative to the main sub-button layout.
So why add to the parent container inside it? You can not add in controls its own container it? It is possible, but there will be a very strange BUG: child can not click the button. Because the size of the container main button controls only its own size, and sub-buttons position outside the control container, so can not click, which is why the size of the parent container must be able to accommodate the position of all the buttons spread out, or will not point .
Here Insert Picture Description

As illustrated, the container is the size of the control block in FIG yellow, red parent container control block. If the sub-button control is generated within the control will own container outside the yellow box, which would not only show clicks. So here the child to select the parent generated inside the container.

2. The main button at the top right of the lower left corner of the example, the button appears in the main sub-button, home button if you want to appear in the other side, you need to modify SetMenuButton method, the code has comments.

3. only supports 90-degree spread, other small partners wants to spread the way of welcome amendments and supplements.

GitHub: https://github.com/YinRunhao/SplashButton.git
welcome of all heroes supplement

Published 15 original articles · won praise 2 · Views 4864

Guess you like

Origin blog.csdn.net/weixin_38138153/article/details/90271442