程序问答   发布时间:2022-06-02  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了使用虚拟 DPAD 在 Xamarin 应用程序中处理 Talkback大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。

如何解决使用虚拟 DPAD 在 Xamarin 应用程序中处理 TalkBACk?

开发过程中遇到使用虚拟 DPAD 在 Xamarin 应用程序中处理 TalkBACk的问题如何解决?下面主要结合日常开发的经验,给出你关于使用虚拟 DPAD 在 Xamarin 应用程序中处理 TalkBACk的解决方法建议,希望对你解决使用虚拟 DPAD 在 Xamarin 应用程序中处理 TalkBACk有所启发或帮助;

我有一个 Xamarin 应用程序,它并不打算处理 androID 的对讲功能,因为它必须以特定方式构建才能正常运行。

我的应用程序是一个小命令,我根本无法完成整个事情。

那么,发生了什么? 我的 Xamarin 应用程序使用非本机库制作,TalkBACk 不支持这些库,因此,当用户打开 TalkBACk 功能时,该应用程序实际上停止接收 DPAD 事件,因为它们由系统辅助功能服务处理。

该服务,获取事件,并尝试在我的应用程序中处理它们,但是,由于我的组件是非本地组件,系统无法识别它们并且浪费了 DPAD,因此,DPAD 不是工作。

那么,如果您只想在打开 TalkBACk 的情况下自己处理 DPAD(而不是其他任何东西),该怎么办

这篇文章的答案将包含描述以下行为的代码:

1. The talkBACk wont be able to 'talk' about your components

2. The DPAD events will be handled by an Accessibility Delegate

3. A virtual DPAD will handle the navigation

4. The green rectangle used for focus will be Disabled,since you wont need it anyway

5. The app will look exactly the same with TalkBACk on and off

这篇文章是为了教育目的而写的,因为我很难想出解决方案,希望下一个人觉得它有帮助。

解决方法

第一步是创建一个继承 AccessibilityDelegateCompat 的类,以便创建我们自己的无障碍服务。

class MyAccessibilityHelper : AccessibilityDelegateCompat
{
    const String Tag = "MyAccessibilityHelper";
    const int ROOT_NODE = -1;
    const int INVALID_NODE = -1000;
    const String NODE_CLASS_NAME = "My_Node";

    public const int nODE_UP = 1;
    public const int nODE_LEFT = 2;
    public const int nODE_CENTER = 3;
    public const int nODE_RIGHT = 4;
    public const int nODE_DOWN = 5;

    private class MyAccessibilityProvider : AccessibilityNodeProviderCompat
    {
        private readonly MyAccessibilityHelper mHelper;

        public MyAccessibilityProvider(MyAccessibilityHelper Helper)
        {
            mHelper = Helper;
        }

        public override bool PerformAction(int virtualViewId,int action,Bundle arguments)
        {
            return mHelper.PerformNodeAction(virtualViewId,action,arguments);
        }

        public override AccessibilityNodeInfoCompat CreateAccessibilityNodeInfo(int virtualViewId)
        {
            var node = mHelper.CreateNode(virtualViewId);
            return AccessibilityNodeInfoCompat.obtain(nodE);
        }
    }

    private readonly View mView;
    private readonly MyAccessibilityProvider mProvider;
    private Dictionary<int,Rect> mRects = new Dictionary<int,Rect>();
    privatE int mAccessibilityFocusIndex = INVALID_NODE;

    public MyAccessibilityHelper(View view)
    {
        mView = view;
        mProvider = new MyAccessibilityProvider(this);
    }

    public override AccessibilityNodeProviderCompat GetAccessibilityNodeProvider(View host)
    {
        return mProvider;
    }

    public override void SendAccessibilityEvent(View host,int eventTypE)
    {
        Android.Util.Log.Debug(tag,"SendAccessibilityEvent: host={0} eventType={1}",host,eventTypE);
        base.SendAccessibilityEvent(host,eventTypE);
    }

    public void AddRect(@R_874_8052@,Rect rect)
    {
        mRects.Add(id,rect);
    }

    public AccessibilityNodeInfoCompat CreateNode(int virtualViewId)
    {
        var node = AccessibilityNodeInfoCompat.obtain(mView);
        if (virtualViewId == ROOT_NODE)
        {
            node.ContentDescription = "Root node";
            ViewCompat.onInitializeAccessibilityNodeInfo(mView,nodE);
            foreach (var r in mRects)
            {
                node.AddChild(mView,r.Key);
            }
        }
        else
        {
            node.ContentDescription = "";
            node.ClassName = NODE_CLASS_NAME;
            node.Enabled = true;
            node.Focusable = true;
            var r = mRects[virtualViewId];
            node.SetBoundsInParent(r);
            int[] offset = new int[2];
            mView.GetLOCATIOnOnScreen(offset);
            node.SetBoundsInScreen(new Rect(offset[0] + r.Left,offset[1] + r.Top,offset[0] + r.Right,offset[1] + r.bottom));
            node.Packagename = mView.Context.Packagename;
            node.Setsource(mView,virtualViewId);
            node.SetParent(mView);
            node.VisibleToUser = true;
            if (virtualViewId == mAccessibilityFocusIndeX)
            {
                node.AccessibilityFocused = true;
                node.AddAction(AccessibilityNodeInfoCompat.ActionClearAccessibilityFocus);
            }
            else
            {
                node.AccessibilityFocused = false;
                node.AddAction(AccessibilityNodeInfoCompat.FocusAccessibility);
            }
        }
        return node;
    }

    private AccessibilityEvent CreateEvent(int virtualViewId,EventTypes eventTypE)
    {
        var e = AccessibilityEvent.obtain(eventTypE);
        if (virtualViewId == ROOT_NODE)
        {
            ViewCompat.onInitializeAccessibilityEvent(mView,E);
        }
        else
        {
            var record = AccessibilityEventCompat.AsRecord(E);
            record.enabled = true;
            record.Setsource(mView,virtualViewId);
            record.ClassName = NODE_CLASS_NAME;
            e.Packagename = mView.Context.Packagename;
        }
        return e;
    }

    public bool SendEventForVirtualView(int virtualViewId,EventTypes eventTypE)
    {
        if (mView.Parent == null)
            return false;
        var e = CreateEvent(virtualViewId,eventTypE);
        return ViewParentCompat.requestSendAccessibilityEvent(mView.Parent,mView,E);
    }

    public bool PerformNodeAction(int virtualViewId,Bundle arguments)
    {
        if (virtualViewId == ROOT_NODE)
        {
            return ViewCompat.PerformAccessibilityAction(mView,arguments);
        }
        else
        {
            switch (action)
            {
                case AccessibilityNodeInfoCompat.ActionAccessibilityFocus:
                    if (virtualViewId != mAccessibilityFocusIndeX)
                    {
                        if (mAccessibilityFocusIndex != INVALID_NODE)
                        {
                            SendEventForVirtualView(mAccessibilityFocusIndex,EventTypes.ViewAccessibilityFocusCleared);
                        }
                        mAccessibilityFocusIndex = virtualViewId;
                        mView.Invalidate();
                        SendEventForVirtualView(virtualViewId,EventTypes.ViewAccessibilityFocused);
                        // virtual key event                            
                        switch (virtualViewId)
                        {
                            case NODE_UP:
                                HandleDpadEvent(Keycode.DpadUp);
                                break;
                            case NODE_LEFT:
                                HandleDpadEvent(Keycode.DpadLeft);
                                break;
                            case NODE_RIGHT:
                                HandleDpadEvent(Keycode.DpadRight);
                                break;
                            case NODE_DOWN:
                                HandleDpadEvent(Keycode.DpadDown); 
                                break;
                        }
                        // refocus center
                        SendEventForVirtualView(NODE_CENTER,EventTypes.ViewAccessibilityFocused);
                        return true;
                    }
                    break;
                case AccessibilityNodeInfoCompat.ActionClearAccessibilityFocus:
                    mView.requestFocus();
                    if (virtualViewId == mAccessibilityFocusIndeX)
                    {
                        mAccessibilityFocusIndex = INVALID_NODE;
                        mView.Invalidate();
                        SendEventForVirtualView(virtualViewId,EventTypes.ViewAccessibilityFocusCleared);
                        return true;
                    }
                    break;
            }
        }
        return false;
    }

    private void HandleDpadEvent(Keycode keycodE)
    {
       //Here you know what DPAD was pressed
       //You can create your own key event and send it to your app
       //This code depends on your own application,and I wont be providing the code
       //Note,it is important to handle both,the KeyDOWN and the KeyUP event for it to work
    }
}

由于代码有点大,我只解释最关键的部分。 一旦对讲处于活动状态,字典(从我们的视图如下)将@R_262_9899@我们的虚拟 DPAD 的虚拟树节点。虑到这一点,PerformNodeAction 函数将是最重要的。

一旦一个虚拟节点被 Accessibility 系统聚焦,它就会处理操作,基于提供的虚拟元素的 id,有两个部分,第一个是 ROOT_NODE,它是包含我们的虚拟 dpad 的视图 iteslf ,大部分可以忽略,但第二部分是处理完成的地方。

第二部分是处理 ActionAccessibilityFocus 和 ActionClearAccessibilityFocus 的地方。两个女巫都很重要,但第一个是我们最终可以处理我们的虚拟 dpad 的地方。

这里所做的是通过字典中提供的虚拟ID,我们知道选择了哪个DPAD(virtualViewId)。根据选择的DPAD,我们可以在HandleDpadEvent函数中执行我们想要的动作。需要注意的重要一点是,在我们处理了 SELEcteds DPAD 事件之后,我们将重新聚焦我们的 CENTER 节点,以便准备好处理下一次按下按钮。这非常重要,因为您不想发现自己处于向下然后向上的情况,只是为了让虚拟 dpad 聚焦在 CENTER 垫上。

所以,我再说一遍,在处理上一个 'DPAD 事件后,需要重新调整 CENTER pad 的焦点,以便我们准确地知道按下下一个 DPAD 按钮后我们将在哪里!

>

有一个函数我不会在这里发布,因为它的代码非常适合我的应用程序,该函数是 HandleDpadEvent,在那里你必须创建一个 keydown 和一个 keyup 事件,并将它发送到你的主要活动,该函数所在的位置onKeyDown/Up 将被触发。一旦你这样做了,委托就完成了。

一旦委托完成,我们必须像这样制作我们的视图:

/**
* SimplestCustomView
*/
public class AccessibilityHelperView : View
{
    private MyAccessibilityHelper mHelper;

    Dictionary<int,Rect> virtualIdRectMap = new Dictionary<int,Rect>();

    public AccessibilityHelperView(Context context) :
        base(context)
    {
        Init();
    }

    public AccessibilityHelperView(Context context,IAttributeSet attrs) :
        base(context,attrs)
    {
        Init();
    }

    public AccessibilityHelperView(Context context,IAttributeSet attrs,int defStylE) :
        base(context,attrs,defStylE)
    {
        Init();
    }

    public void Init()
    {
        this.SetFocusable(ViewFocusability.FocusablE);
        this.Focusable = true;
        this.FocusedByDefault = true;

        setRectangle();

        mHelper = new MyAccessibilityHelper(this);
        ViewCompat.SetAccessibilityDelegate(this,mHelper);
        foreach (var r in virtualIdRectMap)
        {
            mHelper.AddRect(r.Key,r.value);
        }
    }

    private void setRectangle()
    {
        virtualIdRectMap.Add(MRAccessibilityHelper.NODE_CENTER,new Rect(1,1,2,2));
        virtualIdRectMap.Add(MRAccessibilityHelper.NODE_LEFT,new Rect(0,2));
        virtualIdRectMap.Add(MRAccessibilityHelper.NODE_UP,1));
        virtualIdRectMap.Add(MRAccessibilityHelper.NODE_RIGHT,new Rect(2,3,2));
        virtualIdRectMap.Add(MRAccessibilityHelper.NODE_DOWN,3));
    }

    protected override void OnDraw(Canvas canvas)
    {
        base.onDraw(canvas);
    }
}

该视图如下所示:

使用虚拟 DPAD 在 Xamarin 应用程序中处理 Talkback

要注意什么?

  1. 节点焊盘的大小以像素为单位,它们可以在您的应用的左上角找到。

  2. 它们被设置为单个像素大小,因为否则 TalkBACk 功能会选择添加到带有绿色矩形的字典的第一个节点焊盘(这是对讲的标准行为)

  3. 视图中的所有矩形都被添加到一个字典中,该字典将用于我们自己的可访问性代理,这里要提到的是首先添加了 CENTER 垫,因此一旦激活对讲,它就会成为焦点默认

  4. 初始化函数

Init 函数对此至关重要,我们将在那里创建我们的视图,并为我们的虚拟 dpad 设置一些必要的对讲参数,以便系统自己的辅助功能服务能够识别。

此外,还将初始化我们的可访问性代理和我们的字典,其中包含所有创建的 DPAD。

好的,到目前为止,我们制作了一个委托和一个视图,我将它们放在同一个文件中,这样它们就可以看到彼此。但这不是必须的。

那现在怎么办?我们必须在 MainActivity.cs 文件中将 AccessibilityHelperView 添加到@R_640_9616@程序

AccessibilityHelperView mAccessibilityHelperView;

在 OnCreate 函数中,您可以添加以下代码来启动视图:

@H_10_15@mAccessibilityHelperView = new AccessibilityHelperView(this);

在 OnResume 函数中,您可以检查对讲是打开还是关闭,根据结果,您可以在 mBACkgroundLayout(AddView 和 RemoveView)中添加或删除 mAccessibilityHelperView。

OnResume 函数应如下所示:

 if (TalkBACkEnabled && !_isVirtualDPadShown)
 {
     mBACkgroundLayout.AddView(mAccessibilityHelperView);
     _isVirtualDPadShown = true;
 }
 else if (!TalkBACkEnabled && _isVirtualDPadShown)
 {
      mBACkgroundLayout.RemoveView(mAccessibilityHelperView);
      _isVirtualDPadShown = false;
 }

TalkBACkEnabled 变量是一个本地变量,用于检查 TalkBACk 服务是打开还是关闭,如下所示:

 public bool TalkBACkEnabled 
 {
        get
        {
            Accessibilitymanager am = MyApp.Instance.GetSystemservice(Context.AccessibilityservicE) as Accessibilitymanager;
            if (am == null) return false;

            String TALKBACK_SETTinG_ACTIVITY_NAME = "com.android.talkBACk.TalkBACkPreferencesActivity";
            var serviceList = am.GetEnabledAccessibilityserviceList(FeedBACkFlags.AllMask);
            foreach (AccessibilityserviceInfo serviceInfo in serviceList)
            {
                String name = serviceInfo.SetTingsActivityName;
                if (name.Equals(TALKBACK_SETTinG_ACTIVITY_Name))
                {
                    Log.Debug(LogArea,"TalkBACk is active");
                    return true;
                }
            }
            Log.Debug(LogArea,"TalkBACk is inactive");
            return false;
        }
 }

这应该是您让它工作所需的全部。

希望能帮到你。

大佬总结

以上是大佬教程为你收集整理的使用虚拟 DPAD 在 Xamarin 应用程序中处理 Talkback全部内容,希望文章能够帮你解决使用虚拟 DPAD 在 Xamarin 应用程序中处理 Talkback所遇到的程序开发问题。

如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。