Hi, nice to meet you!

This is the second article in the accessibility series about Android accessibility proxies

In this article, we will talk about what a barrier-free agent is and share how we use it and how we can make it easier to use.

What is barrier-free agent?

When the user is in barrier-free mode, all operations of the user on the interface and View will trigger corresponding barrier-free events, which will be finally handled by barrier-free services, which will generate feedback and hints using the information in these events. Since Android1.6 (Api – 4), Android provides corresponding barrier-free agent extension of events, we can achieve barrier-free by proxy class (AccessibilityDelegate or AccessibilityDelegateCompat), To listen for the corresponding method callbacks and make some configuration or parameter changes to meet the changes in certain scenarios.

What can it do?

  • Respond to accessibility events, improve custom accessibility tips, and make some changes
  • Capture behavior information in user accessibility, such as data statistics or analysis

The former is also the main reason for the birth of barrier-free agent, while the latter is a little surprised that we found an operation in the SDK of a certain manufacturer during our investigation recently.

API,

AccessibilityDelegate

The Api is explained as follows, which comes from the Android documentation:

Api4

  • sendAccessibilityEvent()

    This method is called when the user performs an operation on the view. Events are categorized by user action type, such as TYPE_VIEW_CLICKED. Unless you are creating custom views, you usually do not need to implement this method.

  • sendAccessibilityEventUnchecked()

    If a call code needs to directly control the equipment whether to enable accessibility features (AccessibilityManager. IsEnabled ()) for inspection, using this method. If you implement this approach, you must perform the call as if accessibility were enabled, regardless of the actual system setup. You generally do not need to implement this method for custom views.

  • dispatchPopulateAccessibilityEvent()

    The system calls this method when your custom view generates a barrier-free event. Starting from the API level 14, the default implementation of this method to give a view called onPopulateAccessibilityEvent (), then this view each child call dispatchPopulateAccessibilityEvent () method. To support accessibility services on Android revisions prior to 4.0 (API level 14), you must replace this method and fill getText() with descriptive text from the custom view, which is read by accessibility services such as TalkBack.

Api14

  • onPopulateAccessibilityEvent()

    This method sets the AccessibilityEvent text-to-speech prompt for your view. This method is also called if the view is a child of the view generating barrier-free events.

    Note: Modifying properties in this method other than text may replace properties set in other methods. While you can use this method to modify accessibility attributes of events, but these changes should be restricted to text content, and use onInitializeAccessibilityEvent () method to modify other properties of the event.

    Note: Do not call the superclass implementation of this method in your code if the implementation of this event completely replaces the output text and does not allow other parts of the layout to modify its content.

  • onInitializeAccessibilityEvent()

    In addition to the literal content, this method is called to get additional information about the state of the view. If your custom view provides interactive controls other than a simple TextView or Button, you should replace this method and set additional information about the view to events that use this method, such as password field types, check box types, or states that provide user interaction or feedback. If you replace this method, you must call its superclass implementation and then modify only the unset properties of the superclass.

  • onInitializeAccessibilityNodeInfo()

    This method provides information about the state of the view for the accessibility service. The default View implementation has a standard set of View properties, but if your custom View provides interactive controls other than a simple TextView or Button, You should replace this method and set additional information about the view into the AccessibilityNodeInfo object handled by this method.

  • onRequestSendAccessibilityEvent()

    This method is called when your view’s children generate AccessibilityEvent. Through this step, the superview can modify accessibility events with additional information. This approach should be implemented only if your custom view has child views and the parent view can provide contextual information to a barrier-free event that facilitates barrier-free services.

Note that if our Api version >=14 is Android4.0 or above, we can implement the above method directly in the View.

. Otherwise, use ViewCompat setAccessibilityDelegate () or the setAccessibilityDelegate () sets the corresponding agent, to rewrite the corresponding method.

Matters needing attention

Barrier-free way there are two kinds of proxy Settings, default and compatible versions, namely AccessibilityDelegate and AccessibilityDelegateCompat.

Compat is generally a compatible version of the former to meet some of the functionality of the lower version, but I strongly recommend using the latter.

The specific reasons are:

Use AccessibilityDelegate as proxy class, when we will be the AccessibilityDelegate = null, namely we bind agent solution, we think after this agent will not be called, but it will still be every time is called, is outrageous.

And when you use AccessibilityDelegateCompat, you’ll find that when you call ViewCompat. SetAccessibilityDelegate (view, null), you wouldn’t be called proxy class before, isn’t it, If you look at the source code, you will see that when ViewCompat is set to NULL, the internal assignment is not direct, but a new instance is given.

Make accessible agents easier to use

In our current business, barrier-free agents are more likely to add selected state to a View, so we can easily write the following code:

val delegateCompat = object : AccessibilityDelegateCompat() {
    override fun onInitializeAccessibilityNodeInfo(
        host: View? , info:AccessibilityNodeInfoCompat?). {
        super.onInitializeAccessibilityNodeInfo(host, info)
      	// Your custom logicinfo? .isChecked = xxx info? .isCheckable =true
    }
}
ViewCompat.setAccessibilityDelegate(this, delegateCompat)
Copy the code

The simplest optimizations can be extracted for later reuse, resulting in code like this:

1 – > optimize

inline fun lazyAcesDeleteSelectSimple(crossinline obj: () -> Boolean): Lazy<AccessibilityDelegateCompat> =
    lazy {
        object : AccessibilityDelegateCompat() {
            override fun onInitializeAccessibilityNodeInfo(
                host: View? , info:AccessibilityNodeInfoCompat?). {
                super.onInitializeAccessibilityNodeInfo(host, info) info? .isChecked = obj.invoke() info? .isCheckable =true}}}/ / use
val xxxViewAcesDelegate by lazyAcesDeleteSelectSimple{
  // Your logic
   true or false
}
val view=View(null)
ViewCompat.setAccessibilityDelegate(view, xxxViewAcesDelegate)
Copy the code

This code is also easy to understand, as we initialize the proxy for subsequent use with Lazy delegates and extract the methods.

Optimization of 2 – >

Some time ago, my colleague mentioned in the code review that could you simplify the barrier-free part?

For our business scenario, most of the time, adding a proxy is just to add a selected state to the View or ImageView. I see that the way you write it now is to write a unified call method and callback, which is actually pretty good. Can that be simplified a little bit, like we have other configurations or changes in the future, etc.

For example, the View itself has an isSelected property. See if you can change this property to automatically adapt the selected state in barrierless mode. For external callers, I can easily adapt without having to worry about accessibility. For example, everyone is aware of the contenDescortrion property, but not everyone is aware of the need to pass delegates and agents, and it is not convenient to rewrite the corresponding methods in complex cases.

Here’s the new idea:

  • Added barrier-free interface, which contains some [simplified] configuration operations
  • Inherited fromAccessibilityNodeInfoCompatThe corresponding callback function is added and the barrier-free interface is realized
  • increaseViewExtension properties, such asView.accessDelegate , View.isAccessSelected, the former returns a barrier-free interface, and the latter is used to control thisviewWhether it is selected.

Add proxy interface:

interface IAccessibilityDelegate {

    If [setSelectedProvider] is implemented, this field is only used to view the status of */
    var isSelect: Boolean

    Note: If this method is enabled, this callback is used first, and [isSelect] is only viewed as the status */
    fun setSelectedProvider(obj: (() -> Boolean)?: IAccessibilityDelegate

    /** This method provides accessibility services with information about the state of the view, adding this listener for external listening */
    fun setInitializeNodeInfoListener(obj: (View.AccessibilityNodeInfoCompat) - >Unit): IAccessibilityDelegate

    /** Unbind all callback */
    fun unBind(a)
}
Copy the code

Add a proxy implementation class:

class CustomAccessibilityDelegateCompat : AccessibilityDelegateCompat(), IAccessibilityDelegate {

    override var isSelect: Boolean = false

    [IAccessibilityDelegate] * -> */ [IAccessibilityDelegate] * -> */
    private var onSelectedProvider: (() -> Boolean)? = null
    private var onInitializeAccessibilityNodeInfo: ((View, AccessibilityNodeInfoCompat) -> Unit)? = null

    /** This method provides information about view state for accessibility services. * /
    override fun onInitializeAccessibilityNodeInfo(
        host: View? , info:AccessibilityNodeInfoCompat?). {
        super.onInitializeAccessibilityNodeInfo(host, info)
        if (host == null || info == null) returnonInitializeAccessibilityNodeInfo? .invoke(host, info) isSelect = onSelectedProvider? .invoke() ? : (isSelect || host.isSelected) info.isChecked = isSelect info.isCheckable =true
    }

  
    override fun setSelectedProvider(obj: (() -> Boolean)?: IAccessibilityDelegate {
        onSelectedProvider = obj
        return this
    }

    override fun setInitializeNodeInfoListener(obj: (View.AccessibilityNodeInfoCompat) - >Unit): IAccessibilityDelegate {
        onInitializeAccessibilityNodeInfo = obj
        return this
    }

    override fun unBind(a) {
        onInitializeAccessibilityNodeInfo = null}}Copy the code

Add kt extension classes

@file:JvmName("AccessibilityUtils")

private const val ACCESS_DEFAULT_CONTENT_DESCRIPTION = "ACCESS_DEFAULT_CONTENT_DESCRIPTION"

/** * Using the View's default isSelect is also useful if you have already called [initXcfAccessDelegate] ** /
var View.isAccessSelected: Boolean
    get() = accessDelegate.isSelect || isSelected
    set(value) {
        accessDelegate.isSelect = value
    }

/** Get the custom barrier-free interface */
val View.accessDelegate: IAccessibilityDelegate
    get() {
        val delegate = ViewCompat.getAccessibilityDelegate(this) as? IAccessibilityDelegate
        if (delegate == null) {
            val newDelegate = CustomAccessibilityDelegateCompat()
            ViewCompat.setAccessibilityDelegate(this, newDelegate)
            return newDelegate
        }
        return delegate
    }

/** Initializes the barrier-free delegate to meet some basic view operation-free adaptation */
@JvmOverloads
fun View.initAccessDelegate(contentDescription: String = ACCESS_DEFAULT_CONTENT_DESCRIPTION):
    IAccessibilityDelegate {
    if(contentDescription ! = ACCESS_DEFAULT_CONTENT_DESCRIPTION)this.contentDescription = contentDescription
    return AccessDelegate
}
Copy the code

Usage:

// For example, there is a history code that uses ImageView as a switch.
fun test(a) {
    val toggleView = ImageView(context)
    // Set by extended properties
    toggleView.isAccessSelected = false
    // Set by custom logic
    toggleView.initXcfAccessDelegate("Xx switch").setSelectedProvider(::checkToggle)
}

/** Your business logic */
fun checkToggle(a): Boolean = false
Copy the code

After the above steps, we can easily add selected state to any View, and for other students, the cost is relatively low.

If you want to add a new API, you can also change the corresponding proxy class and define a new setXXXListener in the interface.

conclusion

Through the rewriting of barrier-free events, our adaptation cost on View is greatly reduced. For those methods that cannot be directly rewritten, we can also indirectly complete them through barrier-free agents. Relatively speaking, the cost is not high, and after we carry out corresponding encapsulation, it is easier to use.

reference

  • Make custom views easier to use

I am Petterp, a third-rate development, if this article is helpful to you, welcome to like support, your support is my continuous creation of the biggest encouragement!