Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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)
FlKeyEvent * event
int32_t height
int32_t width