You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何防止iOS Voiceover在Flyout菜单未显示时读取菜单项

解决Xamarin Forms 5原生Flyout菜单Voiceover读取未显示菜单项的问题

核心思路是在Flyout菜单收起时,通过原生平台的辅助功能API,将菜单项所在的视图标记为不可被辅助功能访问,展开时恢复可访问状态。

iOS 平台处理

iOS的FlyoutPage对应原生的UISplitViewController,侧边栏菜单在收起时仍会被Voiceover扫描到。我们可以通过自定义FlyoutPageRenderer,监听菜单的展开/收起状态,动态设置菜单项容器的辅助属性:

[assembly: ExportRenderer(typeof(FlyoutPage), typeof(CustomFlyoutPageRenderer))]
namespace YourAppNamespace.iOS.Renderers
{
    public class CustomFlyoutPageRenderer : FlyoutPageRenderer
    {
        protected override void OnElementChanged(VisualElementChangedEventArgs e)
        {
            base.OnElementChanged(e);
            
            if (e.NewElement != null)
            {
                ((FlyoutPage)e.NewElement).IsPresentedChanged += OnFlyoutIsPresentedChanged;
                UpdateMenuAccessibility(false);
            }
        }

        private void OnFlyoutIsPresentedChanged(object sender, EventArgs e)
        {
            var flyoutPage = sender as FlyoutPage;
            UpdateMenuAccessibility(flyoutPage.IsPresented);
        }

        private void UpdateMenuAccessibility(bool isPresented)
        {
            if (SplitViewController?.ViewControllers.Length > 1)
            {
                var menuViewController = SplitViewController.ViewControllers[0];
                menuViewController.View.IsAccessibilityElement = isPresented;
                menuViewController.View.AccessibilityElementsHidden = !isPresented;
            }
        }
    }
}

Android 平台处理

Android的Flyout基于DrawerLayout,抽屉关闭时需要将抽屉内容视图的ImportantForAccessibility属性设置为NoHideDescendants,阻止Voiceover读取内部元素:

[assembly: ExportRenderer(typeof(FlyoutPage), typeof(CustomFlyoutPageRenderer))]
namespace YourAppNamespace.Droid.Renderers
{
    public class CustomFlyoutPageRenderer : FlyoutPageRenderer
    {
        private DrawerLayout _drawerLayout;

        public CustomFlyoutPageRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<FlyoutPage> e)
        {
            base.OnElementChanged(e);
            
            if (e.NewElement != null)
            {
                _drawerLayout = FindViewById<DrawerLayout>(Resource.Id.drawer_layout);
                ((FlyoutPage)e.NewElement).IsPresentedChanged += OnFlyoutIsPresentedChanged;
                UpdateMenuAccessibility(false);
            }
        }

        private void OnFlyoutIsPresentedChanged(object sender, EventArgs e)
        {
            var flyoutPage = sender as FlyoutPage;
            UpdateMenuAccessibility(flyoutPage.IsPresented);
        }

        private void UpdateMenuAccessibility(bool isPresented)
        {
            if (_drawerLayout != null)
            {
                var drawerView = _drawerLayout.GetChildAt(1);
                if (drawerView != null)
                {
                    drawerView.ImportantForAccessibility = isPresented 
                        ? ImportantForAccessibility.Auto 
                        : ImportantForAccessibility.NoHideDescendants;
                }
            }
        }
    }
}

额外注意事项

  • 确保自定义Renderer所在的命名空间正确,并且项目中引用了Xamarin.Forms.Platform.iOS/Xamarin.Forms.Platform.Android命名空间。
  • 如果Flyout菜单使用了自定义菜单项模板,需确保模板中的控件没有强制设置IsAccessibilityElement="True",否则会覆盖上述设置。

内容的提问来源于stack exchange,提问作者Jeffery Jones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 23:50:29