There is an element I want TalkBack to skip when reading out lout the screen.
I can set it's contentDescription to null.
I also read about ImportantForAccessibility: indicates whether an element is visible or not to the Accessibility API.
Which other APIs are there?
Is it cleaner to use ImportantForAccessibility=false over contentDescription=null?
ImportantForAccessibility=false is used to hide any element from the accessibility tree, including buttons, content etc.
contentDescription=null is only useful for things like images (there may be other items I can't think of) that you want to hide as otherwise the Accessibility Tree will do it's best to find a suitable name for an item.
A prime example would be an ImageButton - if you use contentDescription=null then it will announce 'button' and the destination / button text. If you use ImportantForAccessibility=falseit would hide that item completely from the accessibility tree.
The best analogy I can come up with (if you are familiar with Web Standards) is that contentDescription is like an alt attribute or aria-labelledby attribute and ImportantForAccessibility=false is similar to aria-hidden="true".
One thing I would caution you on - other than decorative items you should not really be hiding items from the accessibility tree, just be careful that you are not giving a different experience to screen reader users (you didn't specify your use case, just wanted to hammer that point home).
Final thing - try it with TalkBack, testing it on a device is the quickest way to know if you got it right!
Related
I'm implementing a screen for Android TV, which has a screen title and a button on the left side. And a list of custom views/rows(selectable/clickable), arranged vertically, on the right side of the page.
We want the button on the left to be in focus when the user sees that screen. For that, I'm calling button.requestFocus() in the onResume() of the fragment.
This breaks the accessibility. When talkback is enabled, the first thing announced is the button's label. What I want is to announce the title first and then the button's label.
I tried to announce a custom text(could be title) by
rootView.announceForAccessibility(accessibilityText)
where rootView is the root of the xml layout and accessibilityText a text which needs to be announced.
But it doesn't help, and the button's label gets the priority.
How can I solve the issue?
I would ask you to consider WCAG Guideline 3.2.1:
The intent of this Success Criterion is to ensure that functionality is predictable as visitors navigate their way through a document. Any component that is able to trigger an event when it receives focus must not change the context. Examples of changing context when a component receives focus include, but are not limited to:
forms submitted automatically when a component receives focus;
new windows launched when a component receives focus;
focus is changed to another component when that component receives focus; <-- emphasis here
Also a quote from the Android Accessibility Team:
So something similar that people like to do is manage accessibility focus themselves. And again, this is a bad idea. accessibility focus has to be determined by the accessibility service, and just like announcements this creates an inconsistency in experience. And actually, that one of the biggest issues that accessibility users face, inconsistency, across applications and over time.
With that said, you may want to consider looking at ensuring the focus order / priority of the component using the following attributes:
android:nextFocusUp
android:nextFocusDown
android:nextFocusLeft
android:nextFocusRight
And also ensure that any group component that may get highlighted has the importantForAccessibility attribute set.
I'd like to try help some more, but without an example XML file, it's difficult to get to your particular use case. Have you tried testing the view layout with accessibility users?
I took a cue from this article by ATAUL MUNIM. I added a check if talkback is enabled, before requesting the focus explicitly.
protected fun isTalkBackEnabled(): Boolean {
val a11yServices = context?.getSystemService(ACCESSIBILITY_SERVICE) as? AccessibilityManager
return a11yServices?.isTouchExplorationEnabled?:false
}
and
if(isTalkBackEnabled().not()) {
button.requestFocus()
}
This solution pretty much bailed me out from the problem I was facing. It was also the only way forward for me because my app's min API level is 21 which eliminates the option to use android:screenReaderFocusable
In my Android app I have a custom layout that is being used as a button - it consists of some TextViews and an ImageView, additionally it has some gradient background.
I'm aligning my app now to conform to the Accessibility rules. In order to do so, I would need to convert this layout into a button, so that TalkBack can correctly indicate the action, that this whole layout is clickable and serves like a button.
I know that on iOS there is a possibility to set the UIAccessibilityTraits to treat such view as a button - this kind of solution would save me a huge amount of work in terms of migration.
Is there any similar solution on Android for that? What approach should I follow in order to make this layout recognized correctly by TalkBack?
No, there's no concept of accessibility traits on Android - but you can still get a good accessibility experience without needing to specifically convert your layout into a Button.
Generally, it's most important that TalkBack (or whatever accessibility service is being used - remember, it's not just TalkBack) is able to detect that the widget is clickable and to be able to read a coherent description of what it does. The additional information that it's a button, specifically, isn't super useful, especially because there are so many different kinds of UI elements that it's often a very ambiguous question whether something even is a button.
You can test this by selecting it in TalkBack and confirming that it reads the content description properly, says something along the lines of "Double tap to activate," and performs the correct action when you double tap.
If it's not correct, make sure the content description, clickable flag, and click action are set correctly on the widget's AccessibilityNodeInfo.
I currently retrieve the root node of the active window with getRootInActiveWindow(). Afterwards, I perform a breadth first search to get a list of all nodes.
My questions:
How can I traverse this list of nodes to get the focus order? Is this list ordered according to the nodes' focus order already? Is there a different approach of retrieving the focus order?
Thanks in advance.
Ultimately, no. You cannot, and the accessibility api documentation on this is very misleading.
One might think that the "focusSearch(int direction)" function of AccessibilityNodeInfo would be the way to go. But, ultimately it is broken, and if you go back into the inards of the Android Accessibility APIs you end up finding a function that's documented as such:
Searches for the nearest view in the specified direction that can take the input focus.
COOL, just what we want right? HOLD ON A SECOND. This function calls a function
AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId, mSourceNodeId, direction);
And this function is documented as such:
Finds the accessibility focused {#link android.view.accessibility.AccessibilityNodeInfo}. The search is performed in the window whose id is specified and starts from the node whose accessibility id is specified.
Dissappointingly, you will also find that this function does neither of those jobs (directional focus search, or directional accessibility focus search) well.
Ultimately, when an element in TalkBack gains accessibility focus due to the focus of an element, it's actually responding to the actual focus event coming from the OS and NOT any calculation that TalkBack is doing on input focus ordering.
In order to do this, you would basically have to implement the entire logic from the OS for Tab Ordering yourself, with less information the system itself has to do it, because the System has real android.widget.View objects to deal with, not fake AccessibilityNodeInfo objects with limited information. Any way you can come up to do this is going to be exceptionally fragile, and not guaranteed to line up with what the system actually does.
Your inclination to try a breadth first traversal of "input focusable" (the focusable property of AccessibilityNodeInfo) is a pretty reasonable solution. The problem is going to be, that the real system Tab Ordering is going to be a subset of that, which ignores things like elements existing on different windows and such.
Seriously, open up the TalkBack settings screen, turn on TalkBack, connect a hardware keyboard, go crazy on the tab and shift tab keys, and marvel at how you can't put focus on the "Settings" button. Your traversal function would get this wrong, because the "Settings" button is focusable but somehow, you can't put focus there. The reason why digs into some serious AOSP nonsense that I will omit, and no, I'm not sure if the information that would keep this corner case from coming up in your theoretical algorithm is available to AccessibilityServices.
To summarize, the breadth first traversal can theoretically work, but there are going to be a ton of corner cases, and no I'm not sure if the information to deal with each corner case correctly is available to AccessibilityServices. Also, notably, because of this particular issue, if you'd like the solution to be correct across 4.4, 5.0 and 7.0+ devices, you will have to have different variations of this calculation for different OS versions. YEP, those corner cases are going to be different! (Queue Evil Laughter).
My honest recommendation... give up.
I'm trying to make TalkBack work for my Android app, in my app layout I have a list of CardViews inside a RecyclerView, each CardView contains several TextViews and several Buttons: App layout image
When I turn on Android TalkBack, and tap on a CardView, TalkBack announces all the TextViews, but doesn't announce any Button. If I continue swiping right, the buttons will gain focus one by one. Not sure if my understanding is correct, but seems like TalkBack treats TextViews and Buttons differently because the buttons are focusable.
Is there a way to make TalkBack announce all content on the CardView (TextViews and Buttons) and keep the Buttons focusable (so I'll still be able to set focus on Buttons by swiping)?
PS:
One solution that worked is to directly set a content description on the CardView, and make the content description contain all content of TextViews and Buttons, but that's kind of hacky, so I'm trying to find a better solution here.
Any help is appreciated. Thanks!
The behaviour you've described is expected and your solution is the correct one.
For ViewGroups with no explicit content description, TalkBack will attempt to infer one from the children, by concatenating the content description (or text value if view extends from TextView) of all the children.
Since the buttons themselves are actionable (focusable/clickable), they are not included in the inferred content description.
Setting an explicit content description on the card is the correct approach to take - your goal should be to describe the information that the card represents. IMO, it should not contain the description for the buttons since they will be separately focusable and will be read aloud when the user focuses on them.
The best approach IMO is to hide the buttons if TalkBack is enabled, and to present the cards as entities that contain a single action and a single, explicitly-set content description.
It's important not to reduce functionality for TalkBack users - all the user goals that were achievable should still be achievable; there is no requirement that the goals must be achieved in the same way by all users. So in this case, you could make the card's primary action as the click action, and offer the actions from the buttons in another place in the app.
One pattern which works well is to display a dialog on click, and this contains all the actions. This has a couple of benefits:
the card is single action so navigating between cards is single-swipe only
the default dialog is accessible by default - no extra work needed
I wrote a blog post explaining how you can do it with (and without) a library I helped write. The section under "actions dialog" is the relevant bit.
A button is actionable. If you want your users to be able to do the actions of each button, they need to be separately focusable. If they aren't actionable, they shouldn't be buttons.
What is the best way to go about designing an Android application that features quite a bit of formulas and conversions? I was thinking that multiple Activities with ListViews sort of like a tree with the leaves being the actual calculations. However, after reading Android design principles it's better to avoid a pure navigational structure and try to reduce the deepness of the app.
For example:
Main Menu
Conversions
Weight
Distance
Distance - Speed - Time
Calculate air speed
Calculate distance traveled
Weather
METAR
TAF
So by the 3rd or 4th screen we've reached the actual individual calculator. Does this make sense? And if it does, is there a better way of designing this (maybe using action bars or tabs)?
By using a very simple UI concept of Expandable Listviews, in which there is more than one child row for each section of the parent row, by tapping on the "Conversions", could expand into sub-rows, likewise for each section as you see fit, and treat that as a Main Menu in a sense of a word, that is, your main application screen.
The nice advantage is that the nesting of the menus in the Android way, is eliminated and can accomodate as many as you wish.
The third or forth screen sounds like it's too deep down the rabbit hole, how about instead having multiple tabs for each "type"?
For example;
[Conversions][Distances][Weather]
If you add an ActionBar to it, you could have a type of filter as the GMAIL app has (where you select the account) and use it to toggle the different "modes" so to speak.
Another approach would be to have it all in one screen, just that you switch the active layout (or perhaps Fragment) via the filter I mentioned above.
Check out the GMAIL app, and you'll see what I mean in terms of the filter. :-)