Flutter Engine
The Flutter Engine
FlutterMutatorView.java
Go to the documentation of this file.
1package io.flutter.embedding.engine.mutatorsstack;
2
3import static android.view.View.OnFocusChangeListener;
4
5import android.annotation.SuppressLint;
6import android.content.Context;
7import android.graphics.Canvas;
8import android.graphics.Matrix;
9import android.graphics.Path;
10import android.view.MotionEvent;
11import android.view.View;
12import android.view.ViewTreeObserver;
13import android.view.accessibility.AccessibilityEvent;
14import android.widget.FrameLayout;
15import androidx.annotation.NonNull;
16import androidx.annotation.Nullable;
17import androidx.annotation.VisibleForTesting;
18import io.flutter.embedding.android.AndroidTouchProcessor;
19import io.flutter.util.ViewUtils;
20
21/**
22 * A view that applies the {@link io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack} to
23 * its children.
24 */
25public class FlutterMutatorView extends FrameLayout {
26 private FlutterMutatorsStack mutatorsStack;
27 private float screenDensity;
28 private int left;
29 private int top;
30 private int prevLeft;
31 private int prevTop;
32
33 private final AndroidTouchProcessor androidTouchProcessor;
34
35 /**
36 * Initialize the FlutterMutatorView. Use this to set the screenDensity, which will be used to
37 * correct the final transform matrix.
38 */
40 @NonNull Context context,
41 float screenDensity,
42 @Nullable AndroidTouchProcessor androidTouchProcessor) {
43 super(context, null);
44 this.screenDensity = screenDensity;
45 this.androidTouchProcessor = androidTouchProcessor;
46 }
47
48 /** Initialize the FlutterMutatorView. */
49 public FlutterMutatorView(@NonNull Context context) {
50 this(context, 1, /* androidTouchProcessor=*/ null);
51 }
52
53 @Nullable @VisibleForTesting ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener;
54
55 /**
56 * Sets a focus change listener that notifies when the current view or any of its descendant views
57 * have received focus.
58 *
59 * <p>If there's an active focus listener, it will first remove the current listener, and then add
60 * the new one.
61 *
62 * @param userFocusListener A user provided focus listener.
63 */
64 public void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener) {
66
67 final View mutatorView = this;
68 final ViewTreeObserver observer = getViewTreeObserver();
69 if (observer.isAlive() && activeFocusListener == null) {
71 new ViewTreeObserver.OnGlobalFocusChangeListener() {
72 @Override
73 public void onGlobalFocusChanged(View oldFocus, View newFocus) {
74 userFocusListener.onFocusChange(mutatorView, ViewUtils.childHasFocus(mutatorView));
75 }
76 };
77 observer.addOnGlobalFocusChangeListener(activeFocusListener);
78 }
79 }
80
81 /** Unsets any active focus listener. */
83 final ViewTreeObserver observer = getViewTreeObserver();
84 if (observer.isAlive() && activeFocusListener != null) {
85 final ViewTreeObserver.OnGlobalFocusChangeListener currFocusListener = activeFocusListener;
87 observer.removeOnGlobalFocusChangeListener(currFocusListener);
88 }
89 }
90
91 /**
92 * Pass the necessary parameters to the view so it can apply correct mutations to its children.
93 */
94 public void readyToDisplay(
95 @NonNull FlutterMutatorsStack mutatorsStack, int left, int top, int width, int height) {
96 this.mutatorsStack = mutatorsStack;
97 this.left = left;
98 this.top = top;
99 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
100 layoutParams.leftMargin = left;
101 layoutParams.topMargin = top;
102 setLayoutParams(layoutParams);
103 setWillNotDraw(false);
104 }
105
106 @Override
107 public void draw(Canvas canvas) {
108 // Apply all clippings on the parent canvas.
109 canvas.save();
110 for (Path path : mutatorsStack.getFinalClippingPaths()) {
111 // Reverse the current offset.
112 //
113 // The frame of this view includes the final offset of the bounding rect.
114 // We need to apply all the mutators to the view, which includes the mutation that leads to
115 // the final offset. We should reverse this final offset, both as a translate mutation and to
116 // all the clipping paths
117 Path pathCopy = new Path(path);
118 pathCopy.offset(-left, -top);
119 canvas.clipPath(pathCopy);
120 }
121 super.draw(canvas);
122 canvas.restore();
123 }
124
125 @Override
126 public void dispatchDraw(Canvas canvas) {
127 // Apply all the transforms on the child canvas.
128 canvas.save();
129
130 canvas.concat(getPlatformViewMatrix());
131 super.dispatchDraw(canvas);
132 canvas.restore();
133 }
134
135 private Matrix getPlatformViewMatrix() {
136 Matrix finalMatrix = new Matrix(mutatorsStack.getFinalMatrix());
137
138 // Reverse scale based on screen scale.
139 //
140 // The Android frame is set based on the logical resolution instead of physical.
141 // (https://developer.android.com/training/multiscreen/screendensities).
142 // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
143 // 500 points in Android. And until this point, we did all the calculation based on the flow
144 // resolution. So we need to scale down to match Android's logical resolution.
145 finalMatrix.preScale(1 / screenDensity, 1 / screenDensity);
146
147 // Reverse the current offset.
148 //
149 // The frame of this view includes the final offset of the bounding rect.
150 // We need to apply all the mutators to the view, which includes the mutation that leads to
151 // the final offset. We should reverse this final offset, both as a translate mutation and to
152 // all the clipping paths
153 finalMatrix.postTranslate(-left, -top);
154
155 return finalMatrix;
156 }
157
158 /** Intercept the events here and do not propagate them to the child platform views. */
159 @Override
160 public boolean onInterceptTouchEvent(MotionEvent event) {
161 return true;
162 }
163
164 @Override
165 public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
166 final View embeddedView = getChildAt(0);
167 if (embeddedView != null
168 && embeddedView.getImportantForAccessibility()
169 == View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {
170 return false;
171 }
172 // Forward the request only if the embedded view is in the Flutter accessibility tree.
173 // The embedded view may be ignored when the framework doesn't populate a SemanticNode
174 // for the current platform view.
175 // See AccessibilityBridge for more.
176 return super.requestSendAccessibilityEvent(child, event);
177 }
178
179 @Override
180 @SuppressLint("ClickableViewAccessibility")
181 public boolean onTouchEvent(MotionEvent event) {
182 if (androidTouchProcessor == null) {
183 return super.onTouchEvent(event);
184 }
185
186 final Matrix screenMatrix = new Matrix();
187
188 switch (event.getAction()) {
189 case MotionEvent.ACTION_DOWN:
190 prevLeft = left;
191 prevTop = top;
192 screenMatrix.postTranslate(left, top);
193 break;
194 case MotionEvent.ACTION_MOVE:
195 // While the view is dragged, use the left and top positions as
196 // they were at the moment the touch event fired.
197 screenMatrix.postTranslate(prevLeft, prevTop);
198 prevLeft = left;
199 prevTop = top;
200 break;
201 case MotionEvent.ACTION_UP:
202 default:
203 screenMatrix.postTranslate(left, top);
204 break;
205 }
206 return androidTouchProcessor.onTouchEvent(event, screenMatrix);
207 }
208}
boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event)
void readyToDisplay( @NonNull FlutterMutatorsStack mutatorsStack, int left, int top, int width, int height)
ViewTreeObserver.OnGlobalFocusChangeListener activeFocusListener
void setOnDescendantFocusChangeListener(@NonNull OnFocusChangeListener userFocusListener)
FlutterMutatorView( @NonNull Context context, float screenDensity, @Nullable AndroidTouchProcessor androidTouchProcessor)
static boolean childHasFocus(@Nullable View root)
Definition: ViewUtils.java:67
FlKeyEvent * event
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
CanvasPath Path
Definition: dart_ui.cc:58
SK_API sk_sp< PrecompileColorFilter > Matrix()
int32_t height
int32_t width