I am adding accessibility to an app and I am using google's talkback for testing accessibility and I am unable to block certain views from being selected.
Besides removing all listeners and focusability, is there any better way to disable views from obtaining focus when they are hidden on the screen... ie having a "drawer" open and disabling the selection of items in the content container? It seems like there should be a cleaner solution to this or maybe a fix is required on the talkback team's side.
Thanks
Look into AccessibilityDelegateCompat available in support-v4 library - Link.
Create an instance of AccessibilityDelegateCompat and override the following method:
#Override
public void onInitializeAccessibilityNodeInfo(View host,
AccessibilityNodeInfoCompat info) {
// Check if 'host' is visible or not before calling the super method
if (host.getVisibility() != View.INVISIBLE) {
super.onInitializeAccessibilityNodeInfo(host, info);
}
}
Finally, use static method ViewCompat.setAccessibilityDelegate(View, AccessibilityDelegateCompat) for the views that are invisible.
Related
When I have a recycler view with 12 items, when accessibility focus goes onto cell number 3, for example, talkback announces "Double tap to activate. Item 3 of 12".
I want to keep the action but stop it from announcing the item position and item count. How can I do this? I have tried to assign a delegate to the recycler view but not sure what to override in the delegate.
How can I do this?
So I figured it out. An AccessibilityNodeInfo has a method called SetCollectionInfo(). CollectionInfo has properties like rowCont and columnCount.
I simply set the info to null.
Note, the below is xamarin:
private class TabLayoutTabAccessibilityDelegate: View.AccessibilityDelegate
{
public override void OnInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(host, info);
info.SetCollectionInfo(null);
}
}
This is a functionality that should not be disabled: it lets people using Talkback know that they are in a list, how long is the list, which element is currently focused (are there more elements above/below).
I'm using a custom dynamic contentDescription for my textview, so it has been implemented in my java class and not in my xml file.
private void enableButton(TextView textView, boolean enabled) {
if (textView == null) {
return;
}
if (!enabled) {
textView.setContentDescription(textView.getResources().getString(R.string.install_disabled));
} else {
textView.setContentDescription(textView.getResources().getString(R.string.install));
}
textView.setEnabled(enabled);
}
After I'm enabling my textview to be clickable, and when talkback is enabled, focusing on my textview is announcing the state of my textview which is "disabled". Is there a way to not announce that state?
I do not want to set the accessibility to be not important because I still want my dynamic contentDescription to be recited when talkback users focus on the textview.
Suggestion:
I believe the culprit is the "setEnabled" method that is somehow triggering and announcing the state of the textview, but I'm still not able to stop it from reciting that last.
My first answer is: LEAVE IT ALONE! The "disabled" announcement tells a TalkBack user that there is a user interface control there, that under some circumstances can be interacted with, but is not currently active. Given your description, this is exactly what you have. To remove the announcement is actually going to make things WORSE from an accessibility perspective, the explanations for why this is the case are covered in WCAG 1.3.1.
Definitions:
Button = android.widget.Button
button = a user interface component that does something when you click it.
Text = a user interface component that conveys information, but is not active
Long story short, the fact that the control is ever in a state that it can be active and "not disabled" is significant information on its own, and SHOULD be shared with the user. ESPECIALLY since you're using a "TextView" to implement this. This isn't a super uncommon practice, but one of the ways TalkBack calculates roles (button, link, image, text, etc) is by looking at the Class/Type of object. So, when it sees a TextView, it is going to assume Text, unless you inform it otherwise. Now, since you have added click listeners to your TextView (or Gesture Recognizers, or whatever) TalkBack may be able to figure out that the thing you're dealing with is actually a "button", and it may not. REGARDLESS, the fact that this "button" (lower case B!) is not active is important state to share with the user, and communicates to them the fact that they can somehow enable it and come back and interact with it later. This is immensely important information! Imagine if every button/link on a WebPage looked exactly like plane text? How would you know what to interact with?
Now, I will show you the different pieces of this puzzle, as information, but I really do encourage you to leave the announcement alone. This is coming from someone who routinely speaks at Accessibility conferences on Native Android development, PLEASE LEAVE THIS ANNOUNCEMENT IN. To not do so shows a misunderstanding of how users with sight impairments want to perceive controls within your application, and the information that is important to them.
The setEnabled() function on a TextView corresponds directly with the isEnabled() property of AccessibilityNodeInfo.
In order to implement the behavior you want, you want the AccessibilityNodeInfo representation of your TextView to be different from that of the actual representation of your TextView. In order to do this you can use AccessibilityDelegate, I'm actually not sure which callback you want to use. In one of these the node is likely to be "sealed" and in one of them it might not be sealed yet. You obviously want the one where the node is not yet sealed! Regardless the general approach is:
textView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
#Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
// Let the default implementation populate the info.
super.onInitializeAccessibilityNodeInfo(host, info);
// Override this particular property
info.setEnabled(whateverYouThinkItShouldBe);
}
});
Use setClickable(false) to replace setEnabled(false) will solve this problem.
I'm working on improving the accessibility within my app.
I have pretty complicated layout with cards. Each card has some clickable objects inside it, but it also has the global click-listener.
When I enable Talkback, select the card (not something inside it!), double-clicking (to open the card), the card gets the touch-event in the middle of the card.
As a result, nested object got click event and react respectively.
The question is how to determine, which item is in TalkBack's focus (green-rectangle-thing for me)? The idea is to disable inside touch-listeners, if card itself is in focus.
API level I want to support is 16 (Android 4.1+)
Thanks!
I think what would work best for you, is to override the accessibility delegate of layout view, listening for accessibility focus events. When focus is added to a card, remove listeners, when focus leaves your cards re attach your listeners. Attach this delegate to your layout view, and you should be able to watch as various views within your layout obtain and give up accessibility focus.
class MyAccessibilityDelegate extends View.AccessibilityDelegate {
#Override
public boolean onRequestSendAccessibilityEvent(ViewGroup viewGroup, View child, AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
//Do stuff in here! Maybe also do different stuff when focus is cleared!
}
return super.onRequestSendAccessibilityEvent(viewGroup, child, event);
}
}
The apis for this were added in API level 14 so you should be good to go!
I am trying to change the text announced by TalkBack when an ImageView is focused through accessibility.
The Android documentation states that we should create an AccessibilityDelegate, and override onPopulateAccessibilityEvent (I am using the support library because I am also supporting GingerBread)
Thus, my code is the following:
public static void setImageDelegate(View view) {
AccessibilityDelegateCompat delegate = new AccessibilityDelegateCompat() {
#Override
public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
event.getText().add(event.getContentDescription() + ", image");
}
};
ViewCompat.setAccessibilityDelegate(view, delegate);
}
When I call this function on my imageview, the delegate gets set, but the modified text is not being read. It simply reads the original content description. Am I doing something wrong or missing something about the accessibility functions?
Stepping through the code, it seems to be adding the correct text, but still, no change in spoken text.
Note: the above is a contrived example, content description could be used, but I'm trying to figure out why it doesn't work before I try it on custom views.
In ICS and above, TalkBack doesn't use the accessibility event text in most cases. Instead, it checks the text and content description of the AccessibilityNodeInfo exposed by the view. You would need to override onInitializeAccessibilityNodeInfo.
In most cases, though, you would just want to call View.setContentDescription.
In this particular case, you shouldn't set anything since TalkBack handles speaking control types and capabilities. We strongly advise developers against adding descriptions like "button" or "image."
I am using an ExpandableListView. It works fine, but I want to add an OnGroupExpandListener;
The problem is if I override it, the 'basic' default behaviour for that is overridden as well. I basically want something like this:
mTeamListAdapter = new TeamListAdapter(getLayoutInflater());
mTeamList = (ExpandableListView) findViewById(R.id.screen_team_teamslist);
mTeamList.setAdapter(mTeamListAdapter);
mTeamList.setGroupIndicator(null);
mTeamList.setOnGroupExpandListener(new OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
// do my stuff..
// something like "super();" to enable the other default behaviour
}
});
Specifically, I want to make a Button visible (and clickable - it is "android:visibility="gone" before) on each group in the ExpandableListView. I still want each group, and its children, to be clickable as well!
How do I do this?
EDIT: By "basic default behaviour", I mean that if I override that method (OnGroupExpand) it seems I cannot expand/collapse the groups anymore!
The assertion that "the 'basic' default behaviour is overridden" when an OnGroupExpandListener is set is not correct.
You can verify it by looking at the source code for ExpandableListView (see the handleItemClick method):
http://codesearch.google.com/codesearch#uX1GffpyOZk/core/java/android/widget/ExpandableListView.java&q=package:android.git.kernel.org%20file:android/widget/ExpandableListView.java&l=1
Are you sure you haven't set a OnGroupClickListener? This WILL override group expansion if its OnGroupClick implementation returns true (i.e. the click is deemed to be handled).
Either that, or something in your specific OnGroupExpand implementation is affecting the data that your ExpandableListAdapter uses to display the child rows for the groups.