在我的可滚动自定义视图中添加辅助功能支持(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
}
}
}