diff options
author | Svetoslav <svetoslavganov@google.com> | 2014-10-24 21:57:37 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-10-24 21:57:39 +0000 |
commit | 74a4a8d6162dde06297ff6c3ef264e7f9066b88f (patch) | |
tree | 210603d49c7409ce946daebdfc7dfc7ea112185d | |
parent | cc66a236f90cbef7f9562b4179c3aec517f4f082 (diff) | |
parent | 8c4e97db879eef3b943325a971a145e5223f49f8 (diff) | |
download | frameworks_base-74a4a8d6162dde06297ff6c3ef264e7f9066b88f.zip frameworks_base-74a4a8d6162dde06297ff6c3ef264e7f9066b88f.tar.gz frameworks_base-74a4a8d6162dde06297ff6c3ef264e7f9066b88f.tar.bz2 |
Merge "Enhance computation of click point for accessibility." into lmp-mr1-dev
-rw-r--r-- | core/java/android/view/View.java | 15 | ||||
-rw-r--r-- | core/java/android/view/ViewGroup.java | 210 |
2 files changed, 189 insertions, 36 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8664a24..980d6c4 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5877,6 +5877,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return true; } + /** + * Adds the clickable rectangles withing the bounds of this view. They + * may overlap. This method is intended for use only by the accessibility + * layer. + * + * @param outRects List to which to add clickable areas. + */ + void addClickableRectsForAccessibility(List<RectF> outRects) { + if (isClickable() || isLongClickable()) { + RectF bounds = new RectF(); + bounds.set(0, 0, getWidth(), getHeight()); + outRects.add(bounds); + } + } + static void offsetRects(List<RectF> rects, float offsetX, float offsetY) { final int rectCount = rects.size(); for (int i = 0; i < rectCount; i++) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 134171a..7c7e3e7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -51,8 +51,10 @@ import com.android.internal.util.Predicate; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; @@ -468,6 +470,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @ViewDebug.ExportedProperty(category = "layout") private int mChildCountWithTransientState = 0; + // Iterator over the children in decreasing Z order (top children first). + private OrderedChildIterator mOrderedChildIterator; + /** * Currently registered axes for nested scrolling. Flag set consisting of * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE} @@ -817,19 +822,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return false; } - // Check whether any clickable siblings cover the child - // view and if so keep track of the intersections. Also - // respect Z ordering when iterating over children. - ArrayList<View> orderedList = buildOrderedChildList(); - final boolean useCustomOrder = orderedList == null - && isChildrenDrawingOrderEnabled(); - - final int childCount = mChildrenCount; - for (int i = childCount - 1; i >= 0; i--) { - final int childIndex = useCustomOrder - ? getChildDrawingOrder(childCount, i) : i; - final View sibling = (orderedList == null) - ? mChildren[childIndex] : orderedList.get(childIndex); + Iterator<View> iterator = obtainOrderedChildIterator(); + while (iterator.hasNext()) { + View sibling = iterator.next(); // We care only about siblings over the child. if (sibling == child) { @@ -837,12 +832,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } // Ignore invisible views as they are not interactive. - if (sibling.getVisibility() != View.VISIBLE) { - continue; - } - - // If sibling is not interactive we do not care. - if (!sibling.isClickable() && !sibling.isLongClickable()) { + if (!isVisible(sibling)) { continue; } @@ -850,29 +840,36 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager RectF siblingBounds = mAttachInfo.mTmpTransformRect1; siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight()); - // Take into account the sibling transformation matrix. - if (!sibling.hasIdentityMatrix()) { - sibling.getMatrix().mapRect(siblingBounds); - } - - // Offset the sibling to our coordinates. - final int siblingDx = sibling.mLeft - mScrollX; - final int siblingDy = sibling.mTop - mScrollY; - siblingBounds.offset(siblingDx, siblingDy); + // Translate the sibling bounds to our coordinates. + offsetChildRectToMyCoords(siblingBounds, sibling); // Compute the intersection between the child and the sibling. if (siblingBounds.intersect(bounds)) { - // If an interactive sibling completely covers the child, done. - if (siblingBounds.equals(bounds)) { - if (orderedList != null) orderedList.clear(); - return false; + List<RectF> clickableRects = new ArrayList<>(); + sibling.addClickableRectsForAccessibility(clickableRects); + + final int clickableRectCount = clickableRects.size(); + for (int j = 0; j < clickableRectCount; j++) { + RectF clickableRect = clickableRects.get(j); + + // Translate the clickable rect to our coordinates. + offsetChildRectToMyCoords(clickableRect, sibling); + + // Compute the intersection between the child and the clickable rects. + if (clickableRect.intersect(bounds)) { + // If a clickable rect completely covers the child, done. + if (clickableRect.equals(bounds)) { + releaseOrderedChildIterator(); + return false; + } + // Keep track of the intersection rectangle. + intersections.add(clickableRect); + } } - // Keep track of the intersection rectangle. - RectF intersection = new RectF(siblingBounds); - intersections.add(intersection); } } - if (orderedList != null) orderedList.clear(); + + releaseOrderedChildIterator(); if (mParent instanceof ViewGroup) { ViewGroup parentGroup = (ViewGroup) mParent; @@ -883,6 +880,94 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return true; } + @Override + void addClickableRectsForAccessibility(List<RectF> outRects) { + int sizeBefore = outRects.size(); + + super.addClickableRectsForAccessibility(outRects); + + // If we added ourselves, then no need to visit children. + if (outRects.size() > sizeBefore) { + return; + } + + Iterator<View> iterator = obtainOrderedChildIterator(); + while (iterator.hasNext()) { + View child = iterator.next(); + + // Cannot click on an invisible view. + if (!isVisible(child)) { + continue; + } + + sizeBefore = outRects.size(); + + // Add clickable rects in the child bounds. + child.addClickableRectsForAccessibility(outRects); + + // Offset the clickable rects for out children to our coordinates. + final int sizeAfter = outRects.size(); + for (int j = sizeBefore; j < sizeAfter; j++) { + RectF rect = outRects.get(j); + + // Translate the clickable rect to our coordinates. + offsetChildRectToMyCoords(rect, child); + + // If a clickable rect fills the parent, done. + if ((int) rect.left == 0 && (int) rect.top == 0 + && (int) rect.right == mRight && (int) rect.bottom == mBottom) { + releaseOrderedChildIterator(); + return; + } + } + } + + releaseOrderedChildIterator(); + } + + private void offsetChildRectToMyCoords(RectF rect, View child) { + if (!child.hasIdentityMatrix()) { + child.getMatrix().mapRect(rect); + } + final int childDx = child.mLeft - mScrollX; + final int childDy = child.mTop - mScrollY; + rect.offset(childDx, childDy); + } + + private static boolean isVisible(View view) { + return (view.getAlpha() > 0 && view.getTransitionAlpha() > 0 && + view.getVisibility() == VISIBLE); + } + + /** + * Obtains the iterator to traverse the children in a descending Z order. + * Only one party can use the iterator at any given time and you cannot + * modify the children while using this iterator. Acquisition if already + * obtained is an error. + * + * @return The child iterator. + */ + OrderedChildIterator obtainOrderedChildIterator() { + if (mOrderedChildIterator == null) { + mOrderedChildIterator = new OrderedChildIterator(); + } else if (mOrderedChildIterator.isInitialized()) { + throw new IllegalStateException("Already obtained"); + } + mOrderedChildIterator.initialize(); + return mOrderedChildIterator; + } + + /** + * Releases the iterator to traverse the children in a descending Z order. + * Release if not obtained is an error. + */ + void releaseOrderedChildIterator() { + if (mOrderedChildIterator == null || !mOrderedChildIterator.isInitialized()) { + throw new IllegalStateException("Not obtained"); + } + mOrderedChildIterator.release(); + } + /** * Called when a child view has changed whether or not it is tracking transient state. */ @@ -7298,4 +7383,57 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager canvas.drawLines(sDebugLines, paint); } + + private final class OrderedChildIterator implements Iterator<View> { + private List<View> mOrderedChildList; + private boolean mUseCustomOrder; + private int mCurrentIndex; + private boolean mInitialized; + + public void initialize() { + mOrderedChildList = buildOrderedChildList(); + mUseCustomOrder = (mOrderedChildList == null) + && isChildrenDrawingOrderEnabled(); + mCurrentIndex = mChildrenCount - 1; + mInitialized = true; + } + + public void release() { + if (mOrderedChildList != null) { + mOrderedChildList.clear(); + } + mUseCustomOrder = false; + mCurrentIndex = 0; + mInitialized = false; + } + + public boolean isInitialized() { + return mInitialized; + } + + @Override + public boolean hasNext() { + return (mCurrentIndex >= 0); + } + + @Override + public View next() { + if (!hasNext()) { + throw new NoSuchElementException("No such element"); + } + return getChild(mCurrentIndex--); + } + + private View getChild(int index) { + final int childIndex = mUseCustomOrder + ? getChildDrawingOrder(mChildrenCount, index) : index; + return (mOrderedChildList == null) + ? mChildren[childIndex] : mOrderedChildList.get(childIndex); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } } |