Search code examples
androidviewaccessibilityviewgrouptalkback

Can a View (not ViewGroup) contain multiple UI components that each need Talkback support


Bacground

I have been working on stripping out a library that deals with adding Accessibility with Talkback that I have created in an existing app. Originally my custom views were all ViewGroups, so I got everything working amazingly with ViewGroups (focusable navigation with D-pad, initial view focus, and content descriptions)

When I was moving this to a standalone library, I noticed that it didn't work with View. I thought ViewGroup was the superclass, but it turns out that View is the superclass. So I have been trying to find some workarounds to fix my issue. I started to do the following, and have a question based on this approach...

Code In Question

public class Accessibility {
    public static ViewGroupAccessibility with(ViewGroup viewGroup) {
        return new ViewGroupAccessibility(viewGroup);
    }
    public static ViewAccessibility with(View view){
        return new ViewAccessibility(view);
    }
}

I have fully implemented ViewGroupAccessibility and I intend to fully implement ViewAccessibility as it is a stub right now. So far the below code works well with TalkBack, I can do ViewGroup related stuff with ViewGroups, and it appears that I can do View related stuff with Views; however, I am wondering if this is even needed

What I know

Accessibility.with(new RelativeLayout(...))  // Returns ViewGroupAccessibility as RelativeLayout is a ViewGroup
//

...will return a ViewGroupAccessibility that can handle ViewGroup related stuff that can contain many different View and ViewGroup. (See code at the bottom of this post for real usage, and what what methods are available for ViewGroupAccessibility)

Accessibility.with(new Button(...))  // Returns ViewAccessibility as Button is a View
//

...will return a ViewAccessibility that can handle single View only related stuff (that is my assumption). Think only a Button.

What I don't know

// Hypothetical Usage
Accessibility
    .with(new ClassThatExtendsView_WithMultipleComponentsThatCanHaveAccessibilitySetOnEachComponentIndividually(...));

// Custom View that extends View
public class ClassThatExtendsView_WithMultipleComponentsThatCanHaveAccessibilitySetOnEachComponentIndividually extends View { 
    ... 
}
  • Is this even possible? If no, then I am good. If yes, then I have a lot extra to think about
  • It will return a ViewAccessibility that can handle single View only, but then that would be the wrong thing to return.

Another way of asking the question is am I guaranteed that if a user calls Accessibility.with(View) that the given view will ALWAYS be a single view only? Like Just a single Button. Or can the View be made of more than one component


Full Code

You can check out the code at https://codereview.stackexchange.com/questions/134289/easily-add-accessibility-to-your-app-as-an-afterthought-yes-as-an-afterthought (there is also a GitHub link to the original code). I go in incredible detail into how the project was started, my design decisions, and my future goals all to help guide the code review process.

However, here is a snippet of a usage I have for ViewGroup

public class ContributionView extends RelativeLayout implements Mappable<Resume.Contribution> {

    // Called from Constructors
    private void init(AttributeSet attrs, int defStyle) {

        root = (ViewGroup) LayoutInflater.from(getContext()).inflate(
                R.layout.internal_contribution_view, this, true);

        ...

        // Declare Navigation Accessibility
        Accessibility.with(root)
                
                // Disable certain views in the ViewGroup from ever gaining focus
                .disableFocusableNavigationOn(
                        R.id.contribution_textview_creator,
                        R.id.contribution_textview_year)

                // For all focusable views in the ViewGroup, set the D-pad Navigation
                .setFocusableNavigationOn(txtProjectName)
                    .down(R.id.contribution_textview_description).complete()
                .setFocusableNavigationOn(txtContributionDescription)
                    .up(R.id.contribution_textview_name)
                    .down(R.id.contribution_textview_link).complete()
                .setFocusableNavigationOn(txtProjectLink)
                    .up(R.id.project_textview_description).complete()

                // Set which view in the ViewGroup will have be first to be focused
                .requestFocusOn(R.id.contribution_textview_name);

        invalidateView();

    }
  
    private void invalidateView() {

        ...

        // Declare Content Description Accessibility
        Accessibility.with(root)

                // Set the content descriptions for each focusable view in the ViewGroup

                // Set the content description for the Contribution Name
                .setAccessibilityTextOn(txtProjectName)
                    .setModifiableContentDescription(getProjectName())
                        .prepend("Contribution occurred on the Project called ")
                        .append(String.format(" by %s in %s",
                                getProjectCreator(),
                                getContributionYear())).complete()

                // Set the content description for the Contribution Description
                .setAccessibilityTextOn(txtContributionDescription)
                    .setModifiableContentDescription(getContributionDescription())
                        .prepend("Description: ").complete()

                // Set the content description for the Contribution URL
                .setAccessibilityTextOn(txtProjectLink)
                    .setModifiableContentDescription(getProjectLink())
                        .prepend("URL is ").complete();

    }

    ...

}

Solution

  • Yes, there is a way to move accessibility amongst the various areas/components of a View. It requires a little work, though.

    Start here: https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeProvider.html