如何真正避免在 Xamarin.Forms 中同时单击多个按钮? [英] How to truly avoid multiple buttons being clicked at the same time in Xamarin.Forms?

查看:19
本文介绍了如何真正避免在 Xamarin.Forms 中同时单击多个按钮?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在一个视图中使用了多个按钮,每个按钮都指向自己的弹出页面.同时点击多个按钮时,会一次跳转到不同的弹出页面.

I am using multiple buttons in a view, and each button leads to its own popup page. While clicking multiple button simultaneously, it goes to different popup pages at a time.

我创建了一个包含 3 个按钮(每个按钮转到不同的弹出页面)的示例内容页面来演示此问题:

I created a sample content page with 3 buttons (each goes to a different popup page) to demonstrate this issue:

XAML 页面:

<ContentPage.Content>
    <AbsoluteLayout>

        <!-- button 1 -->
        <Button x:Name="button1" Text="Button 1"
            BackgroundColor="White" Clicked="Button1Clicked"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.3, 0.5, 0.1"/>

        <!-- button 2 -->
        <Button x:Name="button2" Text="Button 2"
            BackgroundColor="White" Clicked="Button2Clicked"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.5, 0.1"/>

        <!-- button 3 -->
        <Button x:Name="button3" Text="Button 3"
            BackgroundColor="White" Clicked="Button3Clicked"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.7, 0.5, 0.1"/>

        <!-- popup page 1 -->
        <AbsoluteLayout x:Name="page1" BackgroundColor="#7f000000" IsVisible="false"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 1.0, 1.0">
            <BoxView Color="Red"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.75, 0.3"/>
            <Label Text="Button 1 clicked" TextColor="White"
                HorizontalTextAlignment="Center"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.45, 0.75, 0.05"/>
            <Button Text="Back" BackgroundColor="White" Clicked="Back1Clicked"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"/>
        </AbsoluteLayout>

        <!-- popup page 2 -->
        <AbsoluteLayout x:Name="page2" BackgroundColor="#7f000000" IsVisible="false"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 1.0, 1.0">
            <BoxView Color="Green"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.75, 0.3"/>
            <Label Text="Button 2 clicked" TextColor="White"
                HorizontalTextAlignment="Center"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.45, 0.75, 0.05"/>
            <Button Text="Back" BackgroundColor="White" Clicked="Back2Clicked"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"/>
        </AbsoluteLayout>

        <!-- popup page 3 -->
        <AbsoluteLayout x:Name="page3" BackgroundColor="#7f000000" IsVisible="false"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 1.0, 1.0">
            <BoxView Color="Blue"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.75, 0.3"/>
            <Label Text="Button 3 clicked" TextColor="White"
                HorizontalTextAlignment="Center"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.45, 0.75, 0.05"/>
            <Button Text="Back" BackgroundColor="White" Clicked="Back3Clicked"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"/>
        </AbsoluteLayout>

    </AbsoluteLayout>
</ContentPage.Content>

C# 事件处理程序:

void Button1Clicked(object sender, EventArgs e)
{
    // ... do something first ...
    page1.IsVisible = true;
    Console.WriteLine("Button 1 Clicked!");
}

void Button2Clicked(object sender, EventArgs e)
{
    // ... do something first ...
    page2.IsVisible = true;
    Console.WriteLine("Button 2 Clicked!");
}

void Button3Clicked(object sender, EventArgs e)
{
    // ... do something first ...
    page3.IsVisible = true;
    Console.WriteLine("Button 3 Clicked!");
}

void Back1Clicked(object sender, EventArgs e)
{
    page1.IsVisible = false;
}

void Back2Clicked(object sender, EventArgs e)
{
    page2.IsVisible = false;
}

void Back3Clicked(object sender, EventArgs e)
{
    page3.IsVisible = false;
}

预期:
点击button1打开page1弹窗,点击弹窗中的后退按钮隐藏弹窗.button2button3 的行为类似.

Expected:
Clicking button1 opens page1 popup page, and clicking the back button in the popup hides the popup page. Similar behavious for button2 and button3.

实际:
同时单击多个按钮(例如 button1button2)会打开两个弹出页面(page1page2).快速双击单个按钮也可以两次触发同一个按钮.

Actual:
Clicking multiple buttons (eg. button1 and button2) at the same time opens both popup pages (page1 and page2). Double clicking a single button quickly can also fire the same button twice.

关于避免双击的一些研究
通过在 stackoverflow 中搜索类似的问题(例如 thisthis),我来到一个结论 您应该设置一个外部变量来控制是否执行事件.这是我在 Xamarin.forms 中的实现:

Some research on avoid double clicking
By searching similar questions in stackoverflow (such as this and this), I come to a conclusion where you should set an external variable to control whether the events are executed or not. This is my implementation in Xamarin.forms:

C# struct 作为外部变量,以便我可以在单独的类中访问此变量:

C# struct as the external variable so that I can access this variable in separate classes:

// struct to avoid multiple button click at the same time
public struct S
{
    // control whether the button events are executed
    public static bool AllowTap = true;

    // wait for 200ms after allowing another button event to be executed
    public static async void ResumeTap() {
        await Task.Delay(200);
        AllowTap = true;
    }
}

然后像这样修改每个按钮事件处理程序(同样适用于Button2Clicked()Button3Clicked()):

Then each button event handler is modified like this (same applies to Button2Clicked() and Button3Clicked()):

void Button1Clicked(object sender, EventArgs e)
{
    // if some buttons are clicked recently, stop executing the method
    if (!S.AllowTap) return; S.AllowTap = false; //##### * NEW * #####//

    // ... do something first ...
    page1.IsVisible = true;
    Console.WriteLine("Button 1 Clicked!");

    // allow other button's event to be fired after the wait specified in struct S
    S.ResumeTap(); //##### * NEW * #####//
}

这通常效果很好.双击同一个按钮只快速触发一次按钮事件,同时点击多个按钮只打开1个弹出页面.

This works generally pretty well. Double-tapping the same button quickly fire the button event once only, and clicking multiple buttons at the same time only open 1 popup page.

真正的问题
如上所述修改代码(在struct S中添加共享状态变量AllowTap)后,仍然可以打开1个以上的弹出页面.例如,如果用户用两根手指按住button1button2,松开button1,等待大约一秒钟,然后松开button2,弹出页面page1page2都会被打开.

The Real Problem
It's still possible to open more than 1 popup pages after modifing the code (adding a shared state variable AllowTap in struct S) as described above. E.g., if the user hold down button1 and button2 using 2 fingers, release button1, wait for around a second, and then release button2, both popup pages page1 and page2 will be opened.

尝试解决此问题失败
如果单击 button1button2button3,我尝试禁用所有按钮,如果单击后退按钮,则启用所有按钮.>

A failed attempt to fix this issue
I tried to disable all buttons if either button1, button2 or button3 is clicked, and enable all buttons if the back button is clicked.

void disableAllButtons()
{
    button1.IsEnabled = false;
    button2.IsEnabled = false;
    button3.IsEnabled = false;
}

void enableAllButtons()
{
    button1.IsEnabled = true;
    button2.IsEnabled = true;
    button3.IsEnabled = true;
}

然后像这样修改每个按钮事件处理程序(同样适用于Button2Clicked()Button3Clicked()):

Then each button event handler is modified like this (same applies to Button2Clicked() and Button3Clicked()):

void Button1Clicked(object sender, EventArgs e)
{
    if (!S.AllowTap) return; S.AllowTap = false;

    // ... do something first ...
    disableAllButtons(); //##### * NEW * #####//
    page1.IsVisible = true;
    Console.WriteLine("Button 1 Clicked!");

    S.ResumeTap();
}

每个后退按钮事件处理程序都是这样修改的(同样适用于Back2Clicked()Back3Clicked()):

And each back button event handler is modified like this (same applies to Back2Clicked() and Back3Clicked()):

void Back1Clicked(object sender, EventArgs e)
{
    page1.IsVisible = false;
    enableAllButtons(); //##### * NEW * #####//
}

然而,同样的问题仍然存在(能够按住另一个按钮并稍后释放它们以同时触发两个按钮).

However, the same issue still persists (able to hold another button and release them later to fire 2 buttons simultaneously).

在我的应用程序中禁用多点触控不是一个选项,因为我需要在我的应用程序的其他页面中使用它.此外,弹出页面也可能包含多个按钮,也可以通向其他页面,因此只需使用弹出页面中的后退按钮设置struct S中的变量AllowTap也不会是一个选择.

Disabling multi-touch in my app won't be an option, since I need that in other pages in my app. Also, the popup pages may also contain multiple buttons which leads to other pages as well, so simply using the back button in the popup page to set the variable AllowTap in struct S won't be an option as well.

任何帮助将不胜感激.谢谢.

Any help will be appreciated. Thanks.

编辑
真正的问题"同时影响 Android 和 iOS.在 Android 上,一旦在用户按住按钮时禁用了某个按钮,就无法激活该按钮.这个按住禁用按钮的问题不会影响 iOS 中的按钮.

EDIT
"The Real Problem" affects both Android and iOS. On Android, a button can't be activated once the button is disabled some time when the user is holding the button. This holding-a-disabled-button issue does not affect buttons in iOS.

推荐答案

我的基础视图模型中有这个:

I have this in my base view model:

public bool IsBusy { get; set; }

protected async Task RunIsBusyTaskAsync(Func<Task> awaitableTask)
{
    if (IsBusy)
    {
        // prevent accidental double-tap calls
        return;
    }
    IsBusy = true;
    try
    {
        await awaitableTask();
    }
    finally
    {
        IsBusy = false;
    }
}

命令委托如下所示:

    private async Task LoginAsync()
    {
        await RunIsBusyTaskAsync(Login);
    }

...或者如果你有参数:

...or if you have parameters:

    private async Task LoginAsync()
    {
        await RunIsBusyTaskAsync(async () => await LoginAsync(Username, Password));
    }

登录方法将包含您的实际逻辑

The login method would contain your actual logic

    private async Task Login()
    {
        var result = await _authenticationService.AuthenticateAsync(Username, Password);

        ...
    }

此外,您还可以使用内联委托:

also, you could use inline delegate:

    private async Task LoginAsync()
    {
        await RunIsBusyTaskAsync(async () =>
        {
            // logic here
        });
    }

不需要 IsEnabled 设置.如果您执行的实际逻辑不是异步的,您可以将 Func 替换为 Action.

no IsEnabled setting necessary. you can replace Func with Action if the actual logic you're executing isn't async.

您还可以绑定到 IsBusy 属性以获取诸如 ActivityIndi​​cator 之类的内容

You can also bind to the IsBusy property for things like the ActivityIndicator

这篇关于如何真正避免在 Xamarin.Forms 中同时单击多个按钮?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆