下面列出了android.support.v4.view.accessibility.AccessibilityNodeInfoCompat#getChildCount ( ) 实例代码,或者点击链接到github查看源代码,也可以在右侧发表评论。
private static AccessibilityNodeInfoCompat refreshFromChild(
AccessibilityNodeInfoCompat node) {
if (node.getChildCount() > 0) {
AccessibilityNodeInfoCompat firstChild = node.getChild(0);
if (firstChild != null) {
AccessibilityNodeInfoCompat parent = firstChild.getParent();
firstChild.recycle();
if (node.equals(parent)) {
return parent;
} else {
recycleNodes(parent);
}
}
}
return null;
}
private static AccessibilityNodeInfoCompat refreshFromParent(
AccessibilityNodeInfoCompat node) {
AccessibilityNodeInfoCompat parent = node.getParent();
if (parent != null) {
try {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; ++i) {
AccessibilityNodeInfoCompat child = parent.getChild(i);
if (node.equals(child)) {
return child;
}
recycleNodes(child);
}
} finally {
parent.recycle();
}
}
return null;
}
public static AccessibilityNodeInfoCompat findFirstFocusableDescendant(
AccessibilityNodeInfoCompat root, Context context) {
// null guard and shortcut for leaf nodes.
if (root == null || root.getChildCount() <= 0) {
return null;
}
HashSet<AccessibilityNodeInfoCompat> seenNodes =
new HashSet<AccessibilityNodeInfoCompat>();
seenNodes.add(root);
try {
return findFirstFocusableDescendantInternal(
root, context, seenNodes);
} finally {
seenNodes.remove(root); // Not owned by us.
AccessibilityNodeInfoUtils.recycleNodes(seenNodes);
}
}
public static AccessibilityNodeInfoCompat findLastFocusableDescendant(
AccessibilityNodeInfoCompat root, Context context) {
// null guard and shortcut for leaf nodes.
if (root == null || root.getChildCount() <= 0) {
return null;
}
HashSet<AccessibilityNodeInfoCompat> seenNodes =
new HashSet<AccessibilityNodeInfoCompat>();
seenNodes.add(root);
try {
return findLastFocusableDescendantInternal(
root, context, seenNodes);
} finally {
seenNodes.remove(root); // Not owned by us.
AccessibilityNodeInfoUtils.recycleNodes(seenNodes);
}
}
@Override
public void format(Editable result,
Context context,
AccessibilityNodeInfoCompat node) {
boolean empty = (node.getChildCount() == 0);
int res;
if (AccessibilityNodeInfoUtils.nodeMatchesClassByType(context, node,
GridView.class)) {
res = empty ? R.string.type_emptygridview : R.string.type_gridview;
} else if (AccessibilityNodeInfoUtils.nodeMatchesClassByType(
context, node, ScrollView.class)) {
res = empty ? R.string.type_emptyscrollview
: R.string.type_scrollview;
} else {
res = empty ? R.string.type_emptylistview : R.string.type_listview;
}
result.append(context.getString(res));
}
/**
* Returns whether the supplied {@link View} and {@link AccessibilityNodeInfoCompat} would
* produce spoken feedback if it were accessibility focused. NOTE: not all speaking nodes are
* focusable.
*
* @param view The {@link View} to evaluate
* @param node The {@link AccessibilityNodeInfoCompat} to evaluate
* @return {@code true} if it meets the criterion for producing spoken feedback
*/
public static boolean isSpeakingNode(
@Nullable AccessibilityNodeInfoCompat node,
@Nullable View view) {
if (node == null || view == null) {
return false;
}
if (!node.isVisibleToUser()) {
return false;
}
int important = ViewCompat.getImportantForAccessibility(view);
if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS ||
(important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO &&
node.getChildCount() <= 0)) {
return false;
}
return node.isCheckable() || hasText(node) || hasNonActionableSpeakingDescendants(node, view);
}
/**
* Returns the result of applying a filter using breadth-first traversal.
*
* @param context The parent context.
* @param node The root node to traverse from.
* @param filter The filter to satisfy.
* @return The first node reached via BFS traversal that satisfies the
* filter.
*/
public static AccessibilityNodeInfoCompat searchFromBfs(
Context context, AccessibilityNodeInfoCompat node, NodeFilter filter) {
if (node == null) {
return null;
}
final LinkedList<AccessibilityNodeInfoCompat> queue =
new LinkedList<AccessibilityNodeInfoCompat>();
queue.add(AccessibilityNodeInfoCompat.obtain(node));
while (!queue.isEmpty()) {
final AccessibilityNodeInfoCompat item = queue.removeFirst();
if (filter.accept(context, item)) {
return AccessibilityNodeInfoCompat.obtain(item);
}
final int childCount = item.getChildCount();
for (int i = 0; i < childCount; i++) {
final AccessibilityNodeInfoCompat child = item.getChild(i);
if (child != null) {
queue.addLast(child);
}
}
}
return null;
}
/**
* Returns the result of applying a filter using breadth-first traversal.
*
* @param context The parent context.
* @param node The root node to traverse from.
* @param filter The filter to satisfy.
* @param maxResults The number of results to stop searching after
* @return Returns all nodes reached via BFS traversal that satisfies the
* filter.
*/
public static List<AccessibilityNodeInfoCompat> searchAllFromBfs(Context context,
AccessibilityNodeInfoCompat node, NodeFilter filter) {
if (node == null) {
return null;
}
final List<AccessibilityNodeInfoCompat> toReturn =
new ArrayList<AccessibilityNodeInfoCompat>();
final LinkedList<AccessibilityNodeInfoCompat> queue =
new LinkedList<AccessibilityNodeInfoCompat>();
queue.add(AccessibilityNodeInfoCompat.obtain(node));
while (!queue.isEmpty()) {
final AccessibilityNodeInfoCompat item = queue.removeFirst();
if (filter.accept(context, item)) {
toReturn.add(AccessibilityNodeInfoCompat.obtain(item));
}
final int childCount = item.getChildCount();
for (int i = 0; i < childCount; i++) {
final AccessibilityNodeInfoCompat child = item.getChild(i);
if (child != null) {
queue.addLast(child);
}
}
}
return toReturn;
}
/**
* Traverses to the next sibling of this node within its parent, returning
* {@code true} on success.
*/
public boolean nextSibling() {
if (mNode == null) {
return false;
}
AccessibilityNodeInfoCompat parent = mNode.getParent();
if (parent == null) {
return false;
}
try {
int childCount = parent.getChildCount();
int childNumber = getChildNumber(parent);
if (childNumber < 0) {
return false;
}
for (int i = childNumber + 1; i < childCount; ++i) {
AccessibilityNodeInfoCompat newNode =
parent.getChild(i);
if (newNode == null) {
return false;
}
if (AccessibilityNodeInfoUtils.isVisibleOrLegacy(newNode)) {
reset(newNode);
return true;
}
newNode.recycle();
}
} finally {
parent.recycle();
}
return false;
}
private int getChildNumber(AccessibilityNodeInfoCompat parent) {
int ret = -1;
int childCount = parent.getChildCount();
for (int i = 0; i < childCount && ret < 0; ++i) {
AccessibilityNodeInfoCompat child = parent.getChild(i);
if (mNode.equals(child)) {
ret = i;
}
if (child != null) {
child.recycle();
}
}
return ret;
}
/**
* Determines whether or not the given node contains native web content (and not ChromeVox).
*
* @param node The node to evaluate
* @return {@code true} if the node contains native web content, {@code false} otherwise
*/
public static boolean hasNativeWebContent(AccessibilityNodeInfoCompat node) {
if (node == null) {
return false;
}
if (!supportsWebActions(node)) {
return false;
}
// ChromeVox does not have sub elements, so if the parent element also has web content
// this cannot be ChromeVox.
AccessibilityNodeInfoCompat parent = node.getParent();
if (supportsWebActions(parent)) {
if (parent != null) {
parent.recycle();
}
return true;
}
if (parent != null) {
parent.recycle();
}
// ChromeVox never has child elements
return node.getChildCount() > 0;
}
/**
* Determines whether or not the given node contains ChromeVox content.
*
* @param node The node to evaluate
* @return {@code true} if the node contains ChromeVox content, {@code false} otherwise
*/
public static boolean hasLegacyWebContent(AccessibilityNodeInfoCompat node) {
if (node == null) {
return false;
}
if (!supportsWebActions(node)) {
return false;
}
// ChromeVox does not have sub elements, so if the parent element also has web content
// this cannot be ChromeVox.
AccessibilityNodeInfoCompat parent = node.getParent();
if (supportsWebActions(parent)) {
if (parent != null) {
parent.recycle();
}
return false;
}
if (parent != null) {
parent.recycle();
}
// ChromeVox never has child elements
return node.getChildCount() == 0;
}
/**
* Formats {@code node} and its descendants, appending the result
* to {@code sb}.
*/
private void formatSubtree(AccessibilityNodeInfoCompat node,
Editable result) {
if (!node.isVisibleToUser()) {
return;
}
BrailleRule rule = mRuleRepository.find(node);
SpannableStringBuilder subtreeResult = new SpannableStringBuilder();
rule.format(subtreeResult, mContext, node);
if (rule.includeChildren(node, mContext)) {
int childCount = node.getChildCount();
for (int i = 0; i < childCount; ++i) {
AccessibilityNodeInfoCompat child = node.getChild(i);
if (child == null) {
continue;
}
formatSubtree(child, subtreeResult);
child.recycle();
}
}
if (!TextUtils.isEmpty(subtreeResult)) {
// If the node is accessibility focused, add the focus span
// here to cover the node and its formatted children.
// This is a fallback in case the formatting rule hasn't set
// focus by itself.
if (node.isAccessibilityFocused()
&& subtreeResult.getSpans(0, subtreeResult.length(),
DisplaySpans.FocusSpan.class).length == 0) {
DisplaySpans.addFocus(subtreeResult, 0,
subtreeResult.length());
}
addNodeSpanForUncovered(node, subtreeResult);
StringUtils.appendWithSpaces(result, subtreeResult);
}
}
private static AccessibilityNodeInfoCompat
findFirstFocusableDescendantInternal(
AccessibilityNodeInfoCompat root, Context context,
HashSet<AccessibilityNodeInfoCompat> seenNodes) {
for (int i = 0, end = root.getChildCount(); i < end; ++i) {
AccessibilityNodeInfoCompat child = root.getChild(i);
if (child == null) {
continue;
}
if (AccessibilityNodeInfoUtils.shouldFocusNode(
context, child)) {
return child;
}
if (!seenNodes.add(child)) {
LogUtils.log(FocusFinder.class, Log.ERROR,
"Cycle in node tree");
child.recycle();
return null;
}
AccessibilityNodeInfoCompat n =
findFirstFocusableDescendantInternal(
child, context, seenNodes);
if (n != null) {
return n;
}
}
return null;
}
private static AccessibilityNodeInfoCompat
findLastFocusableDescendantInternal(
AccessibilityNodeInfoCompat root, Context context,
HashSet<AccessibilityNodeInfoCompat> seenNodes) {
for (int end = root.getChildCount(), i = end - 1; i >= 0; --i) {
AccessibilityNodeInfoCompat child = root.getChild(i);
if (child == null) {
continue;
}
AccessibilityNodeInfoCompat n =
findLastFocusableDescendantInternal(
child, context, seenNodes);
if (n != null) {
return n;
}
if (AccessibilityNodeInfoUtils.shouldFocusNode(context, child)) {
return child;
}
if (!seenNodes.add(child)) {
LogUtils.log(FocusFinder.class, Log.ERROR,
"Cycle in node tree");
child.recycle();
return null;
}
}
return null;
}
private ArrayList<CopyNode> traverseNode(AccessibilityNodeInfoCompat nodeInfo, int width, int height) {
ArrayList<CopyNode> nodeList = new ArrayList();
if (nodeInfo != null && nodeInfo.getInfo() != null) {
nodeInfo.refresh();
for (int i = 0; i < nodeInfo.getChildCount(); ++i) {
//递归遍历nodeInfo
nodeList.addAll(traverseNode(nodeInfo.getChild(i), width, height));
}
if (nodeInfo.getClassName() != null && nodeInfo.getClassName().equals("android.webkit.WebView")) {
return nodeList;
} else {
String content = null;
String description = content;
if (nodeInfo.getContentDescription() != null) {
description = content;
if (!"".equals(nodeInfo.getContentDescription())) {
description = nodeInfo.getContentDescription().toString();
}
}
content = description;
if (nodeInfo.getText() != null) {
content = description;
if (!"".equals(nodeInfo.getText())) {
content = nodeInfo.getText().toString();
}
}
if (content != null) {
Rect outBounds = new Rect();
nodeInfo.getBoundsInScreen(outBounds);
if (checkBound(outBounds, width, height)) {
nodeList.add(new CopyNode(outBounds, content));
}
}
return nodeList;
}
} else {
return nodeList;
}
}
/**
* Returns whether a node should receive accessibility focus from
* navigation. This method should never be called recursively, since it
* traverses up the parent hierarchy on every call.
*
* @see #findFocusFromHover(Context, AccessibilityNodeInfoCompat)
*/
public static boolean shouldFocusNode(Context context, AccessibilityNodeInfoCompat node) {
if (node == null) {
return false;
}
if (!isVisibleOrLegacy(node)) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Don't focus, node is not visible");
return false;
}
if (FILTER_ACCESSIBILITY_FOCUSABLE.accept(context, node)) {
// TODO: This may still result in focusing non-speaking nodes, but it
// won't prevent unlabeled buttons from receiving focus.
if (node.getChildCount() <= 0) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Focus, node is focusable and has no children");
return true;
} else if (isSpeakingNode(context, node)) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Focus, node is focusable and has something to speak");
return true;
} else {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Don't focus, node is focusable but has nothing to speak");
return false;
}
}
// If this node has no focusable ancestors, but it still has text,
// then it should receive focus from navigation and be read aloud.
if (!hasMatchingAncestor(context, node, FILTER_ACCESSIBILITY_FOCUSABLE)
&& hasText(node)) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Focus, node has text and no focusable ancestors");
return true;
}
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Don't focus, failed all focusability tests");
return false;
}
private static boolean hasNonActionableSpeakingChildren(
Context context, AccessibilityNodeInfoCompat node) {
final int childCount = node.getChildCount();
AccessibilityNodeInfoCompat child = null;
// Has non-actionable, speaking children?
for (int i = 0; i < childCount; i++) {
try {
child = node.getChild(i);
if (child == null) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Child %d is null, skipping it", i);
continue;
}
// Ignore invisible nodes.
if (!isVisibleOrLegacy(child)) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Child %d is invisible, skipping it", i);
continue;
}
// Ignore focusable nodes.
if (FILTER_ACCESSIBILITY_FOCUSABLE.accept(context, child)) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Child %d is focusable, skipping it", i);
continue;
}
// Recursively check non-focusable child nodes.
// TODO: Mutual recursion is probably not a good idea.
if (isSpeakingNode(context, child)) {
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Does have actionable speaking children (child %d)", i);
return true;
}
} finally {
AccessibilityNodeInfoUtils.recycleNodes(child);
}
}
LogUtils.log(AccessibilityNodeInfoUtils.class, Log.VERBOSE,
"Does not have non-actionable speaking children");
return false;
}
@Nullable
public static String getFocusableReasons(View view) {
AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);
try {
boolean hasText = AccessibilityUtil.hasText(node);
boolean isCheckable = node.isCheckable();
boolean hasNonActionableSpeakingDescendants =
AccessibilityUtil.hasNonActionableSpeakingDescendants(node, view);
if (AccessibilityUtil.isActionableForAccessibility(node)) {
if (node.getChildCount() <= 0) {
return "View is actionable and has no children.";
} else if (hasText) {
return "View is actionable and has a description.";
} else if (isCheckable) {
return "View is actionable and checkable.";
} else if (hasNonActionableSpeakingDescendants) {
return "View is actionable and has non-actionable descendants with descriptions.";
}
}
if (AccessibilityUtil.isTopLevelScrollItem(node, view)) {
if (hasText) {
return "View is a direct child of a scrollable container and has a description.";
} else if (isCheckable) {
return "View is a direct child of a scrollable container and is checkable.";
} else if (hasNonActionableSpeakingDescendants) {
return
"View is a direct child of a scrollable container and has non-actionable " +
"descendants with descriptions.";
}
}
if (hasText) {
return "View has a description and is not actionable, but has no actionable ancestor.";
}
return null;
} finally {
node.recycle();
}
}
public static boolean getIgnored(View view) {
int important = ViewCompat.getImportantForAccessibility(view);
if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO ||
important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
return true;
}
// Go all the way up the tree to make sure no parent has hidden its descendants
ViewParent parent = view.getParent();
while (parent instanceof View) {
if (ViewCompat.getImportantForAccessibility((View) parent)
== ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
return true;
}
parent = parent.getParent();
}
AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);
try {
if (!node.isVisibleToUser()) {
return true;
}
if (AccessibilityUtil.isAccessibilityFocusable(node, view)) {
if (node.getChildCount() <= 0) {
// Leaves that are accessibility focusable are never ignored, even if they don't have a
// speakable description
return false;
} else if (AccessibilityUtil.isSpeakingNode(node, view)) {
// Node is focusable and has something to speak
return false;
}
// Node is focusable and has nothing to speak
return true;
}
// If this node has no focusable ancestors, but it still has text,
// then it should receive focus from navigation and be read aloud.
if (!AccessibilityUtil.hasFocusableAncestor(node, view) && AccessibilityUtil.hasText(node)) {
return false;
}
return true;
} finally {
node.recycle();
}
}