Android:如何在更改相关视图后重绘可访问性虚拟节点

时间:2018-06-04 12:22:53

标签: android android-accessibility accessibility-api

在我的可滚动自定义视图中添加辅助功能支持(BackTalk)时,我无法弄清楚在滚动视图相关UI视图后如何更新聚焦虚拟节点。

我正在尝试向自定义视图添加辅助功能支持,这与Google日历应用程序非常相似(带日程安排的日视图)。我为静态显示的事件添加了支持(通过扩展ExploreByTouchHelper类),但是由于自定义视图是可滚动的,因此垂直滚动也会影响聚焦虚拟节点,但它会保留在同一个位置。如何更新绘制的聚焦虚拟节点(绿色矩形)。我在Google日历应用中看到它按预期工作,因此确实可以。

private static class WeekViewTouchHelper extends ExploreByTouchHelper {

    private WeekView mWeekView;
    private final Rect mDefaultBounds;

    /**
     * Constructs a new helper that can expose a virtual view hierarchy for the
     * specified host view.
     *
     * @param view view whose virtual view hierarchy is exposed by this helper
     */
    public WeekViewTouchHelper(WeekView view){
        super(view);
        mWeekView = view;
        mDefaultBounds = new Rect(0, 0, 1, 1);
    }

    /**
     * Returns default bounds
     * Please note: we usually don't need to use them if everything goes fine
     *
     * @return {@link Rect} The default bounds
     */
    @NonNull
    private Rect getDefaultBounds(){
        return mDefaultBounds;
    }

    @Nullable
    private AccessibilityItem getAccessibleItemUnderCoordinates(float x, float y){
        for (AccessibilityItem each : mWeekView.mAccessibilityItemMap.values()){
            if (each.getRectF() != null &&
                    x >= each.getRectF().left &&
                    x <= each.getRectF().right &&
                    y >= each.getRectF().top &&
                    y <= each.getRectF().bottom){
                return each;
            }
        }

        return null;
    }

    @Nullable
    private AccessibilityItem getAccessibleItemUnderId(int virtualViewId){
        if (mWeekView.mAccessibilityItemMap.containsKey(virtualViewId)){
            return mWeekView.mAccessibilityItemMap.get(virtualViewId);
        }

        return null;
    }

    /**
     * Provides a mapping between view-relative coordinates and logical items.
     * In another words here you need to return virtual view according to provided
     * {code x} and {code y}
     *
     * @param x The x coordinate
     * @param y The y coordinate
     * @return The id of virtual view according to provided coordinates
     */
    @Override
    protected int getVirtualViewAt(float x, float y) {

        AccessibilityItem item = getAccessibleItemUnderCoordinates(x, y);

        if (item == null){
            Logger.v(TAG, "Didn't find virtual view at  (" + x + ", " + y + ")");
            return INVALID_ID;
        }

        return item.getVirtualViewId();
    }

    /**
     * Populates a list with the view's visible items.
     * You need to return id's of virtual views that are currently visible
     *
     * @param virtualViewIds {@link List<Integer>} The list of virtual ids
     */
    @Override
    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {

        // Expose extra accessibility nodes declared by the component to the
        // accessibility framework. The actual nodes will be populated in
        // {@link #onPopulateNodeForVirtualView}.
        virtualViewIds.addAll(mWeekView.mAccessibilityItemMap.keySet());
    }

    /**
     * Populates an AccessibilityEvent with information about the specified item.
     *
     * AddS content description or text for virtual view related to provided
     * {@code virtualViewId}
     *
     * @param virtualViewId int, The id of virtual view to populate
     * @param event {@link AccessibilityEvent} The event
     */
    @Override
    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
        // TODO: ExploreByTouchHelper enforces subclasses to set a content description
        // or text on new events but components don't provide APIs to do so yet
        AccessibilityItem item = getAccessibleItemUnderId(virtualViewId);
        final String description = item != null ? item.getContentDescription() : "";
        event.setContentDescription(description);
    }

    /**
     * Populates an AccessibilityNodeInfoCompat with information about the specified item.
     *
     * Here we set parameters of virtual view, it's borders within parent view and optional
     * content description.
     *
     * @param virtualViewId int the id of virtual view
     * @param node {@link AccessibilityNodeInfoCompat} The node to populate
     */
    @Override
    protected void onPopulateNodeForVirtualView(int virtualViewId,
                                                @NonNull AccessibilityNodeInfoCompat node) {

        final AccessibilityItem item = getAccessibleItemUnderId(virtualViewId);
        if (item == null){
            Logger.e(TAG, "No accessible item found for view: " + virtualViewId);
            // ExploreByTouchHelper requires us to add something anyway
            node.setContentDescription("");
            node.setBoundsInParent(getDefaultBounds());
            return;
        }

        Rect rect = item.getVirtualViewBoundsRect();
        // Set virtual view bounds in parent
        node.setBoundsInParent(rect != null ? rect : getDefaultBounds());
        // Set action that is available for this view
        node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
        // Get item dimensions (width)
        String description = item.getContentDescription();
        // Set content description into node
        node.setContentDescription(description);

        node.setScrollable(true);
    }

    /**
     * Performs the specified accessibility action on the item associated with the virtual
     * view identifier.
     *
     * @param virtualViewId int the id of virtual view
     * @param action The id of action
     * @param args {@link Bundle} provided with action arguments
     * @return true | false, true if action was handled
     */
    @Override
    protected boolean onPerformActionForVirtualView(int virtualViewId,
                                                    int action,
                                                    @Nullable Bundle args) {

        // TODO: Add here logic for actions
        return false;
    }

    /**
     * Makes focused virtual view according to provided id
     *
     * @param virtualViewId int, the id of virtual view to make focused
     */
    public void setFocusedVirtualView(int virtualViewId){
        getAccessibilityNodeProvider(mWeekView).performAction(virtualViewId,
                AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS ,null);
    }

    /**
     * Clears currently focused virtual view, no-op if there is no focused view
     */
    public void clearFocusedVirtualView(){
        final int focusedVirtualViewId = getAccessibilityFocusedVirtualViewId();
        if (ExploreByTouchHelper.INVALID_ID != focusedVirtualViewId){
            getAccessibilityNodeProvider(mWeekView).performAction(focusedVirtualViewId,
                    AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
        }
    }

    /**
     * Updates currently focused item, no-op if there is no focused view
     */
    public void updateFocusedItem() {
        AccessibilityNodeProviderCompat nodeProvider = getAccessibilityNodeProvider(mWeekView);
        AccessibilityNodeInfoCompat focusedNode = nodeProvider.
                findFocus(AccessibilityNodeInfoCompat.FOCUS_ACCESSIBILITY);
        if (focusedNode != null){
            // TODO: Implement refresh logic here
        }
    }
}

0 个答案:

没有答案