Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit fb3e35d

Browse files
Android Embedding PR15: Add Viewport Metrics to FlutterView (#8029)
1 parent 36ca574 commit fb3e35d

File tree

4 files changed

+113
-38
lines changed

4 files changed

+113
-38
lines changed

shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public void surfaceCreated(SurfaceHolder holder) {
5656

5757
@Override
5858
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
59+
Log.d(TAG, "SurfaceHolder.Callback.surfaceChanged()");
5960
if (isAttachedToFlutterRenderer) {
6061
changeSurfaceSize(width, height);
6162
}
@@ -97,13 +98,17 @@ private void init() {
9798
* Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering
9899
* a Flutter UI to this {@code FlutterSurfaceView}.
99100
*
100-
* If an Android {@link android.view.Surface} is available, this method will begin rendering
101-
* {@link FlutterRenderer}'s Flutter UI to this {@code FlutterSurfaceView}.
101+
* If an Android {@link android.view.Surface} is available, this method will give that
102+
* {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering
103+
* Flutter's UI to this {@code FlutterSurfaceView}.
102104
*
103105
* If no Android {@link android.view.Surface} is available yet, this {@code FlutterSurfaceView}
104-
* will wait until a {@link android.view.Surface} becomes available and then begin rendering.
106+
* will wait until a {@link android.view.Surface} becomes available and then give that
107+
* {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering
108+
* Flutter's UI to this {@code FlutterSurfaceView}.
105109
*/
106110
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
111+
Log.d(TAG, "attachToRenderer");
107112
if (this.flutterRenderer != null) {
108113
this.flutterRenderer.detachFromRenderSurface();
109114
}
@@ -114,6 +119,7 @@ public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
114119
// If we're already attached to an Android window then we're now attached to both a renderer
115120
// and the Android window. We can begin rendering now.
116121
if (isSurfaceAvailableForRendering) {
122+
Log.d(TAG, "Surface is available for rendering. Connecting.");
117123
connectSurfaceToRenderer();
118124
}
119125
}
@@ -179,5 +185,6 @@ public void updateSemantics(ByteBuffer buffer, String[] strings) {
179185
@Override
180186
public void onFirstFrameRendered() {
181187
// TODO(mattcarroll): decide where this method should live and what it needs to do.
188+
Log.d(TAG, "onFirstFrameRendered()");
182189
}
183190
}

shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,14 @@ private void init() {
111111
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering
112112
* a Flutter UI to this {@code FlutterTextureView}.
113113
*
114-
* If an Android {@link SurfaceTexture} is available, this method will begin rendering
115-
* {@link FlutterRenderer}'s Flutter UI to this {@code FlutterTextureView}.
114+
* If an Android {@link SurfaceTexture} is available, this method will give that
115+
* {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
116+
* Flutter's UI to this {@code FlutterTextureView}.
116117
*
117-
* If no Android {@link SurfaceTexture} is available yet, this {@code FlutterSurfaceView}
118-
* will wait until a {@link SurfaceTexture} becomes available and then begin rendering.
118+
* If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView}
119+
* will wait until a {@link SurfaceTexture} becomes available and then give that
120+
* {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
121+
* Flutter's UI to this {@code FlutterTextureView}.
119122
*/
120123
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
121124
if (this.flutterRenderer != null) {
@@ -193,5 +196,6 @@ public void updateSemantics(ByteBuffer buffer, String[] strings) {
193196
@Override
194197
public void onFirstFrameRendered() {
195198
// TODO(mattcarroll): decide where this method should live and what it needs to do.
199+
Log.d(TAG, "onFirstFrameRendered()");
196200
}
197201
}

shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@
1717
import android.view.KeyEvent;
1818
import android.view.MotionEvent;
1919
import android.view.WindowInsets;
20+
import android.view.WindowManager;
2021
import android.view.inputmethod.EditorInfo;
2122
import android.view.inputmethod.InputConnection;
22-
import android.view.inputmethod.InputMethod;
23-
import android.view.inputmethod.InputMethodManager;
2423
import android.widget.FrameLayout;
2524

2625
import java.util.ArrayList;
@@ -30,6 +29,7 @@
3029
import io.flutter.embedding.engine.FlutterEngine;
3130
import io.flutter.embedding.engine.renderer.FlutterRenderer;
3231
import io.flutter.plugin.editing.TextInputPlugin;
32+
import io.flutter.view.VsyncWaiter;
3333

3434
/**
3535
* Displays a Flutter UI on an Android device.
@@ -77,6 +77,9 @@ public class FlutterView extends FrameLayout {
7777
@Nullable
7878
private AndroidKeyProcessor androidKeyProcessor;
7979

80+
// Directly implemented View behavior that communicates with Flutter.
81+
private final FlutterRenderer.ViewportMetrics viewportMetrics = new FlutterRenderer.ViewportMetrics();
82+
8083
/**
8184
* Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes.
8285
*
@@ -158,8 +161,10 @@ protected void onConfigurationChanged(Configuration newConfig) {
158161
*/
159162
@Override
160163
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
161-
// TODO(mattcarroll): hookup to viewport metrics.
162164
super.onSizeChanged(width, height, oldWidth, oldHeight);
165+
viewportMetrics.width = width;
166+
viewportMetrics.height = height;
167+
sendViewportMetricsToFlutter();
163168
}
164169

165170
/**
@@ -174,8 +179,22 @@ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight)
174179
*/
175180
@Override
176181
public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
177-
// TODO(mattcarroll): hookup to Flutter metrics.
178-
return insets;
182+
WindowInsets newInsets = super.onApplyWindowInsets(insets);
183+
184+
// Status bar (top) and left/right system insets should partially obscure the content (padding).
185+
viewportMetrics.paddingTop = insets.getSystemWindowInsetTop();
186+
viewportMetrics.paddingRight = insets.getSystemWindowInsetRight();
187+
viewportMetrics.paddingBottom = 0;
188+
viewportMetrics.paddingLeft = insets.getSystemWindowInsetLeft();
189+
190+
// Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
191+
viewportMetrics.viewInsetTop = 0;
192+
viewportMetrics.viewInsetRight = 0;
193+
viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom();
194+
viewportMetrics.viewInsetLeft = 0;
195+
sendViewportMetricsToFlutter();
196+
197+
return newInsets;
179198
}
180199

181200
/**
@@ -188,8 +207,23 @@ public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
188207
@Override
189208
@SuppressWarnings("deprecation")
190209
protected boolean fitSystemWindows(Rect insets) {
191-
// TODO(mattcarroll): hookup to Flutter metrics.
192-
return super.fitSystemWindows(insets);
210+
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
211+
// Status bar, left/right system insets partially obscure content (padding).
212+
viewportMetrics.paddingTop = insets.top;
213+
viewportMetrics.paddingRight = insets.right;
214+
viewportMetrics.paddingBottom = 0;
215+
viewportMetrics.paddingLeft = insets.left;
216+
217+
// Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
218+
viewportMetrics.viewInsetTop = 0;
219+
viewportMetrics.viewInsetRight = 0;
220+
viewportMetrics.viewInsetBottom = insets.bottom;
221+
viewportMetrics.viewInsetLeft = 0;
222+
sendViewportMetricsToFlutter();
223+
return true;
224+
} else {
225+
return super.fitSystemWindows(insets);
226+
}
193227
}
194228
//------- End: Process View configuration that Flutter cares about. --------
195229

@@ -312,13 +346,16 @@ public boolean onHoverEvent(MotionEvent event) {
312346
* {@link FlutterEngine}.
313347
*/
314348
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
349+
Log.d(TAG, "attachToFlutterEngine()");
315350
if (isAttachedToFlutterEngine()) {
316351
if (flutterEngine == this.flutterEngine) {
317352
// We are already attached to this FlutterEngine
353+
Log.d(TAG, "Already attached to this engine. Doing nothing.");
318354
return;
319355
}
320356

321357
// Detach from a previous FlutterEngine so we can attach to this new one.
358+
Log.d(TAG, "Currently attached to a different engine. Detaching.");
322359
detachFromFlutterEngine();
323360
}
324361

@@ -346,6 +383,7 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
346383
// Push View and Context related information from Android to Flutter.
347384
sendUserSettingsToFlutter();
348385
sendLocalesToFlutter(getResources().getConfiguration());
386+
sendViewportMetricsToFlutter();
349387
}
350388

351389
/**
@@ -359,7 +397,9 @@ public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
359397
* {@link FlutterEngine}.
360398
*/
361399
public void detachFromFlutterEngine() {
400+
Log.d(TAG, "detachFromFlutterEngine()");
362401
if (!isAttachedToFlutterEngine()) {
402+
Log.d(TAG, "Not attached to an engine. Doing nothing.");
363403
return;
364404
}
365405
Log.d(TAG, "Detaching from Flutter Engine");
@@ -422,6 +462,18 @@ private void sendUserSettingsToFlutter() {
422462
.send();
423463
}
424464

465+
// TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI
466+
private void sendViewportMetricsToFlutter() {
467+
Log.d(TAG, "sendViewportMetricsToFlutter()");
468+
if (!isAttachedToFlutterEngine()) {
469+
Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this FlutterView was not attached to a FlutterEngine.");
470+
return;
471+
}
472+
473+
viewportMetrics.devicePixelRatio = getResources().getDisplayMetrics().density;
474+
flutterEngine.getRenderer().setViewportMetrics(viewportMetrics);
475+
}
476+
425477
/**
426478
* Render modes for a {@link FlutterView}.
427479
*/

shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* code via JNI. The corresponding {@link RenderSurface} is used as a delegate to carry out
3030
* certain actions on behalf of this {@code FlutterRenderer} within an Android view hierarchy.
3131
*
32-
* {@link FlutterView} is an implementation of a {@link RenderSurface}.
32+
* {@link io.flutter.embedding.engine.android.FlutterView} is an implementation of a {@link RenderSurface}.
3333
*/
3434
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
3535
public class FlutterRenderer implements TextureRegistry {
@@ -49,14 +49,16 @@ public void attachToRenderSurface(@NonNull RenderSurface renderSurface) {
4949
}
5050

5151
this.renderSurface = renderSurface;
52+
this.renderSurface.attachToRenderer(this);
5253
this.flutterJNI.setRenderSurface(renderSurface);
5354
}
5455

5556
public void detachFromRenderSurface() {
5657
// TODO(mattcarroll): determine desired behavior if we're asked to detach without first being attached
5758
if (this.renderSurface != null) {
58-
surfaceDestroyed();
59+
this.renderSurface.detachFromRenderer();
5960
this.renderSurface = null;
61+
surfaceDestroyed();
6062
this.flutterJNI.setRenderSurface(null);
6163
}
6264
}
@@ -157,29 +159,19 @@ public void surfaceDestroyed() {
157159
}
158160

159161
// TODO(mattcarroll): describe the native behavior that this invokes
160-
public void setViewportMetrics(float devicePixelRatio,
161-
int physicalWidth,
162-
int physicalHeight,
163-
int physicalPaddingTop,
164-
int physicalPaddingRight,
165-
int physicalPaddingBottom,
166-
int physicalPaddingLeft,
167-
int physicalViewInsetTop,
168-
int physicalViewInsetRight,
169-
int physicalViewInsetBottom,
170-
int physicalViewInsetLeft) {
162+
public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) {
171163
flutterJNI.setViewportMetrics(
172-
devicePixelRatio,
173-
physicalWidth,
174-
physicalHeight,
175-
physicalPaddingTop,
176-
physicalPaddingRight,
177-
physicalPaddingBottom,
178-
physicalPaddingLeft,
179-
physicalViewInsetTop,
180-
physicalViewInsetRight,
181-
physicalViewInsetBottom,
182-
physicalViewInsetLeft
164+
viewportMetrics.devicePixelRatio,
165+
viewportMetrics.width,
166+
viewportMetrics.height,
167+
viewportMetrics.paddingTop,
168+
viewportMetrics.paddingRight,
169+
viewportMetrics.paddingBottom,
170+
viewportMetrics.paddingLeft,
171+
viewportMetrics.viewInsetTop,
172+
viewportMetrics.viewInsetRight,
173+
viewportMetrics.viewInsetBottom,
174+
viewportMetrics.viewInsetLeft
183175
);
184176
}
185177

@@ -281,4 +273,24 @@ public interface RenderSurface {
281273
*/
282274
void onFirstFrameRendered();
283275
}
276+
277+
/**
278+
* Mutable data structure that holds all viewport metrics properties that Flutter cares about.
279+
*
280+
* All distance measurements, e.g., width, height, padding, viewInsets, are measured in device
281+
* pixels, not logical pixels.
282+
*/
283+
public static final class ViewportMetrics {
284+
public float devicePixelRatio = 1.0f;
285+
public int width = 0;
286+
public int height = 0;
287+
public int paddingTop = 0;
288+
public int paddingRight = 0;
289+
public int paddingBottom = 0;
290+
public int paddingLeft = 0;
291+
public int viewInsetTop = 0;
292+
public int viewInsetRight = 0;
293+
public int viewInsetBottom = 0;
294+
public int viewInsetLeft = 0;
295+
}
284296
}

0 commit comments

Comments
 (0)