1+ package com .idlefish .flutterboost ;
2+ import android .content .Context ;
3+ import android .graphics .SurfaceTexture ;
4+ import android .util .AttributeSet ;
5+ import android .view .Surface ;
6+ import android .view .TextureView ;
7+
8+ import androidx .annotation .NonNull ;
9+ import androidx .annotation .Nullable ;
10+ import io .flutter .Log ;
11+ import io .flutter .embedding .engine .renderer .FlutterRenderer ;
12+ import io .flutter .embedding .engine .renderer .RenderSurface ;
13+
14+ public class XFlutterTextureView extends TextureView implements RenderSurface {
15+ private static final String TAG = "FlutterTextureView" ;
16+
17+ private boolean isSurfaceAvailableForRendering = false ;
18+ private boolean isAttachedToFlutterRenderer = false ;
19+ @ Nullable
20+ private FlutterRenderer flutterRenderer ;
21+
22+ private Surface renderSurface ;
23+
24+ // Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native code.
25+ // Callbacks are received by this Object and then those messages are forwarded to our
26+ // FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
27+ private final SurfaceTextureListener surfaceTextureListener = new SurfaceTextureListener () {
28+ @ Override
29+ public void onSurfaceTextureAvailable (SurfaceTexture surfaceTexture , int width , int height ) {
30+ Log .v (TAG , "SurfaceTextureListener.onSurfaceTextureAvailable()" );
31+ isSurfaceAvailableForRendering = true ;
32+
33+ // If we're already attached to a FlutterRenderer then we're now attached to both a renderer
34+ // and the Android window, so we can begin rendering now.
35+ if (isAttachedToFlutterRenderer ) {
36+ connectSurfaceToRenderer ();
37+ }
38+ }
39+
40+ @ Override
41+ public void onSurfaceTextureSizeChanged (@ NonNull SurfaceTexture surface , int width , int height ) {
42+ Log .v (TAG , "SurfaceTextureListener.onSurfaceTextureSizeChanged()" );
43+ if (isAttachedToFlutterRenderer ) {
44+ changeSurfaceSize (width , height );
45+ }
46+ }
47+
48+ @ Override
49+ public void onSurfaceTextureUpdated (@ NonNull SurfaceTexture surface ) {
50+ // Invoked every time a new frame is available. We don't care.
51+ }
52+
53+ @ Override
54+ public boolean onSurfaceTextureDestroyed (@ NonNull SurfaceTexture surface ) {
55+ Log .v (TAG , "SurfaceTextureListener.onSurfaceTextureDestroyed()" );
56+ isSurfaceAvailableForRendering = false ;
57+
58+ // If we're attached to a FlutterRenderer then we need to notify it that our SurfaceTexture
59+ // has been destroyed.
60+ if (isAttachedToFlutterRenderer ) {
61+ disconnectSurfaceFromRenderer ();
62+ }
63+
64+ // Return true to indicate that no further painting will take place
65+ // within this SurfaceTexture.
66+ return true ;
67+ }
68+ };
69+
70+ /**
71+ * Constructs a {@code FlutterTextureView} programmatically, without any XML attributes.
72+ */
73+ public XFlutterTextureView (@ NonNull Context context ) {
74+ this (context , null );
75+ }
76+
77+ /**
78+ * Constructs a {@code FlutterTextureView} in an XML-inflation-compliant manner.
79+ */
80+ public XFlutterTextureView (@ NonNull Context context , @ Nullable AttributeSet attrs ) {
81+ super (context , attrs );
82+ init ();
83+ }
84+
85+ private void init () {
86+ // Listen for when our underlying SurfaceTexture becomes available, changes size, or
87+ // gets destroyed, and take the appropriate actions.
88+ setSurfaceTextureListener (surfaceTextureListener );
89+ }
90+
91+ @ Nullable
92+ @ Override
93+ public FlutterRenderer getAttachedRenderer () {
94+ return flutterRenderer ;
95+ }
96+
97+ /**
98+ * Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering
99+ * a Flutter UI to this {@code FlutterTextureView}.
100+ *
101+ * If an Android {@link SurfaceTexture} is available, this method will give that
102+ * {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
103+ * Flutter's UI to this {@code FlutterTextureView}.
104+ *
105+ * If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView}
106+ * will wait until a {@link SurfaceTexture} becomes available and then give that
107+ * {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
108+ * Flutter's UI to this {@code FlutterTextureView}.
109+ */
110+ public void attachToRenderer (@ NonNull FlutterRenderer flutterRenderer ) {
111+ Log .v (TAG , "Attaching to FlutterRenderer." );
112+ if (this .flutterRenderer != null ) {
113+ Log .v (TAG , "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one." );
114+ this .flutterRenderer .stopRenderingToSurface ();
115+ }
116+
117+ this .flutterRenderer = flutterRenderer ;
118+ isAttachedToFlutterRenderer = true ;
119+
120+ // If we're already attached to an Android window then we're now attached to both a renderer
121+ // and the Android window. We can begin rendering now.
122+ if (isSurfaceAvailableForRendering ) {
123+ Log .v (TAG , "Surface is available for rendering. Connecting FlutterRenderer to Android surface." );
124+ connectSurfaceToRenderer ();
125+ }
126+ }
127+
128+ /**
129+ * Invoked by the owner of this {@code FlutterTextureView} when it no longer wants to render
130+ * a Flutter UI to this {@code FlutterTextureView}.
131+ *
132+ * This method will cease any on-going rendering from Flutter to this {@code FlutterTextureView}.
133+ */
134+ public void detachFromRenderer () {
135+ if (flutterRenderer != null ) {
136+ // If we're attached to an Android window then we were rendering a Flutter UI. Now that
137+ // this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering.
138+ // TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() != null"
139+ if (getWindowToken () != null ) {
140+ Log .v (TAG , "Disconnecting FlutterRenderer from Android surface." );
141+ disconnectSurfaceFromRenderer ();
142+ }
143+
144+ flutterRenderer = null ;
145+ isAttachedToFlutterRenderer = false ;
146+ } else {
147+ Log .w (TAG , "detachFromRenderer() invoked when no FlutterRenderer was attached." );
148+ }
149+ }
150+
151+ // FlutterRenderer and getSurfaceTexture() must both be non-null.
152+ private void connectSurfaceToRenderer () {
153+ if (flutterRenderer == null || getSurfaceTexture () == null ) {
154+ throw new IllegalStateException ("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null." );
155+ }
156+
157+ // flutterRenderer.startRenderingToSurface(new Surface(getSurfaceTexture()));
158+
159+ renderSurface = new Surface (getSurfaceTexture ());
160+ flutterRenderer .startRenderingToSurface (renderSurface );
161+ }
162+
163+ // FlutterRenderer must be non-null.
164+ private void changeSurfaceSize (int width , int height ) {
165+ if (flutterRenderer == null ) {
166+ throw new IllegalStateException ("changeSurfaceSize() should only be called when flutterRenderer is non-null." );
167+ }
168+
169+ Log .v (TAG , "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height );
170+ flutterRenderer .surfaceChanged (width , height );
171+ }
172+
173+ // FlutterRenderer must be non-null.
174+ private void disconnectSurfaceFromRenderer () {
175+ if (flutterRenderer == null ) {
176+ throw new IllegalStateException ("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null." );
177+ }
178+
179+ flutterRenderer .stopRenderingToSurface ();
180+ renderSurface .release ();
181+ renderSurface = null ;
182+ }
183+ }
0 commit comments