未验证 提交 8ac6f6ef 编写于 作者: M Michael Goderbauer 提交者: GitHub

Encode scrolling status into tree (#4647)

上级 a031239a
...@@ -337,6 +337,13 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { ...@@ -337,6 +337,13 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
/// The fields 'textSelectionBase' and 'textSelectionExtent' describe the /// The fields 'textSelectionBase' and 'textSelectionExtent' describe the
/// currently selected text within `value`. /// currently selected text within `value`.
/// ///
/// For scrollable nodes `scrollPosition` describes the current scroll
/// position in logical pixel. `scrollExtentMax` and `scrollExtentMin`
/// describe the maximum and minimum in-rage values that `scrollPosition` can
/// be. Both or either may be infinity to indicate unbound scrolling. The
/// value for `scrollPosition` can (temporarily) be outside this range, for
/// example during an overscroll.
///
/// The `rect` is the region occupied by this node in its own coordinate /// The `rect` is the region occupied by this node in its own coordinate
/// system. /// system.
/// ///
...@@ -348,6 +355,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { ...@@ -348,6 +355,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
int actions, int actions,
int textSelectionBase, int textSelectionBase,
int textSelectionExtent, int textSelectionExtent,
double scrollPosition,
double scrollExtentMax,
double scrollExtentMin,
Rect rect, Rect rect,
String label, String label,
String hint, String hint,
...@@ -366,6 +376,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { ...@@ -366,6 +376,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
actions, actions,
textSelectionBase, textSelectionBase,
textSelectionExtent, textSelectionExtent,
scrollPosition,
scrollExtentMax,
scrollExtentMin,
rect.left, rect.left,
rect.top, rect.top,
rect.right, rect.right,
...@@ -386,6 +399,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 { ...@@ -386,6 +399,9 @@ class SemanticsUpdateBuilder extends NativeFieldWrapperClass2 {
int actions, int actions,
int textSelectionBase, int textSelectionBase,
int textSelectionExtent, int textSelectionExtent,
double scrollPosition,
double scrollExtentMax,
double scrollExtentMin,
double left, double left,
double top, double top,
double right, double right,
......
...@@ -58,6 +58,9 @@ struct SemanticsNode { ...@@ -58,6 +58,9 @@ struct SemanticsNode {
int32_t actions = 0; int32_t actions = 0;
int32_t textSelectionBase = -1; int32_t textSelectionBase = -1;
int32_t textSelectionExtent = -1; int32_t textSelectionExtent = -1;
double scrollPosition = std::nan("");
double scrollExtentMax = std::nan("");
double scrollExtentMin = std::nan("");
std::string label; std::string label;
std::string hint; std::string hint;
std::string value; std::string value;
......
...@@ -39,6 +39,9 @@ void SemanticsUpdateBuilder::updateNode(int id, ...@@ -39,6 +39,9 @@ void SemanticsUpdateBuilder::updateNode(int id,
int actions, int actions,
int textSelectionBase, int textSelectionBase,
int textSelectionExtent, int textSelectionExtent,
double scrollPosition,
double scrollExtentMax,
double scrollExtentMin,
double left, double left,
double top, double top,
double right, double right,
...@@ -58,6 +61,9 @@ void SemanticsUpdateBuilder::updateNode(int id, ...@@ -58,6 +61,9 @@ void SemanticsUpdateBuilder::updateNode(int id,
node.actions = actions; node.actions = actions;
node.textSelectionBase = textSelectionBase; node.textSelectionBase = textSelectionBase;
node.textSelectionExtent = textSelectionExtent; node.textSelectionExtent = textSelectionExtent;
node.scrollPosition = scrollPosition;
node.scrollExtentMax = scrollExtentMax;
node.scrollExtentMin = scrollExtentMin;
node.rect = SkRect::MakeLTRB(left, top, right, bottom); node.rect = SkRect::MakeLTRB(left, top, right, bottom);
node.label = label; node.label = label;
node.hint = hint; node.hint = hint;
......
...@@ -30,6 +30,9 @@ class SemanticsUpdateBuilder ...@@ -30,6 +30,9 @@ class SemanticsUpdateBuilder
int actions, int actions,
int textSelectionBase, int textSelectionBase,
int textSelectionExtent, int textSelectionExtent,
double scrollPosition,
double scrollExtentMax,
double scrollExtentMin,
double left, double left,
double top, double top,
double right, double right,
......
...@@ -34,7 +34,10 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -34,7 +34,10 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
// Constants from higher API levels. // Constants from higher API levels.
// TODO(goderbauer): Get these from Android Support Library when // TODO(goderbauer): Get these from Android Support Library when
// https://github.com/flutter/flutter/issues/11099 is resolved. // https://github.com/flutter/flutter/issues/11099 is resolved.
public static final int ACTION_SHOW_ON_SCREEN = 16908342; // API level 23 private static final int ACTION_SHOW_ON_SCREEN = 16908342; // API level 23
private static final float SCROLL_EXTENT_FOR_INFINITY = 100000.0f;
private static final float SCROLL_POSITION_CAP_FOR_INFINITY = 70000.0f;
private Map<Integer, SemanticsObject> mObjects; private Map<Integer, SemanticsObject> mObjects;
private final FlutterView mOwner; private final FlutterView mOwner;
...@@ -460,8 +463,10 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -460,8 +463,10 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
if (object.hasFlag(Flag.IS_FOCUSED)) { if (object.hasFlag(Flag.IS_FOCUSED)) {
mInputFocusedObject = object; mInputFocusedObject = object;
} }
if (object.hadPreviousConfig) {
updated.add(object); updated.add(object);
} }
}
Set<SemanticsObject> visitedObjects = new HashSet<SemanticsObject>(); Set<SemanticsObject> visitedObjects = new HashSet<SemanticsObject>();
SemanticsObject rootObject = getRootObject(); SemanticsObject rootObject = getRootObject();
...@@ -481,13 +486,46 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -481,13 +486,46 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
} }
} }
// Send accessibility events for updated nodes // TODO(goderbauer): Send this event only once (!) for changed subtrees,
// see https://github.com/flutter/flutter/issues/14534
sendAccessibilityEvent(0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
for (SemanticsObject object : updated) { for (SemanticsObject object : updated) {
sendAccessibilityEvent(object.id, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); if (object.didScroll()) {
if (!object.hadPreviousConfig) { AccessibilityEvent event =
continue; obtainAccessibilityEvent(object.id, AccessibilityEvent.TYPE_VIEW_SCROLLED);
// Android doesn't support unbound scrolling. So we pretend there is a large
// bound (SCROLL_EXTENT_FOR_INFINITY), which you can never reach.
float position = object.scrollPosition;
float max = object.scrollExtentMax;
if (Float.isInfinite(object.scrollExtentMax)) {
max = SCROLL_EXTENT_FOR_INFINITY;
if (position > SCROLL_POSITION_CAP_FOR_INFINITY) {
position = SCROLL_POSITION_CAP_FOR_INFINITY;
}
}
if (Float.isInfinite(object.scrollExtentMin)) {
max += SCROLL_EXTENT_FOR_INFINITY;
if (position < -SCROLL_POSITION_CAP_FOR_INFINITY) {
position = -SCROLL_POSITION_CAP_FOR_INFINITY;
}
position += SCROLL_EXTENT_FOR_INFINITY;
} else {
max -= object.scrollExtentMin;
position -= object.scrollExtentMin;
} }
if (object.hadAction(Action.SCROLL_UP) || object.hadAction(Action.SCROLL_DOWN)) {
event.setScrollY((int) position);
event.setMaxScrollY((int) max);
} else if (object.hadAction(Action.SCROLL_LEFT)
|| object.hadAction(Action.SCROLL_RIGHT)) {
event.setScrollX((int) position);
event.setMaxScrollX((int) max);
}
sendAccessibilityEvent(event);
}
if (mA11yFocusedObject != null && mA11yFocusedObject.id == object.id if (mA11yFocusedObject != null && mA11yFocusedObject.id == object.id
&& object.hadFlag(Flag.HAS_CHECKED_STATE) && object.hadFlag(Flag.HAS_CHECKED_STATE)
&& object.hasFlag(Flag.HAS_CHECKED_STATE) && object.hasFlag(Flag.HAS_CHECKED_STATE)
...@@ -586,24 +624,6 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -586,24 +624,6 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
final HashMap<String, Object> data = (HashMap<String, Object>)annotatedEvent.get("data"); final HashMap<String, Object> data = (HashMap<String, Object>)annotatedEvent.get("data");
switch (type) { switch (type) {
case "scroll":
final int nodeId = (int)annotatedEvent.get("nodeId");
AccessibilityEvent event =
obtainAccessibilityEvent(nodeId, AccessibilityEvent.TYPE_VIEW_SCROLLED);
char axis = ((String)data.get("axis")).charAt(0);
double minPosition = (double)data.get("minScrollExtent");
double maxPosition = (double)data.get("maxScrollExtent") - minPosition;
double position = (double)data.get("pixels") - minPosition;
if (axis == 'v') {
event.setScrollY((int)position);
event.setMaxScrollY((int)maxPosition);
} else {
assert axis == 'h';
event.setScrollX((int)position);
event.setMaxScrollX((int)maxPosition);
}
sendAccessibilityEvent(event);
break;
case "announce": case "announce":
mOwner.announceForAccessibility((String) data.get("message")); mOwner.announceForAccessibility((String) data.get("message"));
break; break;
...@@ -660,6 +680,9 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -660,6 +680,9 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
int actions; int actions;
int textSelectionBase; int textSelectionBase;
int textSelectionExtent; int textSelectionExtent;
float scrollPosition;
float scrollExtentMax;
float scrollExtentMin;
String label; String label;
String value; String value;
String increasedValue; String increasedValue;
...@@ -670,8 +693,12 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -670,8 +693,12 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
boolean hadPreviousConfig = false; boolean hadPreviousConfig = false;
int previousFlags; int previousFlags;
int previousActions;
int previousTextSelectionBase; int previousTextSelectionBase;
int previousTextSelectionExtent; int previousTextSelectionExtent;
float previousScrollPosition;
float previousScrollExtentMax;
float previousScrollExtentMin;
String previousValue; String previousValue;
private float left; private float left;
...@@ -694,6 +721,10 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -694,6 +721,10 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
return (actions & action.value) != 0; return (actions & action.value) != 0;
} }
boolean hadAction(Action action) {
return (previousActions & action.value) != 0;
}
boolean hasFlag(Flag flag) { boolean hasFlag(Flag flag) {
return (flags & flag.value) != 0; return (flags & flag.value) != 0;
} }
...@@ -703,6 +734,11 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -703,6 +734,11 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
return (previousFlags & flag.value) != 0; return (previousFlags & flag.value) != 0;
} }
boolean didScroll() {
return !Float.isNaN(scrollPosition) && !Float.isNaN(previousScrollPosition)
&& previousScrollPosition != scrollPosition;
}
void log(String indent, boolean recursive) { void log(String indent, boolean recursive) {
Log.i(TAG, indent + "SemanticsObject id=" + id + " label=" + label + " actions=" + actions + " flags=" + flags + "\n" + Log.i(TAG, indent + "SemanticsObject id=" + id + " label=" + label + " actions=" + actions + " flags=" + flags + "\n" +
indent + " +-- textDirection=" + textDirection + "\n"+ indent + " +-- textDirection=" + textDirection + "\n"+
...@@ -721,13 +757,20 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess ...@@ -721,13 +757,20 @@ class AccessibilityBridge extends AccessibilityNodeProvider implements BasicMess
hadPreviousConfig = true; hadPreviousConfig = true;
previousValue = value; previousValue = value;
previousFlags = flags; previousFlags = flags;
previousActions = actions;
previousTextSelectionBase = textSelectionBase; previousTextSelectionBase = textSelectionBase;
previousTextSelectionExtent = textSelectionExtent; previousTextSelectionExtent = textSelectionExtent;
previousScrollPosition = scrollPosition;
previousScrollExtentMax = scrollExtentMax;
previousScrollExtentMin = scrollExtentMin;
flags = buffer.getInt(); flags = buffer.getInt();
actions = buffer.getInt(); actions = buffer.getInt();
textSelectionBase = buffer.getInt(); textSelectionBase = buffer.getInt();
textSelectionExtent = buffer.getInt(); textSelectionExtent = buffer.getInt();
scrollPosition = buffer.getFloat();
scrollExtentMax = buffer.getFloat();
scrollExtentMin = buffer.getFloat();
int stringIndex = buffer.getInt(); int stringIndex = buffer.getInt();
label = stringIndex == -1 ? null : strings[stringIndex]; label = stringIndex == -1 ? null : strings[stringIndex];
......
...@@ -461,7 +461,7 @@ bool PlatformViewAndroid::ResourceContextMakeCurrent() { ...@@ -461,7 +461,7 @@ bool PlatformViewAndroid::ResourceContextMakeCurrent() {
void PlatformViewAndroid::UpdateSemantics( void PlatformViewAndroid::UpdateSemantics(
blink::SemanticsNodeUpdates update) { blink::SemanticsNodeUpdates update) {
constexpr size_t kBytesPerNode = 33 * sizeof(int32_t); constexpr size_t kBytesPerNode = 36 * sizeof(int32_t);
constexpr size_t kBytesPerChild = sizeof(int32_t); constexpr size_t kBytesPerChild = sizeof(int32_t);
JNIEnv* env = fml::jni::AttachCurrentThread(); JNIEnv* env = fml::jni::AttachCurrentThread();
...@@ -492,6 +492,9 @@ void PlatformViewAndroid::UpdateSemantics( ...@@ -492,6 +492,9 @@ void PlatformViewAndroid::UpdateSemantics(
buffer_int32[position++] = node.actions; buffer_int32[position++] = node.actions;
buffer_int32[position++] = node.textSelectionBase; buffer_int32[position++] = node.textSelectionBase;
buffer_int32[position++] = node.textSelectionExtent; buffer_int32[position++] = node.textSelectionExtent;
buffer_float32[position++] = (float)node.scrollPosition;
buffer_float32[position++] = (float)node.scrollExtentMax;
buffer_float32[position++] = (float)node.scrollExtentMin;
if (node.label.empty()) { if (node.label.empty()) {
buffer_int32[position++] = -1; buffer_int32[position++] = -1;
} else { } else {
......
...@@ -143,6 +143,14 @@ bool GeometryComparator(SemanticsObject* a, SemanticsObject* b) { ...@@ -143,6 +143,14 @@ bool GeometryComparator(SemanticsObject* a, SemanticsObject* b) {
return [self node].rect != node->rect || [self node].transform != node->transform; return [self node].rect != node->rect || [self node].transform != node->transform;
} }
/**
* Whether calling `setSemanticsNode:` with `node` would cause a scroll event.
*/
- (BOOL)nodeWillCauseScroll:(const blink::SemanticsNode*)node {
return !isnan([self node].scrollPosition) && !isnan(node->scrollPosition) &&
[self node].scrollPosition != node->scrollPosition;
}
- (std::vector<SemanticsObject*>*)children { - (std::vector<SemanticsObject*>*)children {
return &_children; return &_children;
} }
...@@ -404,11 +412,13 @@ void AccessibilityBridge::UpdateSemantics(blink::SemanticsNodeUpdates nodes) { ...@@ -404,11 +412,13 @@ void AccessibilityBridge::UpdateSemantics(blink::SemanticsNodeUpdates nodes) {
// traversal order (top left to bottom right, with hit testing order as tie breaker). // traversal order (top left to bottom right, with hit testing order as tie breaker).
NSMutableSet<SemanticsObject*>* childOrdersToUpdate = [[[NSMutableSet alloc] init] autorelease]; NSMutableSet<SemanticsObject*>* childOrdersToUpdate = [[[NSMutableSet alloc] init] autorelease];
BOOL layoutChanged = NO; BOOL layoutChanged = NO;
BOOL scrollOccured = NO;
for (const auto& entry : nodes) { for (const auto& entry : nodes) {
const blink::SemanticsNode& node = entry.second; const blink::SemanticsNode& node = entry.second;
SemanticsObject* object = GetOrCreateObject(node.id, nodes); SemanticsObject* object = GetOrCreateObject(node.id, nodes);
layoutChanged = layoutChanged || [object nodeWillCauseLayoutChange:&node]; layoutChanged = layoutChanged || [object nodeWillCauseLayoutChange:&node];
scrollOccured = scrollOccured || [object nodeWillCauseScroll:&node];
[object setSemanticsNode:&node]; [object setSemanticsNode:&node];
const size_t childrenCount = node.children.size(); const size_t childrenCount = node.children.size();
auto& children = *[object children]; auto& children = *[object children];
...@@ -452,6 +462,10 @@ void AccessibilityBridge::UpdateSemantics(blink::SemanticsNodeUpdates nodes) { ...@@ -452,6 +462,10 @@ void AccessibilityBridge::UpdateSemantics(blink::SemanticsNodeUpdates nodes) {
// TODO(goderbauer): figure out which node to focus next. // TODO(goderbauer): figure out which node to focus next.
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
} }
if (scrollOccured) {
// TODO(tvolkert): provide meaningful string (e.g. "page 2 of 5")
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, @"");
}
} }
void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, blink::SemanticsAction action) { void AccessibilityBridge::DispatchSemanticsAction(int32_t uid, blink::SemanticsAction action) {
...@@ -511,10 +525,7 @@ void AccessibilityBridge::VisitObjectsRecursivelyAndRemove(SemanticsObject* obje ...@@ -511,10 +525,7 @@ void AccessibilityBridge::VisitObjectsRecursivelyAndRemove(SemanticsObject* obje
void AccessibilityBridge::HandleEvent(NSDictionary<NSString*, id>* annotatedEvent) { void AccessibilityBridge::HandleEvent(NSDictionary<NSString*, id>* annotatedEvent) {
NSString* type = annotatedEvent[@"type"]; NSString* type = annotatedEvent[@"type"];
if ([type isEqualToString:@"scroll"]) { if ([type isEqualToString:@"announce"]) {
// TODO(tvolkert): provide meaningful string (e.g. "page 2 of 5")
UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, @"");
} else if ([type isEqualToString:@"announce"]) {
NSString* message = annotatedEvent[@"data"][@"message"]; NSString* message = annotatedEvent[@"data"][@"message"];
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, message); UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, message);
} else { } else {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册