Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
weixin_43355755
engine
提交
3aaeca3a
E
engine
项目概览
weixin_43355755
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
3aaeca3a
编写于
7月 22, 2019
作者:
M
Matt Carroll
提交者:
GitHub
7月 22, 2019
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Android Embedding Refactor PR36: Add splash screen support. (#9525)
上级
268533e8
变更
9
隐藏空白更改
内联
并排
Showing
9 changed file
with
713 addition
and
84 deletion
+713
-84
ci/licenses_golden/licenses_flutter
ci/licenses_golden/licenses_flutter
+3
-0
shell/platform/android/BUILD.gn
shell/platform/android/BUILD.gn
+3
-0
shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java
...id/io/flutter/embedding/android/DrawableSplashScreen.java
+111
-0
shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
...android/io/flutter/embedding/android/FlutterActivity.java
+152
-68
shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
...android/io/flutter/embedding/android/FlutterFragment.java
+40
-1
shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java
...droid/io/flutter/embedding/android/FlutterSplashView.java
+276
-0
shell/platform/android/io/flutter/embedding/android/FlutterView.java
...orm/android/io/flutter/embedding/android/FlutterView.java
+21
-15
shell/platform/android/io/flutter/embedding/android/SplashScreen.java
...rm/android/io/flutter/embedding/android/SplashScreen.java
+87
-0
shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
...io/flutter/embedding/engine/renderer/FlutterRenderer.java
+20
-0
未找到文件。
ci/licenses_golden/licenses_flutter
浏览文件 @
3aaeca3a
...
...
@@ -549,11 +549,14 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActi
FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidKeyProcessor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/AndroidTouchProcessor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/SplashScreen.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineAndroidLifecycle.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java
...
...
shell/platform/android/BUILD.gn
浏览文件 @
3aaeca3a
...
...
@@ -123,11 +123,14 @@ action("flutter_shell_java") {
"io/flutter/app/FlutterPluginRegistry.java",
"io/flutter/embedding/android/AndroidKeyProcessor.java",
"io/flutter/embedding/android/AndroidTouchProcessor.java",
"io/flutter/embedding/android/DrawableSplashScreen.java",
"io/flutter/embedding/android/FlutterActivity.java",
"io/flutter/embedding/android/FlutterFragment.java",
"io/flutter/embedding/android/FlutterSplashView.java",
"io/flutter/embedding/android/FlutterSurfaceView.java",
"io/flutter/embedding/android/FlutterTextureView.java",
"io/flutter/embedding/android/FlutterView.java",
"io/flutter/embedding/android/SplashScreen.java",
"io/flutter/embedding/engine/FlutterEngine.java",
"io/flutter/embedding/engine/FlutterEngineAndroidLifecycle.java",
"io/flutter/embedding/engine/FlutterEnginePluginRegistry.java",
...
...
shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java
0 → 100644
浏览文件 @
3aaeca3a
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package
io.flutter.embedding.android
;
import
android.animation.Animator
;
import
android.content.Context
;
import
android.graphics.drawable.Drawable
;
import
android.os.Bundle
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.util.AttributeSet
;
import
android.view.View
;
import
android.widget.FrameLayout
;
import
android.widget.ImageView
;
/**
* {@link SplashScreen} that displays a given {@link Drawable}, which then fades its alpha
* to zero when instructed to {@link #transitionToFlutter(Runnable)}.
*/
public
final
class
DrawableSplashScreen
implements
SplashScreen
{
private
final
Drawable
drawable
;
private
final
ImageView
.
ScaleType
scaleType
;
private
final
long
crossfadeDurationInMillis
;
private
DrawableSplashScreenView
splashView
;
/**
* Constructs a {@code DrawableSplashScreen} that displays the given {@code drawable} and
* crossfades to Flutter content in 500 milliseconds.
*/
public
DrawableSplashScreen
(
@NonNull
Drawable
drawable
)
{
this
(
drawable
,
ImageView
.
ScaleType
.
FIT_XY
,
500
);
}
/**
* Constructs a {@code DrawableSplashScreen} that displays the given {@code drawable} and
* crossfades to Flutter content in the given {@code crossfadeDurationInMillis}.
* <p>
* @param drawable The {@code Drawable} to be displayed as a splash screen.
* @param scaleType The {@link ImageView.ScaleType} to be applied to the {@code Drawable} when the
* {@code Drawable} is displayed full-screen.
*/
public
DrawableSplashScreen
(
@NonNull
Drawable
drawable
,
@NonNull
ImageView
.
ScaleType
scaleType
,
long
crossfadeDurationInMillis
)
{
this
.
drawable
=
drawable
;
this
.
scaleType
=
scaleType
;
this
.
crossfadeDurationInMillis
=
crossfadeDurationInMillis
;
}
@Nullable
@Override
public
View
createSplashView
(
@NonNull
Context
context
,
@Nullable
Bundle
savedInstanceState
)
{
splashView
=
new
DrawableSplashScreenView
(
context
);
splashView
.
setSplashDrawable
(
drawable
,
scaleType
);
return
splashView
;
}
@Override
public
void
transitionToFlutter
(
@NonNull
Runnable
onTransitionComplete
)
{
if
(
splashView
==
null
)
{
onTransitionComplete
.
run
();
return
;
}
splashView
.
animate
()
.
alpha
(
0.0f
)
.
setDuration
(
crossfadeDurationInMillis
)
.
setListener
(
new
Animator
.
AnimatorListener
()
{
@Override
public
void
onAnimationStart
(
Animator
animation
)
{}
@Override
public
void
onAnimationEnd
(
Animator
animation
)
{
onTransitionComplete
.
run
();
}
@Override
public
void
onAnimationCancel
(
Animator
animation
)
{
onTransitionComplete
.
run
();
}
@Override
public
void
onAnimationRepeat
(
Animator
animation
)
{}
}
);
}
// Public for Android OS requirements. This View should not be used by external developers.
public
static
class
DrawableSplashScreenView
extends
ImageView
{
public
DrawableSplashScreenView
(
@NonNull
Context
context
)
{
this
(
context
,
null
,
0
);
}
public
DrawableSplashScreenView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
)
{
this
(
context
,
attrs
,
0
);
}
public
DrawableSplashScreenView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
,
int
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
);
}
public
void
setSplashDrawable
(
@Nullable
Drawable
drawable
)
{
setSplashDrawable
(
drawable
,
ImageView
.
ScaleType
.
FIT_XY
);
}
public
void
setSplashDrawable
(
@Nullable
Drawable
drawable
,
@NonNull
ImageView
.
ScaleType
scaleType
)
{
setScaleType
(
scaleType
);
setImageDrawable
(
drawable
);
}
}
}
shell/platform/android/io/flutter/embedding/android/FlutterActivity.java
浏览文件 @
3aaeca3a
...
...
@@ -9,6 +9,7 @@ import android.content.Intent;
import
android.content.pm.ActivityInfo
;
import
android.content.pm.ApplicationInfo
;
import
android.content.pm.PackageManager
;
import
android.content.res.Resources
;
import
android.graphics.Color
;
import
android.graphics.drawable.ColorDrawable
;
import
android.graphics.drawable.Drawable
;
...
...
@@ -16,7 +17,6 @@ import android.os.Build;
import
android.os.Bundle
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.support.v4.app.Fragment
;
import
android.support.v4.app.FragmentActivity
;
import
android.support.v4.app.FragmentManager
;
import
android.util.TypedValue
;
...
...
@@ -29,7 +29,6 @@ import android.widget.FrameLayout;
import
io.flutter.Log
;
import
io.flutter.embedding.engine.FlutterEngine
;
import
io.flutter.embedding.engine.FlutterShellArgs
;
import
io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener
;
import
io.flutter.plugin.platform.PlatformPlugin
;
import
io.flutter.view.FlutterMain
;
...
...
@@ -65,17 +64,70 @@ import io.flutter.view.FlutterMain;
* {@link FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an
* {@code Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a
* {@code Fragment}.
* <p>
* <strong>Launch Screen and Splash Screen</strong>
* <p>
* {@code FlutterActivity} supports the display of an Android "launch screen" as well as a
* Flutter-specific "splash screen". The launch screen is displayed while the Android application
* loads. It is only applicable if {@code FlutterActivity} is the first {@code Activity} displayed
* upon loading the app. After the launch screen passes, a splash screen is optionally displayed.
* The splash screen is displayed for as long as it takes Flutter to initialize and render its
* first frame.
* <p>
* Use Android themes to display a launch screen. Create two themes: a launch theme and a normal
* theme. In the launch theme, set {@code windowBackground} to the desired {@code Drawable} for
* the launch screen. In the normal theme, set {@code windowBackground} to any desired background
* color that should normally appear behind your Flutter content. In most cases this background
* color will never be seen, but for possible transition edge cases it is a good idea to explicitly
* replace the launch screen window background with a neutral color.
* <p>
* Do not change aspects of system chrome between a launch theme and normal theme. Either define
* both themes to be fullscreen or not, and define both themes to display the same status bar and
* navigation bar settings. To adjust system chrome once the Flutter app renders, use platform
* channels to instruct Android to do so at the appropriate time. This will avoid any jarring visual
* changes during app startup.
* <p>
* In the AndroidManifest.xml, set the theme of {@code FlutterActivity} to the defined launch theme.
* In the metadata section for {@code FlutterActivity}, defined the following reference to your
* normal theme:
*
* {@code
* <meta-data
* android:name="io.flutter.embedding.android.NormalTheme"
* android:resource="@style/YourNormalTheme"
* />
* }
*
* With themes defined, and AndroidManifest.xml updated, Flutter displays the specified launch
* screen until the Android application is initialized.
* <p>
* Flutter also requires initialization time. To specify a splash screen for Flutter initialization,
* subclass {@code FlutterActivity} and override {@link #provideSplashScreen()}. See
* {@link SplashScreen} for details on implementing a splash screen.
* <p>
* Flutter ships with a splash screen that automatically displays the exact same
* {@code windowBackground} as the launch theme discussed previously. To use that splash screen,
* include the following metadata in AndroidManifest.xml for this {@code FlutterActivity}:
*
* {@code
* <meta-data
* android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
* android:value="true"
* />
* }
*/
// TODO(mattcarroll): explain each call forwarded to Fragment (first requires resolution of PluginRegistry API).
public
class
FlutterActivity
extends
FragmentActivity
implements
FlutterFragment
.
FlutterEngineProvider
,
FlutterFragment
.
FlutterEngineConfigurator
,
OnFirstFrameRenderedListen
er
{
FlutterFragment
.
SplashScreenProvid
er
{
private
static
final
String
TAG
=
"FlutterActivity"
;
// Meta-data arguments, processed from manifest XML.
protected
static
final
String
DART_ENTRYPOINT_META_DATA_KEY
=
"io.flutter.Entrypoint"
;
protected
static
final
String
INITIAL_ROUTE_META_DATA_KEY
=
"io.flutter.InitialRoute"
;
protected
static
final
String
SPLASH_SCREEN_META_DATA_KEY
=
"io.flutter.embedding.android.SplashScreenDrawable"
;
protected
static
final
String
NORMAL_THEME_META_DATA_KEY
=
"io.flutter.embedding.android.NormalTheme"
;
// Intent extra arguments.
protected
static
final
String
EXTRA_DART_ENTRYPOINT
=
"dart_entrypoint"
;
...
...
@@ -94,11 +146,6 @@ public class FlutterActivity extends FragmentActivity
@Nullable
private
FlutterFragment
flutterFragment
;
// Used to cover the Activity until the 1st frame is rendered so as to
// avoid a brief black flicker from a SurfaceView version of FlutterView.
@Nullable
private
View
coverView
;
/**
* Creates an {@link Intent} that launches a {@code FlutterActivity}, which executes
* a {@code main()} Dart entrypoint, and displays the "/" route as Flutter's initial route.
...
...
@@ -187,91 +234,134 @@ public class FlutterActivity extends FragmentActivity
@Override
protected
void
onCreate
(
@Nullable
Bundle
savedInstanceState
)
{
switchLaunchThemeForNormalTheme
();
super
.
onCreate
(
savedInstanceState
);
configureWindowForTransparency
();
setContentView
(
createFragmentContainer
());
showCoverView
();
configureStatusBarForFullscreenFlutterExperience
();
ensureFlutterFragmentCreated
();
}
/**
* Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
* bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}.
* Switches themes for this {@code Activity} from the theme used to launch this
* {@code Activity} to a "normal theme" that is intended for regular {@code Activity}
* operation.
* <p>
* For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity}
* must include {@code <item name="android:windowIsTranslucent">true</item>}.
* This behavior is offered so that a "launch screen" can be displayed while the
* application initially loads. To utilize this behavior in an app, do the following:
* <ol>
* <li>Create 2 different themes in style.xml: one theme for the launch screen and
* one theme for normal display.
* <li>In the launch screen theme, set the "windowBackground" property to a {@code Drawable}
* of your choice.
* <li>In the normal theme, customize however you'd like.
* <li>In the AndroidManifest.xml, set the theme of your {@code FlutterActivity} to
* your launch theme.
* <li>Add a {@code <meta-data>} property to your {@code FlutterActivity} with a name
* of "io.flutter.embedding.android.NormalTheme" and set the resource to your normal
* theme, e.g., {@code android:resource="@style/MyNormalTheme}.
* </ol>
* With the above settings, your launch theme will be used when loading the app, and
* then the theme will be switched to your normal theme once the app has initialized.
* <p>
* Do not change aspects of system chrome between a launch theme and normal theme. Either define
* both themes to be fullscreen or not, and define both themes to display the same status bar and
* navigation bar settings. If you wish to adjust system chrome once your Flutter app renders, use
* platform channels to instruct Android to do so at the appropriate time. This will avoid any
* jarring visual changes during app startup.
*/
private
void
configureWindowForTransparency
()
{
BackgroundMode
backgroundMode
=
getBackgroundMode
();
if
(
backgroundMode
==
BackgroundMode
.
transparent
)
{
getWindow
().
setBackgroundDrawable
(
new
ColorDrawable
(
Color
.
TRANSPARENT
));
getWindow
().
setFlags
(
WindowManager
.
LayoutParams
.
FLAG_LAYOUT_NO_LIMITS
,
WindowManager
.
LayoutParams
.
FLAG_LAYOUT_NO_LIMITS
);
private
void
switchLaunchThemeForNormalTheme
()
{
try
{
ActivityInfo
activityInfo
=
getPackageManager
().
getActivityInfo
(
getComponentName
(),
PackageManager
.
GET_META_DATA
);
if
(
activityInfo
.
metaData
!=
null
)
{
int
normalThemeRID
=
activityInfo
.
metaData
.
getInt
(
NORMAL_THEME_META_DATA_KEY
,
-
1
);
if
(
normalThemeRID
!=
-
1
)
{
setTheme
(
normalThemeRID
);
}
}
else
{
Log
.
d
(
TAG
,
"Using the launch theme as normal theme."
);
}
}
catch
(
PackageManager
.
NameNotFoundException
exception
)
{
Log
.
e
(
TAG
,
"Could not read meta-data for FlutterActivity. Using the launch theme as normal theme."
);
}
}
/**
* Cover all visible {@code Activity} area with a {@code View} that paints everything the same
* color as the {@code Window}.
* Extracts a {@link Drawable} from the {@code Activity}'s {@code windowBackground}.
* <p>
* This cover {@code View} should be displayed at the very beginning of the {@code Activity}'s
* lifespan and then hidden once Flutter renders its first frame. The purpose of this cover is to
* cover {@link FlutterSurfaceView}, which briefly displays a black rectangle before it can make
* itself transparent.
* Returns null if no {@code windowBackground} is set for the activity.
*/
private
void
showCoverView
()
{
if
(
getBackgroundMode
()
==
BackgroundMode
.
transparent
)
{
// Don't display an opaque cover view if the Activity is intended to be transparent.
return
;
private
Drawable
getLaunchScreenDrawableFromActivityTheme
()
{
TypedValue
typedValue
=
new
TypedValue
();
if
(!
getTheme
().
resolveAttribute
(
android
.
R
.
attr
.
windowBackground
,
typedValue
,
true
))
{
return
null
;
}
Log
.
v
(
TAG
,
"Showing cover view until first frame is rendered."
);
// Create the coverView.
if
(
coverView
==
null
)
{
coverView
=
new
View
(
this
);
addContentView
(
coverView
,
new
ViewGroup
.
LayoutParams
(
ViewGroup
.
LayoutParams
.
MATCH_PARENT
,
ViewGroup
.
LayoutParams
.
MATCH_PARENT
));
if
(
typedValue
.
resourceId
==
0
)
{
return
null
;
}
try
{
return
getResources
().
getDrawable
(
typedValue
.
resourceId
,
getTheme
());
}
catch
(
Resources
.
NotFoundException
e
)
{
Log
.
e
(
TAG
,
"Splash screen requested in AndroidManifest.xml, but no windowBackground"
+
" is available in the theme."
);
return
null
;
}
}
// Pain the coverView with the Window's background.
Drawable
background
=
createCoverViewBackground
();
if
(
background
!=
null
)
{
coverView
.
setBackground
(
background
);
@Nullable
@Override
public
SplashScreen
provideSplashScreen
()
{
Drawable
manifestSplashDrawable
=
getSplashScreenFromManifest
();
if
(
manifestSplashDrawable
!=
null
)
{
return
new
DrawableSplashScreen
(
manifestSplashDrawable
);
}
else
{
// If we can't obtain a window background to replicate then we'd be guessing as to the least
// intrusive color. But there is no way to make an accurate guess. In this case we don't
// give the coverView any color, which means a brief black rectangle will be visible upon
// Activity launch.
return
null
;
}
}
/**
* Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the
* {@code AndroidManifest.xml} file, or null if no such splash screen is requested.
* <p>
* See {@link #SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to be used in a
* manifest file.
*/
@Nullable
private
Drawable
createCoverViewBackground
()
{
TypedValue
typedValue
=
new
TypedValue
();
boolean
hasBackgroundColor
=
getTheme
().
resolveAttribute
(
android
.
R
.
attr
.
windowBackground
,
typedValue
,
true
);
if
(
hasBackgroundColor
&&
typedValue
.
resourceId
!=
0
)
{
return
getResources
().
getDrawable
(
typedValue
.
resourceId
,
getTheme
());
}
else
{
private
Drawable
getSplashScreenFromManifest
()
{
try
{
ActivityInfo
activityInfo
=
getPackageManager
().
getActivityInfo
(
getComponentName
(),
PackageManager
.
GET_META_DATA
|
PackageManager
.
GET_ACTIVITIES
);
Bundle
metadata
=
activityInfo
.
metaData
;
Integer
splashScreenId
=
metadata
!=
null
?
metadata
.
getInt
(
SPLASH_SCREEN_META_DATA_KEY
)
:
null
;
return
splashScreenId
!=
null
?
getResources
().
getDrawable
(
splashScreenId
,
getTheme
())
:
null
;
}
catch
(
PackageManager
.
NameNotFoundException
e
)
{
// This is never expected to happen.
return
null
;
}
}
/**
* Hides the cover {@code View}.
* Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status
* bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}.
* <p>
*
This method should be called when Flutter renders its first frame. See {@link #showCoverView()
}
*
for details
.
*
For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity
}
*
must include {@code <item name="android:windowIsTranslucent">true</item>}
.
*/
private
void
hideCoverView
()
{
if
(
coverView
!=
null
)
{
coverView
.
setVisibility
(
View
.
GONE
);
private
void
configureWindowForTransparency
()
{
BackgroundMode
backgroundMode
=
getBackgroundMode
();
if
(
backgroundMode
==
BackgroundMode
.
transparent
)
{
getWindow
().
setBackgroundDrawable
(
new
ColorDrawable
(
Color
.
TRANSPARENT
));
getWindow
().
setFlags
(
WindowManager
.
LayoutParams
.
FLAG_LAYOUT_NO_LIMITS
,
WindowManager
.
LayoutParams
.
FLAG_LAYOUT_NO_LIMITS
);
}
}
...
...
@@ -550,12 +640,6 @@ public class FlutterActivity extends FragmentActivity
return
(
getApplicationInfo
().
flags
&
ApplicationInfo
.
FLAG_DEBUGGABLE
)
!=
0
;
}
@Override
public
void
onFirstFrameRendered
()
{
Log
.
v
(
TAG
,
"First frame has been rendered. Hiding cover view."
);
hideCoverView
();
}
/**
* The mode of the background of a {@code FlutterActivity}, either opaque or transparent.
*/
...
...
shell/platform/android/io/flutter/embedding/android/FlutterFragment.java
浏览文件 @
3aaeca3a
...
...
@@ -10,6 +10,7 @@ import android.app.Activity;
import
android.arch.lifecycle.Lifecycle
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.graphics.Color
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android.os.Handler
;
...
...
@@ -295,6 +296,8 @@ public class FlutterFragment extends Fragment {
private
FlutterEngine
flutterEngine
;
private
boolean
isFlutterEngineFromActivity
;
@Nullable
private
FlutterSplashView
flutterSplashView
;
@Nullable
private
FlutterView
flutterView
;
@Nullable
private
PlatformPlugin
platformPlugin
;
...
...
@@ -470,7 +473,30 @@ public class FlutterFragment extends Fragment {
Log
.
v
(
TAG
,
"Creating FlutterView."
);
flutterView
=
new
FlutterView
(
getActivity
(),
getRenderMode
(),
getTransparencyMode
());
flutterView
.
addOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
return
flutterView
;
flutterSplashView
=
new
FlutterSplashView
(
getContext
());
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
JELLY_BEAN_MR1
)
{
flutterSplashView
.
setId
(
View
.
generateViewId
());
}
else
{
// TODO(mattcarroll): Find a better solution to this ID. This is a random, static ID.
// It might conflict with other Views, and it means that only a single FlutterSplashView
// can exist in a View hierarchy at one time.
flutterSplashView
.
setId
(
486947586
);
}
flutterSplashView
.
displayFlutterViewWithSplash
(
flutterView
,
provideSplashScreen
());
return
flutterSplashView
;
}
@Nullable
protected
SplashScreen
provideSplashScreen
()
{
FragmentActivity
parentActivity
=
getActivity
();
if
(
parentActivity
instanceof
SplashScreenProvider
)
{
SplashScreenProvider
splashScreenProvider
=
(
SplashScreenProvider
)
parentActivity
;
return
splashScreenProvider
.
provideSplashScreen
();
}
return
null
;
}
/**
...
...
@@ -853,4 +879,17 @@ public class FlutterFragment extends Fragment {
*/
void
configureFlutterEngine
(
@NonNull
FlutterEngine
flutterEngine
);
}
/**
* Provides a {@link SplashScreen} to display while Flutter initializes and renders its first
* frame.
*/
public
interface
SplashScreenProvider
{
/**
* Provides a {@link SplashScreen} to display while Flutter initializes and renders its first
* frame.
*/
@Nullable
SplashScreen
provideSplashScreen
();
}
}
shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java
0 → 100644
浏览文件 @
3aaeca3a
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package
io.flutter.embedding.android
;
import
android.content.Context
;
import
android.os.Bundle
;
import
android.os.Parcel
;
import
android.os.Parcelable
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.util.AttributeSet
;
import
android.view.View
;
import
android.widget.FrameLayout
;
import
io.flutter.Log
;
import
io.flutter.embedding.engine.FlutterEngine
;
import
io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener
;
/**
* {@code View} that displays a {@link SplashScreen} until a given {@link FlutterView}
* renders its first frame.
*/
/* package */
final
class
FlutterSplashView
extends
FrameLayout
{
private
static
String
TAG
=
"FlutterSplashView"
;
@Nullable
private
SplashScreen
splashScreen
;
@Nullable
private
FlutterView
flutterView
;
@Nullable
private
View
splashScreenView
;
@Nullable
private
Bundle
splashScreenState
;
@Nullable
private
String
transitioningIsolateId
;
@Nullable
private
String
previousCompletedSplashIsolate
;
@NonNull
private
final
FlutterView
.
FlutterEngineAttachmentListener
flutterEngineAttachmentListener
=
new
FlutterView
.
FlutterEngineAttachmentListener
()
{
@Override
public
void
onFlutterEngineAttachedToFlutterView
(
@NonNull
FlutterEngine
engine
)
{
flutterView
.
removeFlutterEngineAttachmentListener
(
this
);
displayFlutterViewWithSplash
(
flutterView
,
splashScreen
);
}
@Override
public
void
onFlutterEngineDetachedFromFlutterView
()
{}
};
@NonNull
private
final
OnFirstFrameRenderedListener
onFirstFrameRenderedListener
=
new
OnFirstFrameRenderedListener
()
{
@Override
public
void
onFirstFrameRendered
()
{
if
(
splashScreen
!=
null
)
{
transitionToFlutter
();
}
}
};
@NonNull
private
final
Runnable
onTransitionComplete
=
new
Runnable
()
{
@Override
public
void
run
()
{
removeView
(
splashScreenView
);
previousCompletedSplashIsolate
=
transitioningIsolateId
;
}
};
public
FlutterSplashView
(
@NonNull
Context
context
)
{
this
(
context
,
null
,
0
);
}
public
FlutterSplashView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
)
{
this
(
context
,
attrs
,
0
);
}
public
FlutterSplashView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
,
int
defStyleAttr
)
{
super
(
context
,
attrs
,
defStyleAttr
);
setSaveEnabled
(
true
);
}
@Nullable
@Override
protected
Parcelable
onSaveInstanceState
()
{
Parcelable
superState
=
super
.
onSaveInstanceState
();
SavedState
savedState
=
new
SavedState
(
superState
);
savedState
.
previousCompletedSplashIsolate
=
previousCompletedSplashIsolate
;
savedState
.
splashScreenState
=
splashScreen
!=
null
?
splashScreen
.
saveSplashScreenState
()
:
null
;
return
savedState
;
}
@Override
protected
void
onRestoreInstanceState
(
Parcelable
state
)
{
SavedState
savedState
=
(
SavedState
)
state
;
super
.
onRestoreInstanceState
(
savedState
.
getSuperState
());
previousCompletedSplashIsolate
=
savedState
.
previousCompletedSplashIsolate
;
splashScreenState
=
savedState
.
splashScreenState
;
}
/**
* Displays the given {@code splashScreen} on top of the given {@code flutterView} until
* Flutter has rendered its first frame, then the {@code splashScreen} is transitioned away.
* <p>
* If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the
* given {@code flutterView} on its own.
*/
public
void
displayFlutterViewWithSplash
(
@NonNull
FlutterView
flutterView
,
@Nullable
SplashScreen
splashScreen
)
{
// If we were displaying a previous FlutterView, remove it.
if
(
this
.
flutterView
!=
null
)
{
this
.
flutterView
.
removeOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
removeView
(
this
.
flutterView
);
}
// If we were displaying a previous splash screen View, remove it.
if
(
splashScreenView
!=
null
)
{
removeView
(
splashScreenView
);
}
// Display the new FlutterView.
this
.
flutterView
=
flutterView
;
addView
(
flutterView
);
this
.
splashScreen
=
splashScreen
;
// Display the new splash screen, if needed.
if
(
splashScreen
!=
null
)
{
if
(
isSplashScreenNeededNow
())
{
Log
.
v
(
TAG
,
"Showing splash screen UI."
);
// This is the typical case. A FlutterEngine is attached to the FlutterView and we're
// waiting for the first frame to render. Show a splash UI until that happens.
splashScreenView
=
splashScreen
.
createSplashView
(
getContext
(),
splashScreenState
);
addView
(
this
.
splashScreenView
);
flutterView
.
addOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
}
else
if
(
isSplashScreenTransitionNeededNow
())
{
Log
.
v
(
TAG
,
"Showing an immediate splash transition to Flutter due to previously interrupted transition."
);
splashScreenView
=
splashScreen
.
createSplashView
(
getContext
(),
splashScreenState
);
addView
(
splashScreenView
);
transitionToFlutter
();
}
else
if
(!
flutterView
.
isAttachedToFlutterEngine
())
{
Log
.
v
(
TAG
,
"FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached."
);
flutterView
.
addFlutterEngineAttachmentListener
(
flutterEngineAttachmentListener
);
}
}
}
/**
* Returns true if current conditions require a splash UI to be displayed.
* <p>
* This method does not evaluate whether a previously interrupted splash transition needs
* to resume. See {@link #isSplashScreenTransitionNeededNow()} to answer that question.
*/
private
boolean
isSplashScreenNeededNow
()
{
return
flutterView
!=
null
&&
flutterView
.
isAttachedToFlutterEngine
()
&&
!
flutterView
.
hasRenderedFirstFrame
()
&&
!
hasSplashCompleted
();
}
/**
* Returns true if a previous splash transition was interrupted by recreation, e.g., an
* orientation change, and that previous transition should be resumed.
* <p>
* Not all splash screens are capable of remembering their transition progress. In those
* cases, this method will return false even if a previous visual transition was
* interrupted.
*/
private
boolean
isSplashScreenTransitionNeededNow
()
{
return
flutterView
!=
null
&&
flutterView
.
isAttachedToFlutterEngine
()
&&
splashScreen
!=
null
&&
splashScreen
.
doesSplashViewRememberItsTransition
()
&&
wasPreviousSplashTransitionInterrupted
();
}
/**
* Returns true if a splash screen was transitioning to a Flutter experience and was then
* interrupted, e.g., by an Android configuration change. Returns false otherwise.
* <p>
* Invoking this method expects that a {@code flutterView} exists and it is attached to a
* {@code FlutterEngine}.
*/
private
boolean
wasPreviousSplashTransitionInterrupted
()
{
if
(
flutterView
==
null
)
{
throw
new
IllegalStateException
(
"Cannot determine if previous splash transition was "
+
"interrupted when no FlutterView is set."
);
}
if
(!
flutterView
.
isAttachedToFlutterEngine
())
{
throw
new
IllegalStateException
(
"Cannot determine if previous splash transition was "
+
"interrupted when no FlutterEngine is attached to our FlutterView. This question "
+
"depends on an isolate ID to differentiate Flutter experiences."
);
}
return
flutterView
.
hasRenderedFirstFrame
()
&&
!
hasSplashCompleted
();
}
/**
* Returns true if a splash UI for a specific Flutter experience has already completed.
* <p>
* A "specific Flutter experience" is defined as any experience with the same Dart isolate
* ID. The purpose of this distinction is to prevent a situation where a user gets past a
* splash UI, rotates the device (or otherwise triggers a recreation) and the splash screen
* reappears.
* <p>
* An isolate ID is deemed reasonable as a key for a completion event because a Dart isolate
* cannot be entered twice. Therefore, a single Dart isolate cannot return to an "un-rendered"
* state after having previously rendered content.
*/
private
boolean
hasSplashCompleted
()
{
if
(
flutterView
==
null
)
{
throw
new
IllegalStateException
(
"Cannot determine if splash has completed when no FlutterView "
+
"is set."
);
}
if
(!
flutterView
.
isAttachedToFlutterEngine
())
{
throw
new
IllegalStateException
(
"Cannot determine if splash has completed when no "
+
"FlutterEngine is attached to our FlutterView. This question depends on an isolate ID "
+
"to differentiate Flutter experiences."
);
}
// A null isolate ID on a non-null FlutterEngine indicates that the Dart isolate has not
// been initialized. Therefore, no frame has been rendered for this engine, which means
// no splash screen could have completed yet.
return
flutterView
.
getAttachedFlutterEngine
().
getDartExecutor
().
getIsolateServiceId
()
!=
null
&&
flutterView
.
getAttachedFlutterEngine
().
getDartExecutor
().
getIsolateServiceId
().
equals
(
previousCompletedSplashIsolate
);
}
/**
* Transitions a splash screen to the Flutter UI.
* <p>
* This method requires that our FlutterView be attached to an engine, and that engine have
* a Dart isolate ID. It also requires that a {@code splashScreen} exist.
*/
private
void
transitionToFlutter
()
{
transitioningIsolateId
=
flutterView
.
getAttachedFlutterEngine
().
getDartExecutor
().
getIsolateServiceId
();
Log
.
v
(
TAG
,
"Transitioning splash screen to a Flutter UI. Isolate: "
+
transitioningIsolateId
);
splashScreen
.
transitionToFlutter
(
onTransitionComplete
);
}
public
static
class
SavedState
extends
BaseSavedState
{
public
static
Creator
CREATOR
=
new
Creator
()
{
@Override
public
SavedState
createFromParcel
(
Parcel
source
)
{
return
new
SavedState
(
source
);
}
@Override
public
SavedState
[]
newArray
(
int
size
)
{
return
new
SavedState
[
size
];
}
};
private
String
previousCompletedSplashIsolate
;
private
Bundle
splashScreenState
;
SavedState
(
Parcelable
superState
)
{
super
(
superState
);
}
SavedState
(
Parcel
source
)
{
super
(
source
);
previousCompletedSplashIsolate
=
source
.
readString
();
splashScreenState
=
source
.
readBundle
(
getClass
().
getClassLoader
());
}
@Override
public
void
writeToParcel
(
Parcel
out
,
int
flags
)
{
super
.
writeToParcel
(
out
,
flags
);
out
.
writeString
(
previousCompletedSplashIsolate
);
out
.
writeBundle
(
splashScreenState
);
}
}
}
shell/platform/android/io/flutter/embedding/android/FlutterView.java
浏览文件 @
3aaeca3a
...
...
@@ -73,6 +73,7 @@ public class FlutterView extends FrameLayout {
// Internal view hierarchy references.
@Nullable
private
FlutterRenderer
.
RenderSurface
renderSurface
;
private
final
Set
<
OnFirstFrameRenderedListener
>
onFirstFrameRenderedListeners
=
new
HashSet
<>();
private
boolean
didRenderFirstFrame
;
// Connections to a Flutter execution context.
...
...
@@ -109,6 +110,10 @@ public class FlutterView extends FrameLayout {
@Override
public
void
onFirstFrameRendered
()
{
didRenderFirstFrame
=
true
;
for
(
OnFirstFrameRenderedListener
listener
:
onFirstFrameRenderedListeners
)
{
listener
.
onFirstFrameRendered
();
}
}
};
...
...
@@ -199,9 +204,6 @@ public class FlutterView extends FrameLayout {
break
;
}
// Register a listener for the first frame render event to set didRenderFirstFrame.
renderSurface
.
addOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
// FlutterView needs to be focusable so that the InputMethodManager can interact with it.
setFocusable
(
true
);
setFocusableInTouchMode
(
true
);
...
...
@@ -232,7 +234,7 @@ public class FlutterView extends FrameLayout {
* first rendered frame.
*/
public
void
addOnFirstFrameRenderedListener
(
@NonNull
OnFirstFrameRenderedListener
listener
)
{
renderSurface
.
addOnFirstFrameRenderedListener
(
listener
);
onFirstFrameRenderedListeners
.
add
(
listener
);
}
/**
...
...
@@ -240,7 +242,7 @@ public class FlutterView extends FrameLayout {
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/
public
void
removeOnFirstFrameRenderedListener
(
@NonNull
OnFirstFrameRenderedListener
listener
)
{
renderSurface
.
removeOnFirstFrameRenderedListener
(
listener
);
onFirstFrameRenderedListeners
.
remove
(
listener
);
}
//------- Start: Process View configuration that Flutter cares about. ------
...
...
@@ -561,8 +563,10 @@ public class FlutterView extends FrameLayout {
this
.
flutterEngine
=
flutterEngine
;
// Instruct our FlutterRenderer that we are now its designated RenderSurface.
didRenderFirstFrame
=
false
;
this
.
flutterEngine
.
getRenderer
().
attachToRenderSurface
(
renderSurface
);
FlutterRenderer
flutterRenderer
=
this
.
flutterEngine
.
getRenderer
();
didRenderFirstFrame
=
flutterRenderer
.
hasRenderedFirstFrame
();
flutterRenderer
.
attachToRenderSurface
(
renderSurface
);
flutterRenderer
.
addOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
// Initialize various components that know how to process Android View I/O
// in a way that Flutter understands.
...
...
@@ -607,6 +611,13 @@ public class FlutterView extends FrameLayout {
for
(
FlutterEngineAttachmentListener
listener
:
flutterEngineAttachmentListeners
)
{
listener
.
onFlutterEngineAttachedToFlutterView
(
flutterEngine
);
}
// If the first frame has already been rendered, notify all first frame listeners.
// Do this after all other initialization so that listeners don't inadvertently interact
// with a FlutterView that is only partially attached to a FlutterEngine.
if
(
didRenderFirstFrame
)
{
onFirstFrameRenderedListener
.
onFirstFrameRendered
();
}
}
/**
...
...
@@ -646,16 +657,11 @@ public class FlutterView extends FrameLayout {
textInputPlugin
.
destroy
();
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
FlutterRenderer
flutterRenderer
=
flutterEngine
.
getRenderer
();
didRenderFirstFrame
=
false
;
flutterEngine
.
getRenderer
().
detachFromRenderSurface
();
flutterRenderer
.
removeOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
flutterRenderer
.
detachFromRenderSurface
();
flutterEngine
=
null
;
// TODO(mattcarroll): clear the surface when JNI doesn't blow up
// if (isSurfaceAvailableForRendering) {
// Canvas canvas = surfaceHolder.lockCanvas();
// canvas.drawColor(Color.RED);
// surfaceHolder.unlockCanvasAndPost(canvas);
// }
}
/**
...
...
shell/platform/android/io/flutter/embedding/android/SplashScreen.java
0 → 100644
浏览文件 @
3aaeca3a
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package
io.flutter.embedding.android
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.os.Bundle
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.view.View
;
/**
* Splash screen configuration for a given Flutter experience.
* <p>
* Implementations provide a visual representation of a splash screen in
* {@link #createSplashView(Context, Bundle)}, and implement a transition from the
* splash UI to Flutter's UI in {@link #transitionToFlutter(Runnable)}.
*/
public
interface
SplashScreen
{
/**
* Creates a {@code View} to be displayed as a splash screen before
* Flutter renders its first frame.
* <p>
* This method can be called at any time, and may be called multiple times depending on Android
* configuration changes that require recreation of a view hierarchy. Implementers that provide
* a stateful splash view, such as one with animations, should take care to migrate that
* animation state from the previously returned splash view to the newly created splash view.
*/
@Nullable
View
createSplashView
(
@NonNull
Context
context
,
@Nullable
Bundle
savedInstanceState
);
/**
* Invoked by Flutter when Flutter has rendered its first frame, and would like
* the {@code splashView} to disappear.
* <p>
* The provided {@code onTransitionComplete} callback must be invoked when
* the splash {@code View} has finished transitioning itself away. The splash
* {@code View} will be removed and destroyed when the callback is invoked.
*/
void
transitionToFlutter
(
@NonNull
Runnable
onTransitionComplete
);
/**
* Returns {@code true} if the splash {@code View} built by this {@code SplashScreen}
* remembers its transition progress across configuration changes by saving that progress to
* {@code View} state. Returns {@code false} otherwise.
* <p>
* The typical return value for this method is {@code false}. When the return value is
* {@code false}, the following can happen:
* <ol>
* <li>Splash {@code View} begins transitioning to the Flutter UI.
* <li>A configuration change occurs, like an orientation change, and the {@code Activity}
* is re-created, along with the {@code View} hierarchy.
* <li>The remainder of the splash transition is skipped and the Flutter UI is displayed.
* </ol>
* In the vast majority of cases, skipping a little bit of the splash transition should be
* acceptable. Most users will never experience such a situation, and those that do are
* unlikely to notice the visual artifact. However, a workaround is available for those
* developers who need it.
* <p>
* Returning {@code true} from this method will cause the given splash {@code View} to be
* displayed in the {@code View} hierarchy, even if Flutter has already rendered its first
* frame. It is then the responsibility of the splash {@code View} to remember its previous
* transition progress, restart any animations, and then trigger its completion callback
* when appropriate. It is also the responsibility of the splash {@code View} to immediately
* invoke the completion callback if it has already completed its transition. By meeting
* these requirements, and returning {@code true} from this method, the splash screen
* experience will be completely seamless, including configuration changes.
*/
// We suppress NewApi because the CI linter thinks that "default" methods are unsupported.
@SuppressLint
(
"NewApi"
)
default
boolean
doesSplashViewRememberItsTransition
()
{
return
false
;
}
/**
* Returns whatever state is necessary to restore a splash {@code View} after destruction
* and recreation, e.g., orientation change.
*/
// We suppress NewApi because the CI linter thinks that "default" methods are unsupported.
@SuppressLint
(
"NewApi"
)
@Nullable
default
Bundle
saveSplashScreenState
()
{
return
null
;
}
}
shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java
浏览文件 @
3aaeca3a
...
...
@@ -41,9 +41,18 @@ public class FlutterRenderer implements TextureRegistry {
private
final
FlutterJNI
flutterJNI
;
private
final
AtomicLong
nextTextureId
=
new
AtomicLong
(
0L
);
private
RenderSurface
renderSurface
;
private
boolean
hasRenderedFirstFrame
=
false
;
private
final
OnFirstFrameRenderedListener
onFirstFrameRenderedListener
=
new
OnFirstFrameRenderedListener
()
{
@Override
public
void
onFirstFrameRendered
()
{
hasRenderedFirstFrame
=
true
;
}
};
public
FlutterRenderer
(
@NonNull
FlutterJNI
flutterJNI
)
{
this
.
flutterJNI
=
flutterJNI
;
this
.
flutterJNI
.
addOnFirstFrameRenderedListener
(
onFirstFrameRenderedListener
);
}
/**
...
...
@@ -78,8 +87,16 @@ public class FlutterRenderer implements TextureRegistry {
}
}
public
boolean
hasRenderedFirstFrame
()
{
return
hasRenderedFirstFrame
;
}
public
void
addOnFirstFrameRenderedListener
(
@NonNull
OnFirstFrameRenderedListener
listener
)
{
flutterJNI
.
addOnFirstFrameRenderedListener
(
listener
);
if
(
hasRenderedFirstFrame
)
{
listener
.
onFirstFrameRendered
();
}
}
public
void
removeOnFirstFrameRenderedListener
(
@NonNull
OnFirstFrameRenderedListener
listener
)
{
...
...
@@ -283,6 +300,9 @@ public class FlutterRenderer implements TextureRegistry {
*/
void
detachFromRenderer
();
// TODO(mattcarroll): convert old FlutterView to use FlutterEngine instead of individual
// components, then use FlutterEngine's FlutterRenderer to watch for the first frame and
// remove the following methods from this interface.
/**
* The {@link FlutterRenderer} corresponding to this {@code RenderSurface} has painted its
* first frame since being initialized.
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录