• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package io.flutter.embedding.android;
6 
7 import android.content.Context;
8 import android.graphics.PixelFormat;
9 import android.support.annotation.NonNull;
10 import android.support.annotation.Nullable;
11 import android.util.AttributeSet;
12 import android.view.SurfaceHolder;
13 import android.view.SurfaceView;
14 
15 import java.util.HashSet;
16 import java.util.Set;
17 
18 import io.flutter.Log;
19 import io.flutter.embedding.engine.renderer.FlutterRenderer;
20 import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
21 
22 /**
23  * Paints a Flutter UI on a {@link android.view.Surface}.
24  *
25  * To begin rendering a Flutter UI, the owner of this {@code FlutterSurfaceView} must invoke
26  * {@link #attachToRenderer(FlutterRenderer)} with the desired {@link FlutterRenderer}.
27  *
28  * To stop rendering a Flutter UI, the owner of this {@code FlutterSurfaceView} must invoke
29  * {@link #detachFromRenderer()}.
30  *
31  * A {@code FlutterSurfaceView} is intended for situations where a developer needs to render
32  * a Flutter UI, but does not require any keyboard input, gesture input, accessibility
33  * integrations or any other interactivity beyond rendering. If standard interactivity is
34  * desired, consider using a {@link FlutterView} which provides all of these behaviors and
35  * utilizes a {@code FlutterSurfaceView} internally.
36  */
37 public class FlutterSurfaceView extends SurfaceView implements FlutterRenderer.RenderSurface {
38   private static final String TAG = "FlutterSurfaceView";
39 
40   private final boolean renderTransparently;
41   private boolean isSurfaceAvailableForRendering = false;
42   private boolean isAttachedToFlutterRenderer = false;
43   @Nullable
44   private FlutterRenderer flutterRenderer;
45   @NonNull
46   private Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
47 
48   // Connects the {@code Surface} beneath this {@code SurfaceView} with Flutter's native code.
49   // Callbacks are received by this Object and then those messages are forwarded to our
50   // FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
51   private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
52     @Override
53     public void surfaceCreated(@NonNull SurfaceHolder holder) {
54       Log.v(TAG, "SurfaceHolder.Callback.surfaceCreated()");
55       isSurfaceAvailableForRendering = true;
56 
57       if (isAttachedToFlutterRenderer) {
58         connectSurfaceToRenderer();
59       }
60     }
61 
62     @Override
63     public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
64       Log.v(TAG, "SurfaceHolder.Callback.surfaceChanged()");
65       if (isAttachedToFlutterRenderer) {
66         changeSurfaceSize(width, height);
67       }
68     }
69 
70     @Override
71     public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
72       Log.v(TAG, "SurfaceHolder.Callback.surfaceDestroyed()");
73       isSurfaceAvailableForRendering = false;
74 
75       if (isAttachedToFlutterRenderer) {
76         disconnectSurfaceFromRenderer();
77       }
78     }
79   };
80 
81   /**
82    * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes.
83    */
FlutterSurfaceView(@onNull Context context)84   public FlutterSurfaceView(@NonNull Context context) {
85     this(context, null, false);
86   }
87 
88   /**
89    * Constructs a {@code FlutterSurfaceView} programmatically, without any XML attributes, and
90    * with control over whether or not this {@code FlutterSurfaceView} renders with transparency.
91    */
FlutterSurfaceView(@onNull Context context, boolean renderTransparently)92   public FlutterSurfaceView(@NonNull Context context, boolean renderTransparently) {
93     this(context, null, renderTransparently);
94   }
95 
96   /**
97    * Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner.
98    */
FlutterSurfaceView(@onNull Context context, @NonNull AttributeSet attrs)99   public FlutterSurfaceView(@NonNull Context context, @NonNull AttributeSet attrs) {
100     this(context, attrs, false);
101   }
102 
FlutterSurfaceView(@onNull Context context, @Nullable AttributeSet attrs, boolean renderTransparently)103   private FlutterSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, boolean renderTransparently) {
104     super(context, attrs);
105     this.renderTransparently = renderTransparently;
106     init();
107   }
108 
init()109   private void init() {
110     // If transparency is desired then we'll enable a transparent pixel format and place
111     // our Window above everything else to get transparent background rendering.
112     if (renderTransparently) {
113       getHolder().setFormat(PixelFormat.TRANSPARENT);
114       setZOrderOnTop(true);
115     }
116 
117     // Grab a reference to our underlying Surface and register callbacks with that Surface so we
118     // can monitor changes and forward those changes on to native Flutter code.
119     getHolder().addCallback(surfaceCallback);
120 
121     // Keep this SurfaceView transparent until Flutter has a frame ready to render. This avoids
122     // displaying a black rectangle in our place.
123     setAlpha(0.0f);
124   }
125 
126   /**
127    * Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering
128    * a Flutter UI to this {@code FlutterSurfaceView}.
129    *
130    * If an Android {@link android.view.Surface} is available, this method will give that
131    * {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering
132    * Flutter's UI to this {@code FlutterSurfaceView}.
133    *
134    * If no Android {@link android.view.Surface} is available yet, this {@code FlutterSurfaceView}
135    * will wait until a {@link android.view.Surface} becomes available and then give that
136    * {@link android.view.Surface} to the given {@link FlutterRenderer} to begin rendering
137    * Flutter's UI to this {@code FlutterSurfaceView}.
138    */
attachToRenderer(@onNull FlutterRenderer flutterRenderer)139   public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
140     Log.v(TAG, "Attaching to FlutterRenderer.");
141     if (this.flutterRenderer != null) {
142       Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
143       this.flutterRenderer.detachFromRenderSurface();
144     }
145 
146     this.flutterRenderer = flutterRenderer;
147     isAttachedToFlutterRenderer = true;
148 
149     // If we're already attached to an Android window then we're now attached to both a renderer
150     // and the Android window. We can begin rendering now.
151     if (isSurfaceAvailableForRendering) {
152       Log.v(TAG, "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
153       connectSurfaceToRenderer();
154     }
155   }
156 
157   /**
158    * Invoked by the owner of this {@code FlutterSurfaceView} when it no longer wants to render
159    * a Flutter UI to this {@code FlutterSurfaceView}.
160    *
161    * This method will cease any on-going rendering from Flutter to this {@code FlutterSurfaceView}.
162    */
detachFromRenderer()163   public void detachFromRenderer() {
164     if (flutterRenderer != null) {
165       // If we're attached to an Android window then we were rendering a Flutter UI. Now that
166       // this FlutterSurfaceView is detached from the FlutterRenderer, we need to stop rendering.
167       // TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() != null"
168       if (getWindowToken() != null) {
169         Log.v(TAG, "Disconnecting FlutterRenderer from Android surface.");
170         disconnectSurfaceFromRenderer();
171       }
172 
173       // Make the SurfaceView invisible to avoid showing a black rectangle.
174       setAlpha(0.0f);
175 
176       flutterRenderer = null;
177       isAttachedToFlutterRenderer = false;
178     } else {
179       Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
180     }
181   }
182 
183   // FlutterRenderer and getSurfaceTexture() must both be non-null.
connectSurfaceToRenderer()184   private void connectSurfaceToRenderer() {
185     if (flutterRenderer == null || getHolder() == null) {
186       throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getHolder() are non-null.");
187     }
188 
189     flutterRenderer.surfaceCreated(getHolder().getSurface());
190   }
191 
192   // FlutterRenderer must be non-null.
changeSurfaceSize(int width, int height)193   private void changeSurfaceSize(int width, int height) {
194     if (flutterRenderer == null) {
195       throw new IllegalStateException("changeSurfaceSize() should only be called when flutterRenderer is non-null.");
196     }
197 
198     Log.v(TAG, "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height);
199     flutterRenderer.surfaceChanged(width, height);
200   }
201 
202   // FlutterRenderer must be non-null.
disconnectSurfaceFromRenderer()203   private void disconnectSurfaceFromRenderer() {
204     if (flutterRenderer == null) {
205       throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
206     }
207 
208     flutterRenderer.surfaceDestroyed();
209   }
210 
211   /**
212    * Adds the given {@code listener} to this {@code FlutterSurfaceView}, to be notified upon Flutter's
213    * first rendered frame.
214    */
215   @Override
addOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)216   public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
217     onFirstFrameRenderedListeners.add(listener);
218   }
219 
220   /**
221    * Removes the given {@code listener}, which was previously added with
222    * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
223    */
224   @Override
removeOnFirstFrameRenderedListener(@onNull OnFirstFrameRenderedListener listener)225   public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
226     onFirstFrameRenderedListeners.remove(listener);
227   }
228 
229   @Override
onFirstFrameRendered()230   public void onFirstFrameRendered() {
231     // TODO(mattcarroll): decide where this method should live and what it needs to do.
232     Log.v(TAG, "onFirstFrameRendered()");
233     // Now that a frame is ready to display, take this SurfaceView from transparent to opaque.
234     setAlpha(1.0f);
235 
236     for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
237       listener.onFirstFrameRendered();
238     }
239   }
240 }
241