Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
flutter_boost_1.22.4
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
李增强
flutter_boost_1.22.4
Commits
3a7185d0
Commit
3a7185d0
authored
Nov 18, 2020
by
justin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1.22适配
parent
ec7c6e0b
Changes
28
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
150 additions
and
2216 deletions
+150
-2216
android/build.gradle
android/build.gradle
+5
-5
android/gradle.properties
android/gradle.properties
+1
-0
android/src/main/java/com/idlefish/flutterboost/BoostRegistrar.java
...c/main/java/com/idlefish/flutterboost/BoostRegistrar.java
+1
-1
android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java
...src/main/java/com/idlefish/flutterboost/FlutterBoost.java
+2
-2
android/src/main/java/com/idlefish/flutterboost/FlutterBoostPlugin.java
...in/java/com/idlefish/flutterboost/FlutterBoostPlugin.java
+2
-2
android/src/main/java/com/idlefish/flutterboost/XAndroidKeyProcessor.java
.../java/com/idlefish/flutterboost/XAndroidKeyProcessor.java
+0
-99
android/src/main/java/com/idlefish/flutterboost/XFlutterTextureView.java
...n/java/com/idlefish/flutterboost/XFlutterTextureView.java
+0
-186
android/src/main/java/com/idlefish/flutterboost/XFlutterView.java
...src/main/java/com/idlefish/flutterboost/XFlutterView.java
+0
-773
android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java
...va/com/idlefish/flutterboost/XInputConnectionAdaptor.java
+0
-263
android/src/main/java/com/idlefish/flutterboost/XPlatformPlugin.java
.../main/java/com/idlefish/flutterboost/XPlatformPlugin.java
+0
-352
android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java
...main/java/com/idlefish/flutterboost/XTextInputPlugin.java
+0
-413
android/src/main/java/com/idlefish/flutterboost/containers/BoostFlutterActivity.java
...dlefish/flutterboost/containers/BoostFlutterActivity.java
+13
-13
android/src/main/java/com/idlefish/flutterboost/containers/BoostViewUtils.java
.../com/idlefish/flutterboost/containers/BoostViewUtils.java
+0
-24
android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java
...rboost/containers/FlutterActivityAndFragmentDelegate.java
+32
-26
android/src/main/java/com/idlefish/flutterboost/containers/FlutterFragment.java
...com/idlefish/flutterboost/containers/FlutterFragment.java
+14
-10
android/src/main/java/com/idlefish/flutterboost/containers/FlutterSplashView.java
...m/idlefish/flutterboost/containers/FlutterSplashView.java
+4
-4
example/android/app/build.gradle
example/android/app/build.gradle
+7
-7
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/FitSystemWindowFrameLayout.java
...efish/flutterboostexample/FitSystemWindowFrameLayout.java
+4
-4
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/FlutterFragmentPageActivity.java
...fish/flutterboostexample/FlutterFragmentPageActivity.java
+3
-3
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MainActivity.java
...com/taobao/idlefish/flutterboostexample/MainActivity.java
+2
-2
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/NativePageActivity.java
...obao/idlefish/flutterboostexample/NativePageActivity.java
+2
-2
example/android/build.gradle
example/android/build.gradle
+1
-1
example/android/gradle.properties
example/android/gradle.properties
+2
-0
example/android/gradle/wrapper/gradle-wrapper.properties
example/android/gradle/wrapper/gradle-wrapper.properties
+1
-1
example/android/settings_aar.gradle
example/android/settings_aar.gradle
+1
-0
example/ios/Runner.xcworkspace/xcuserdata/wubian.xcuserdatad/UserInterfaceState.xcuserstate
...serdata/wubian.xcuserdatad/UserInterfaceState.xcuserstate
+0
-0
example/lib/main.dart
example/lib/main.dart
+50
-23
lib/container/boost_container.dart
lib/container/boost_container.dart
+3
-0
No files found.
android/build.gradle
View file @
3a7185d0
...
...
@@ -27,7 +27,7 @@ android {
defaultConfig
{
minSdkVersion
16
targetSdkVersion
28
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner
'androidx.test.runner.AndroidJUnitRunner'
}
lintOptions
{
disable
'InvalidPackage'
...
...
@@ -35,10 +35,10 @@ android {
}
dependencies
{
compileOnly
'
com.android.support:appcompat-v7:28
.0.0'
compileOnly
'com.
android.support:design:28
.0.0'
compileOnly
'
com.android.support:support-v4:28
.0.0'
implementation
'android
.arch.lifecycle:common-java8:1.1.1
'
compileOnly
'
androidx.appcompat:appcompat:1
.0.0'
compileOnly
'com.
google.android.material:material:1
.0.0'
compileOnly
'
androidx.legacy:legacy-support-v4:1
.0.0'
implementation
'android
x.lifecycle:lifecycle-common-java8:2.0.0
'
compileOnly
'com.alibaba:fastjson:1.2.41'
}
...
...
android/gradle.properties
View file @
3a7185d0
org.gradle.jvmargs
=
-Xmx1536M
android.enableR8
=
true
android/src/main/java/com/idlefish/flutterboost/BoostRegistrar.java
View file @
3a7185d0
...
...
@@ -2,7 +2,7 @@ package com.idlefish.flutterboost;
import
android.app.Activity
;
import
android.content.Context
;
import
android
.support
.annotation.NonNull
;
import
android
x
.annotation.NonNull
;
import
com.idlefish.flutterboost.interfaces.IContainerRecord
;
import
io.flutter.Log
;
import
io.flutter.embedding.engine.plugins.FlutterPlugin
;
...
...
android/src/main/java/com/idlefish/flutterboost/FlutterBoost.java
View file @
3a7185d0
...
...
@@ -6,7 +6,7 @@ import android.app.Application;
import
android.content.Context
;
import
android.os.Bundle
;
import
android
.support
.annotation.NonNull
;
import
android
x
.annotation.NonNull
;
import
com.idlefish.flutterboost.interfaces.*
;
...
...
@@ -355,7 +355,7 @@ public class FlutterBoost {
mEngine
=
new
FlutterEngine
(
mPlatform
.
getApplication
().
getApplicationContext
(),
FlutterLoader
.
getInstance
(),
new
FlutterJNI
(),
null
,
false
);
}
//
registerPlugins(mEngine);
registerPlugins
(
mEngine
);
// mRegistry = new BoostPluginRegistry(createEngine());
// mPlatform.registerPlugins(mRegistry);
}
...
...
android/src/main/java/com/idlefish/flutterboost/FlutterBoostPlugin.java
View file @
3a7185d0
package
com.idlefish.flutterboost
;
import
android
.support
.annotation.Nullable
;
import
android.os.Handler
;
import
android
x
.annotation.Nullable
;
import
android.util.Log
;
import
com.idlefish.flutterboost.interfaces.IContainerRecord
;
...
...
android/src/main/java/com/idlefish/flutterboost/XAndroidKeyProcessor.java
deleted
100644 → 0
View file @
ec7c6e0b
package
com.idlefish.flutterboost
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.view.KeyCharacterMap
;
import
android.view.KeyEvent
;
import
io.flutter.embedding.engine.systemchannels.KeyEventChannel
;
import
io.flutter.plugin.editing.TextInputPlugin
;
public
class
XAndroidKeyProcessor
{
@NonNull
private
final
KeyEventChannel
keyEventChannel
;
@NonNull
private
final
XTextInputPlugin
textInputPlugin
;
private
int
combiningCharacter
;
public
XAndroidKeyProcessor
(
@NonNull
KeyEventChannel
keyEventChannel
,
@NonNull
XTextInputPlugin
textInputPlugin
)
{
this
.
keyEventChannel
=
keyEventChannel
;
this
.
textInputPlugin
=
textInputPlugin
;
}
public
void
onKeyUp
(
@NonNull
KeyEvent
keyEvent
)
{
Character
complexCharacter
=
applyCombiningCharacterToBaseCharacter
(
keyEvent
.
getUnicodeChar
());
keyEventChannel
.
keyUp
(
new
KeyEventChannel
.
FlutterKeyEvent
(
keyEvent
,
complexCharacter
)
);
}
public
void
onKeyDown
(
@NonNull
KeyEvent
keyEvent
)
{
if
(
textInputPlugin
.
getLastInputConnection
()
!=
null
&&
textInputPlugin
.
getInputMethodManager
().
isAcceptingText
())
{
textInputPlugin
.
getLastInputConnection
().
sendKeyEvent
(
keyEvent
);
}
Character
complexCharacter
=
applyCombiningCharacterToBaseCharacter
(
keyEvent
.
getUnicodeChar
());
keyEventChannel
.
keyDown
(
new
KeyEventChannel
.
FlutterKeyEvent
(
keyEvent
,
complexCharacter
)
);
}
/**
* Applies the given Unicode character in {@code newCharacterCodePoint} to a previously
* entered Unicode combining character and returns the combination of these characters
* if a combination exists.
* <p>
* This method mutates {@link #combiningCharacter} over time to combine characters.
* <p>
* One of the following things happens in this method:
* <ul>
* <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is not a combining character, then {@code newCharacterCodePoint} is returned.</li>
* <li>If no previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is a combining character, then {@code newCharacterCodePoint} is saved as the
* {@link #combiningCharacter} and null is returned.</li>
* <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is also a combining character, then the {@code newCharacterCodePoint} is combined with
* the existing {@link #combiningCharacter} and null is returned.</li>
* <li>If a previous {@link #combiningCharacter} exists and the {@code newCharacterCodePoint}
* is not a combining character, then the {@link #combiningCharacter} is applied to the
* regular {@code newCharacterCodePoint} and the resulting complex character is returned. The
* {@link #combiningCharacter} is cleared.</li>
* </ul>
* <p>
* The following reference explains the concept of a "combining character":
* https://en.wikipedia.org/wiki/Combining_character
*/
@Nullable
private
Character
applyCombiningCharacterToBaseCharacter
(
int
newCharacterCodePoint
)
{
if
(
newCharacterCodePoint
==
0
)
{
return
null
;
}
Character
complexCharacter
=
(
char
)
newCharacterCodePoint
;
boolean
isNewCodePointACombiningCharacter
=
(
newCharacterCodePoint
&
KeyCharacterMap
.
COMBINING_ACCENT
)
!=
0
;
if
(
isNewCodePointACombiningCharacter
)
{
// If a combining character was entered before, combine this one with that one.
int
plainCodePoint
=
newCharacterCodePoint
&
KeyCharacterMap
.
COMBINING_ACCENT_MASK
;
if
(
combiningCharacter
!=
0
)
{
combiningCharacter
=
KeyCharacterMap
.
getDeadChar
(
combiningCharacter
,
plainCodePoint
);
}
else
{
combiningCharacter
=
plainCodePoint
;
}
}
else
{
// The new character is a regular character. Apply combiningCharacter to it, if it exists.
if
(
combiningCharacter
!=
0
)
{
int
combinedChar
=
KeyCharacterMap
.
getDeadChar
(
combiningCharacter
,
newCharacterCodePoint
);
if
(
combinedChar
>
0
)
{
complexCharacter
=
(
char
)
combinedChar
;
}
combiningCharacter
=
0
;
}
}
return
complexCharacter
;
}
}
\ No newline at end of file
android/src/main/java/com/idlefish/flutterboost/XFlutterTextureView.java
deleted
100644 → 0
View file @
ec7c6e0b
package
com.idlefish.flutterboost
;
import
android.content.Context
;
import
android.graphics.SurfaceTexture
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.util.AttributeSet
;
import
android.view.Surface
;
import
android.view.TextureView
;
import
io.flutter.Log
;
import
io.flutter.embedding.engine.renderer.FlutterRenderer
;
import
io.flutter.embedding.engine.renderer.RenderSurface
;
public
class
XFlutterTextureView
extends
TextureView
implements
RenderSurface
{
private
static
final
String
TAG
=
"FlutterTextureView"
;
private
boolean
isSurfaceAvailableForRendering
=
false
;
private
boolean
isAttachedToFlutterRenderer
=
false
;
@Nullable
private
FlutterRenderer
flutterRenderer
;
private
Surface
renderSurface
;
// Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native code.
// Callbacks are received by this Object and then those messages are forwarded to our
// FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
private
final
SurfaceTextureListener
surfaceTextureListener
=
new
SurfaceTextureListener
()
{
@Override
public
void
onSurfaceTextureAvailable
(
SurfaceTexture
surfaceTexture
,
int
width
,
int
height
)
{
Log
.
v
(
TAG
,
"SurfaceTextureListener.onSurfaceTextureAvailable()"
);
isSurfaceAvailableForRendering
=
true
;
// If we're already attached to a FlutterRenderer then we're now attached to both a renderer
// and the Android window, so we can begin rendering now.
if
(
isAttachedToFlutterRenderer
)
{
connectSurfaceToRenderer
();
}
}
@Override
public
void
onSurfaceTextureSizeChanged
(
@NonNull
SurfaceTexture
surface
,
int
width
,
int
height
)
{
Log
.
v
(
TAG
,
"SurfaceTextureListener.onSurfaceTextureSizeChanged()"
);
if
(
isAttachedToFlutterRenderer
)
{
changeSurfaceSize
(
width
,
height
);
}
}
@Override
public
void
onSurfaceTextureUpdated
(
@NonNull
SurfaceTexture
surface
)
{
// Invoked every time a new frame is available. We don't care.
}
@Override
public
boolean
onSurfaceTextureDestroyed
(
@NonNull
SurfaceTexture
surface
)
{
Log
.
v
(
TAG
,
"SurfaceTextureListener.onSurfaceTextureDestroyed()"
);
isSurfaceAvailableForRendering
=
false
;
// If we're attached to a FlutterRenderer then we need to notify it that our SurfaceTexture
// has been destroyed.
if
(
isAttachedToFlutterRenderer
)
{
disconnectSurfaceFromRenderer
();
}
// Return true to indicate that no further painting will take place
// within this SurfaceTexture.
return
true
;
}
};
/**
* Constructs a {@code FlutterTextureView} programmatically, without any XML attributes.
*/
public
XFlutterTextureView
(
@NonNull
Context
context
)
{
this
(
context
,
null
);
}
/**
* Constructs a {@code FlutterTextureView} in an XML-inflation-compliant manner.
*/
public
XFlutterTextureView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
)
{
super
(
context
,
attrs
);
init
();
}
private
void
init
()
{
// Listen for when our underlying SurfaceTexture becomes available, changes size, or
// gets destroyed, and take the appropriate actions.
setSurfaceTextureListener
(
surfaceTextureListener
);
}
@Nullable
@Override
public
FlutterRenderer
getAttachedRenderer
()
{
return
flutterRenderer
;
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering
* a Flutter UI to this {@code FlutterTextureView}.
*
* If an Android {@link SurfaceTexture} is available, this method will give that
* {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
* Flutter's UI to this {@code FlutterTextureView}.
*
* If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView}
* will wait until a {@link SurfaceTexture} becomes available and then give that
* {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
* Flutter's UI to this {@code FlutterTextureView}.
*/
public
void
attachToRenderer
(
@NonNull
FlutterRenderer
flutterRenderer
)
{
Log
.
v
(
TAG
,
"Attaching to FlutterRenderer."
);
if
(
this
.
flutterRenderer
!=
null
)
{
Log
.
v
(
TAG
,
"Already connected to a FlutterRenderer. Detaching from old one and attaching to new one."
);
this
.
flutterRenderer
.
stopRenderingToSurface
();
}
this
.
flutterRenderer
=
flutterRenderer
;
isAttachedToFlutterRenderer
=
true
;
// If we're already attached to an Android window then we're now attached to both a renderer
// and the Android window. We can begin rendering now.
if
(
isSurfaceAvailableForRendering
)
{
Log
.
v
(
TAG
,
"Surface is available for rendering. Connecting FlutterRenderer to Android surface."
);
connectSurfaceToRenderer
();
}
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it no longer wants to render
* a Flutter UI to this {@code FlutterTextureView}.
*
* This method will cease any on-going rendering from Flutter to this {@code FlutterTextureView}.
*/
public
void
detachFromRenderer
()
{
if
(
flutterRenderer
!=
null
)
{
// If we're attached to an Android window then we were rendering a Flutter UI. Now that
// this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering.
// TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() != null"
if
(
getWindowToken
()
!=
null
)
{
Log
.
v
(
TAG
,
"Disconnecting FlutterRenderer from Android surface."
);
disconnectSurfaceFromRenderer
();
}
flutterRenderer
=
null
;
isAttachedToFlutterRenderer
=
false
;
}
else
{
Log
.
w
(
TAG
,
"detachFromRenderer() invoked when no FlutterRenderer was attached."
);
}
}
// FlutterRenderer and getSurfaceTexture() must both be non-null.
private
void
connectSurfaceToRenderer
()
{
if
(
flutterRenderer
==
null
||
getSurfaceTexture
()
==
null
)
{
throw
new
IllegalStateException
(
"connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null."
);
}
// flutterRenderer.startRenderingToSurface(new Surface(getSurfaceTexture()));
renderSurface
=
new
Surface
(
getSurfaceTexture
());
flutterRenderer
.
startRenderingToSurface
(
renderSurface
);
}
// FlutterRenderer must be non-null.
private
void
changeSurfaceSize
(
int
width
,
int
height
)
{
if
(
flutterRenderer
==
null
)
{
throw
new
IllegalStateException
(
"changeSurfaceSize() should only be called when flutterRenderer is non-null."
);
}
Log
.
v
(
TAG
,
"Notifying FlutterRenderer that Android surface size has changed to "
+
width
+
" x "
+
height
);
flutterRenderer
.
surfaceChanged
(
width
,
height
);
}
// FlutterRenderer must be non-null.
private
void
disconnectSurfaceFromRenderer
()
{
if
(
flutterRenderer
==
null
)
{
throw
new
IllegalStateException
(
"disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null."
);
}
flutterRenderer
.
stopRenderingToSurface
();
if
(
renderSurface
!=
null
){
renderSurface
.
release
();
renderSurface
=
null
;
}
}
}
\ No newline at end of file
android/src/main/java/com/idlefish/flutterboost/XFlutterView.java
deleted
100644 → 0
View file @
ec7c6e0b
package
com.idlefish.flutterboost
;
import
android.annotation.TargetApi
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.content.res.Configuration
;
//import android.graphics.Insets;
import
android.graphics.Rect
;
import
android.os.Build
;
import
android.os.LocaleList
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.support.annotation.RequiresApi
;
import
android.support.annotation.VisibleForTesting
;
import
android.text.format.DateFormat
;
import
android.util.AttributeSet
;
import
android.view.KeyEvent
;
import
android.view.MotionEvent
;
import
android.view.View
;
import
android.view.WindowInsets
;
import
android.view.accessibility.AccessibilityManager
;
import
android.view.accessibility.AccessibilityNodeProvider
;
import
android.view.inputmethod.EditorInfo
;
import
android.view.inputmethod.InputConnection
;
import
android.widget.FrameLayout
;
import
java.util.ArrayList
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Set
;
import
io.flutter.Log
;
import
io.flutter.embedding.android.*
;
import
io.flutter.embedding.engine.FlutterEngine
;
import
io.flutter.embedding.engine.renderer.FlutterRenderer
;
import
io.flutter.embedding.engine.renderer.FlutterUiDisplayListener
;
import
io.flutter.embedding.engine.renderer.RenderSurface
;
import
io.flutter.plugin.editing.TextInputPlugin
;
import
io.flutter.plugin.platform.PlatformViewsController
;
import
io.flutter.view.AccessibilityBridge
;
public
class
XFlutterView
extends
FrameLayout
{
private
static
final
String
TAG
=
"FlutterView"
;
// Behavior configuration of this FlutterView.
@NonNull
private
FlutterView
.
RenderMode
renderMode
;
@Nullable
private
FlutterView
.
TransparencyMode
transparencyMode
;
// Internal view hierarchy references.
@Nullable
private
RenderSurface
renderSurface
;
private
final
Set
<
FlutterUiDisplayListener
>
flutterUiDisplayListeners
=
new
HashSet
<>();
private
boolean
didRenderFirstFrame
;
// Connections to a Flutter execution context.
@Nullable
private
FlutterEngine
flutterEngine
;
@NonNull
private
final
Set
<
FlutterView
.
FlutterEngineAttachmentListener
>
flutterEngineAttachmentListeners
=
new
HashSet
<>();
// Components that process various types of Android View input and events,
// possibly storing intermediate state, and communicating those events to Flutter.
//
// These components essentially add some additional behavioral logic on top of
// existing, stateless system channels, e.g., KeyEventChannel, TextInputChannel, etc.
@Nullable
private
XTextInputPlugin
textInputPlugin
;
@Nullable
private
XAndroidKeyProcessor
androidKeyProcessor
;
@Nullable
private
AndroidTouchProcessor
androidTouchProcessor
;
@Nullable
private
AccessibilityBridge
accessibilityBridge
;
private
boolean
hasAddFirstFrameRenderedListener
=
false
;
private
boolean
isFlutterUiDisplayed
;
// Directly implemented View behavior that communicates with Flutter.
private
final
FlutterRenderer
.
ViewportMetrics
viewportMetrics
=
new
FlutterRenderer
.
ViewportMetrics
();
private
final
AccessibilityBridge
.
OnAccessibilityChangeListener
onAccessibilityChangeListener
=
new
AccessibilityBridge
.
OnAccessibilityChangeListener
()
{
@Override
public
void
onAccessibilityChanged
(
boolean
isAccessibilityEnabled
,
boolean
isTouchExplorationEnabled
)
{
resetWillNotDraw
(
isAccessibilityEnabled
,
isTouchExplorationEnabled
);
}
};
private
final
FlutterUiDisplayListener
flutterUiDisplayListener
=
new
FlutterUiDisplayListener
()
{
@Override
public
void
onFlutterUiDisplayed
()
{
isFlutterUiDisplayed
=
true
;
for
(
FlutterUiDisplayListener
listener
:
flutterUiDisplayListeners
)
{
listener
.
onFlutterUiDisplayed
();
}
}
@Override
public
void
onFlutterUiNoLongerDisplayed
()
{
isFlutterUiDisplayed
=
false
;
for
(
FlutterUiDisplayListener
listener
:
flutterUiDisplayListeners
)
{
listener
.
onFlutterUiNoLongerDisplayed
();
}
}
};
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes.
* <p>
* <ul>
* </ul>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public
XFlutterView
(
@NonNull
Context
context
)
{
this
(
context
,
null
,
null
,
null
);
}
public
XFlutterView
(
@NonNull
Context
context
,
@NonNull
FlutterView
.
RenderMode
renderMode
)
{
this
(
context
,
null
,
renderMode
,
null
);
}
public
XFlutterView
(
@NonNull
Context
context
,
@NonNull
FlutterView
.
TransparencyMode
transparencyMode
)
{
this
(
context
,
null
,
FlutterView
.
RenderMode
.
surface
,
transparencyMode
);
}
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows
* a selection of {@link #renderMode} and {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public
XFlutterView
(
@NonNull
Context
context
,
@NonNull
FlutterView
.
RenderMode
renderMode
,
@NonNull
FlutterView
.
TransparencyMode
transparencyMode
)
{
this
(
context
,
null
,
renderMode
,
transparencyMode
);
}
/**
* Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
// TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
public
XFlutterView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
)
{
this
(
context
,
attrs
,
null
,
null
);
}
private
XFlutterView
(
@NonNull
Context
context
,
@Nullable
AttributeSet
attrs
,
@Nullable
FlutterView
.
RenderMode
renderMode
,
@Nullable
FlutterView
.
TransparencyMode
transparencyMode
)
{
super
(
context
,
attrs
);
this
.
renderMode
=
renderMode
==
null
?
FlutterView
.
RenderMode
.
surface
:
renderMode
;
this
.
transparencyMode
=
transparencyMode
!=
null
?
transparencyMode
:
FlutterView
.
TransparencyMode
.
opaque
;
init
();
}
private
void
init
()
{
Log
.
v
(
TAG
,
"Initializing FlutterView"
);
switch
(
renderMode
)
{
case
surface:
Log
.
v
(
TAG
,
"Internally using a FlutterSurfaceView."
);
FlutterSurfaceView
flutterSurfaceView
=
new
FlutterSurfaceView
(
getContext
(),
transparencyMode
==
FlutterView
.
TransparencyMode
.
transparent
);
renderSurface
=
flutterSurfaceView
;
addView
(
flutterSurfaceView
);
break
;
case
texture:
Log
.
v
(
TAG
,
"Internally using a FlutterTextureView."
);
XFlutterTextureView
flutterTextureView
=
new
XFlutterTextureView
(
getContext
());
renderSurface
=
flutterTextureView
;
addView
(
flutterTextureView
);
break
;
}
// FlutterView needs to be focusable so that the InputMethodManager can interact with it.
setFocusable
(
true
);
setFocusableInTouchMode
(
true
);
}
/**
* Returns true if an attached {@link FlutterEngine} has rendered at least 1 frame to this
* {@code FlutterView}.
* <p>
* Returns false if no {@link FlutterEngine} is attached.
* <p>
* This flag is specific to a given {@link FlutterEngine}. The following hypothetical timeline
* demonstrates how this flag changes over time.
* <ol>
* <li>{@code flutterEngineA} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineA} renders its first frame to this {@code FlutterView}: returns true</li>
* <li>{@code flutterEngineA} is detached from this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} renders its first frame to this {@code FlutterView}: returns true</li>
* </ol>
*/
public
boolean
hasRenderedFirstFrame
()
{
return
didRenderFirstFrame
;
}
public
void
addOnFirstFrameRenderedListener
(
@NonNull
FlutterUiDisplayListener
listener
)
{
flutterUiDisplayListeners
.
add
(
listener
);
}
/**
* Removes the given {@code listener}, which was previously added with
* {@link #addOnFirstFrameRenderedListener(FlutterUiDisplayListener)}.
*/
public
void
removeOnFirstFrameRenderedListener
(
@NonNull
FlutterUiDisplayListener
listener
)
{
flutterUiDisplayListeners
.
remove
(
listener
);
}
//------- Start: Process View configuration that Flutter cares about. ------
/**
* Sends relevant configuration data from Android to Flutter when the Android
* {@link Configuration} changes.
*
* The Android {@link Configuration} might change as a result of device orientation
* change, device language change, device text scale factor change, etc.
*/
@Override
protected
void
onConfigurationChanged
(
@NonNull
Configuration
newConfig
)
{
super
.
onConfigurationChanged
(
newConfig
);
Log
.
v
(
TAG
,
"Configuration changed. Sending locales and user settings to Flutter."
);
try
{
sendLocalesToFlutter
(
newConfig
);
sendUserSettingsToFlutter
();
}
catch
(
Throwable
e
){
Log
.
e
(
TAG
,
"onConfigurationChanged error "
);
}
}
/**
* Invoked when this {@code FlutterView} changes size, including upon initial
* measure.
*
* The initial measure reports an {@code oldWidth} and {@code oldHeight} of zero.
*
* Flutter cares about the width and height of the view that displays it on the host
* platform. Therefore, when this method is invoked, the new width and height are
* communicated to Flutter as the "physical size" of the view that displays Flutter's
* UI.
*/
@Override
protected
void
onSizeChanged
(
int
width
,
int
height
,
int
oldWidth
,
int
oldHeight
)
{
super
.
onSizeChanged
(
width
,
height
,
oldWidth
,
oldHeight
);
Log
.
v
(
TAG
,
"Size changed. Sending Flutter new viewport metrics. FlutterView was "
+
oldWidth
+
" x "
+
oldHeight
+
", it is now "
+
width
+
" x "
+
height
);
viewportMetrics
.
width
=
width
;
viewportMetrics
.
height
=
height
;
sendViewportMetricsToFlutter
();
}
/**
* Invoked when Android's desired window insets change, i.e., padding.
*
* Flutter does not use a standard {@code View} hierarchy and therefore Flutter is
* unaware of these insets. Therefore, this method calculates the viewport metrics
* that Flutter should use and then sends those metrics to Flutter.
*
* This callback is not present in API < 20, which means lower API devices will see
* the wider than expected padding when the status and navigation bars are hidden.
*/
@Override
@TargetApi
(
20
)
@RequiresApi
(
20
)
// The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings
// caused by usage of Android Q APIs. These calls are safe because they are
// guarded.
@SuppressLint
({
"InlinedApi"
,
"NewApi"
})
@NonNull
public
final
WindowInsets
onApplyWindowInsets
(
@NonNull
WindowInsets
insets
)
{
WindowInsets
newInsets
=
super
.
onApplyWindowInsets
(
insets
);
// Status bar (top) and left/right system insets should partially obscure the content (padding).
viewportMetrics
.
paddingTop
=
insets
.
getSystemWindowInsetTop
();
viewportMetrics
.
paddingRight
=
insets
.
getSystemWindowInsetRight
();
viewportMetrics
.
paddingBottom
=
0
;
viewportMetrics
.
paddingLeft
=
insets
.
getSystemWindowInsetLeft
();
// Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
viewportMetrics
.
viewInsetTop
=
0
;
viewportMetrics
.
viewInsetRight
=
0
;
viewportMetrics
.
viewInsetBottom
=
insets
.
getSystemWindowInsetBottom
();
viewportMetrics
.
viewInsetLeft
=
0
;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Insets systemGestureInsets = insets.getSystemGestureInsets();
// viewportMetrics.systemGestureInsetTop = systemGestureInsets.top;
// viewportMetrics.systemGestureInsetRight = systemGestureInsets.right;
// viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
// viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left;
// }
Log
.
v
(
TAG
,
"Updating window insets (onApplyWindowInsets()):\n"
+
"Status bar insets: Top: "
+
viewportMetrics
.
paddingTop
+
", Left: "
+
viewportMetrics
.
paddingLeft
+
", Right: "
+
viewportMetrics
.
paddingRight
+
"\n"
+
"Keyboard insets: Bottom: "
+
viewportMetrics
.
viewInsetBottom
+
", Left: "
+
viewportMetrics
.
viewInsetLeft
+
", Right: "
+
viewportMetrics
.
viewInsetRight
+
"System Gesture Insets - Left: "
+
viewportMetrics
.
systemGestureInsetLeft
+
", Top: "
+
viewportMetrics
.
systemGestureInsetTop
+
", Right: "
+
viewportMetrics
.
systemGestureInsetRight
+
", Bottom: "
+
viewportMetrics
.
viewInsetBottom
);
sendViewportMetricsToFlutter
();
return
newInsets
;
}
/**
* Invoked when Android's desired window insets change, i.e., padding.
*
* {@code fitSystemWindows} is an earlier version of
* {@link #onApplyWindowInsets(WindowInsets)}. See that method for more details
* about how window insets relate to Flutter.
*/
@Override
@SuppressWarnings
(
"deprecation"
)
protected
boolean
fitSystemWindows
(
@NonNull
Rect
insets
)
{
if
(
Build
.
VERSION
.
SDK_INT
<=
Build
.
VERSION_CODES
.
KITKAT
)
{
// Status bar, left/right system insets partially obscure content (padding).
viewportMetrics
.
paddingTop
=
insets
.
top
;
viewportMetrics
.
paddingRight
=
insets
.
right
;
viewportMetrics
.
paddingBottom
=
0
;
viewportMetrics
.
paddingLeft
=
insets
.
left
;
// Bottom system inset (keyboard) should adjust scrollable bottom edge (inset).
viewportMetrics
.
viewInsetTop
=
0
;
viewportMetrics
.
viewInsetRight
=
0
;
viewportMetrics
.
viewInsetBottom
=
insets
.
bottom
;
viewportMetrics
.
viewInsetLeft
=
0
;
Log
.
v
(
TAG
,
"Updating window insets (fitSystemWindows()):\n"
+
"Status bar insets: Top: "
+
viewportMetrics
.
paddingTop
+
", Left: "
+
viewportMetrics
.
paddingLeft
+
", Right: "
+
viewportMetrics
.
paddingRight
+
"\n"
+
"Keyboard insets: Bottom: "
+
viewportMetrics
.
viewInsetBottom
+
", Left: "
+
viewportMetrics
.
viewInsetLeft
+
", Right: "
+
viewportMetrics
.
viewInsetRight
);
sendViewportMetricsToFlutter
();
return
true
;
}
else
{
return
super
.
fitSystemWindows
(
insets
);
}
}
//------- End: Process View configuration that Flutter cares about. --------
//-------- Start: Process UI I/O that Flutter cares about. -------
/**
* Creates an {@link InputConnection} to work with a {@link android.view.inputmethod.InputMethodManager}.
*
* Any {@code View} that can take focus or process text input must implement this
* method by returning a non-null {@code InputConnection}. Flutter may render one or
* many focusable and text-input widgets, therefore {@code FlutterView} must support
* an {@code InputConnection}.
*
* The {@code InputConnection} returned from this method comes from a
* {@link TextInputPlugin}, which is owned by this {@code FlutterView}. A
* {@link TextInputPlugin} exists to encapsulate the nuances of input communication,
* rather than spread that logic throughout this {@code FlutterView}.
*/
@Override
@Nullable
public
InputConnection
onCreateInputConnection
(
@NonNull
EditorInfo
outAttrs
)
{
if
(!
isAttachedToFlutterEngine
())
{
return
super
.
onCreateInputConnection
(
outAttrs
);
}
return
textInputPlugin
.
createInputConnection
(
this
,
outAttrs
);
}
/**
* Allows a {@code View} that is not currently the input connection target to invoke commands on
* the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed.
* <p>
* Returns true to allow non-input-connection-targets to invoke methods on
* {@code InputMethodManager}, or false to exclusively allow the input connection target to invoke
* such methods.
*/
@Override
public
boolean
checkInputConnectionProxy
(
View
view
)
{
return
flutterEngine
!=
null
&&
view
!=
null
?
flutterEngine
.
getPlatformViewsController
().
checkInputConnectionProxy
(
view
)
:
super
.
checkInputConnectionProxy
(
view
);
}
/**
* Invoked when key is released.
*
* This method is typically invoked in response to the release of a physical
* keyboard key or a D-pad button. It is generally not invoked when a virtual
* software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations.
*
* {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character.
*/
@Override
public
boolean
onKeyUp
(
int
keyCode
,
@NonNull
KeyEvent
event
)
{
if
(!
isAttachedToFlutterEngine
())
{
return
super
.
onKeyUp
(
keyCode
,
event
);
}
androidKeyProcessor
.
onKeyUp
(
event
);
return
super
.
onKeyUp
(
keyCode
,
event
);
}
/**
* Invoked when key is pressed.
*
* This method is typically invoked in response to the press of a physical
* keyboard key or a D-pad button. It is generally not invoked when a virtual
* software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations.
*
* {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character.
*/
@Override
public
boolean
onKeyDown
(
int
keyCode
,
@NonNull
KeyEvent
event
)
{
if
(!
isAttachedToFlutterEngine
())
{
return
super
.
onKeyDown
(
keyCode
,
event
);
}
androidKeyProcessor
.
onKeyDown
(
event
);
return
super
.
onKeyDown
(
keyCode
,
event
);
}
/**
* Invoked by Android when a user touch event occurs.
*
* Flutter handles all of its own gesture detection and processing, therefore this
* method forwards all {@link MotionEvent} data from Android to Flutter.
*/
@Override
public
boolean
onTouchEvent
(
@NonNull
MotionEvent
event
)
{
if
(!
isAttachedToFlutterEngine
())
{
return
super
.
onTouchEvent
(
event
);
}
// TODO(abarth): This version check might not be effective in some
// versions of Android that statically compile code and will be upset
// at the lack of |requestUnbufferedDispatch|. Instead, we should factor
// version-dependent code into separate classes for each supported
// version and dispatch dynamically.
if
(
Build
.
VERSION
.
SDK_INT
>=
Build
.
VERSION_CODES
.
LOLLIPOP
)
{
requestUnbufferedDispatch
(
event
);
}
return
androidTouchProcessor
.
onTouchEvent
(
event
);
}
/**
* Invoked by Android when a generic motion event occurs, e.g., joystick movement, mouse hover,
* track pad touches, scroll wheel movements, etc.
*
* Flutter handles all of its own gesture detection and processing, therefore this
* method forwards all {@link MotionEvent} data from Android to Flutter.
*/
@Override
public
boolean
onGenericMotionEvent
(
@NonNull
MotionEvent
event
)
{
boolean
handled
=
isAttachedToFlutterEngine
()
&&
androidTouchProcessor
.
onGenericMotionEvent
(
event
);
return
handled
?
true
:
super
.
onGenericMotionEvent
(
event
);
}
/**
* Invoked by Android when a hover-compliant input system causes a hover event.
*
* An example of hover events is a stylus sitting near an Android screen. As the
* stylus moves from outside a {@code View} to hover over a {@code View}, or move
* around within a {@code View}, or moves from over a {@code View} to outside a
* {@code View}, a corresponding {@link MotionEvent} is reported via this method.
*
* Hover events can be used for accessibility touch exploration and therefore are
* processed here for accessibility purposes.
*/
@Override
public
boolean
onHoverEvent
(
@NonNull
MotionEvent
event
)
{
if
(!
isAttachedToFlutterEngine
())
{
return
super
.
onHoverEvent
(
event
);
}
boolean
handled
=
false
;
try
{
handled
=
accessibilityBridge
.
onAccessibilityHoverEvent
(
event
);
}
catch
(
Throwable
e
){
Log
.
e
(
TAG
,
"onConfigurationChanged error "
);
}
if
(!
handled
)
{
// TODO(ianh): Expose hover events to the platform,
// implementing ADD, REMOVE, etc.
}
return
handled
;
}
//-------- End: Process UI I/O that Flutter cares about. ---------
//-------- Start: Accessibility -------
@Override
@Nullable
public
AccessibilityNodeProvider
getAccessibilityNodeProvider
()
{
if
(
accessibilityBridge
!=
null
&&
accessibilityBridge
.
isAccessibilityEnabled
())
{
return
accessibilityBridge
;
}
else
{
// TODO(goderbauer): when a11y is off this should return a one-off snapshot of
// the a11y
// tree.
return
null
;
}
}
// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments.
private
void
resetWillNotDraw
(
boolean
isAccessibilityEnabled
,
boolean
isTouchExplorationEnabled
)
{
if
(!
flutterEngine
.
getRenderer
().
isSoftwareRenderingEnabled
())
{
setWillNotDraw
(!(
isAccessibilityEnabled
||
isTouchExplorationEnabled
));
}
else
{
setWillNotDraw
(
false
);
}
}
//-------- End: Accessibility ---------
/**
* Connects this {@code FlutterView} to the given {@link FlutterEngine}.
*
* This {@code FlutterView} will begin rendering the UI painted by the given {@link FlutterEngine}.
* This {@code FlutterView} will also begin forwarding interaction events from this
* {@code FlutterView} to the given {@link FlutterEngine}, e.g., user touch events, accessibility
* events, keyboard events, and others.
*
* See {@link #detachFromFlutterEngine()} for information on how to detach from a
* {@link FlutterEngine}.
*/
public
void
attachToFlutterEngine
(
@NonNull
FlutterEngine
flutterEngine
)
{
Log
.
d
(
TAG
,
"Attaching to a FlutterEngine: "
+
flutterEngine
);
if
(
isAttachedToFlutterEngine
())
{
if
(
flutterEngine
==
this
.
flutterEngine
)
{
// We are already attached to this FlutterEngine
Log
.
d
(
TAG
,
"Already attached to this engine. Doing nothing."
);
return
;
}
// Detach from a previous FlutterEngine so we can attach to this new one.
Log
.
d
(
TAG
,
"Currently attached to a different engine. Detaching and then attaching"
+
" to new engine."
);
detachFromFlutterEngine
();
}
this
.
flutterEngine
=
flutterEngine
;
FlutterRenderer
flutterRenderer
=
this
.
flutterEngine
.
getRenderer
();
isFlutterUiDisplayed
=
flutterRenderer
.
isDisplayingFlutterUi
();
renderSurface
.
attachToRenderer
(
flutterRenderer
);
flutterRenderer
.
addIsDisplayingFlutterUiListener
(
flutterUiDisplayListener
);
this
.
flutterEngine
.
getPlatformViewsController
().
attachToView
(
this
);
textInputPlugin
=
XTextInputPlugin
.
getTextInputPlugin
(
this
.
flutterEngine
.
getDartExecutor
(),
this
.
flutterEngine
.
getPlatformViewsController
());
textInputPlugin
.
updateView
(
this
);
textInputPlugin
.
getInputMethodManager
().
restartInput
(
this
);
this
.
androidKeyProcessor
=
new
XAndroidKeyProcessor
(
this
.
flutterEngine
.
getKeyEventChannel
(),
textInputPlugin
);
this
.
androidTouchProcessor
=
new
AndroidTouchProcessor
(
this
.
flutterEngine
.
getRenderer
());
this
.
accessibilityBridge
=
new
AccessibilityBridge
(
this
,
flutterEngine
.
getAccessibilityChannel
(),
(
AccessibilityManager
)
getContext
().
getSystemService
(
Context
.
ACCESSIBILITY_SERVICE
),
getContext
().
getContentResolver
(),
this
.
flutterEngine
.
getPlatformViewsController
()
);
accessibilityBridge
.
setOnAccessibilityChangeListener
(
onAccessibilityChangeListener
);
resetWillNotDraw
(
accessibilityBridge
.
isAccessibilityEnabled
(),
accessibilityBridge
.
isTouchExplorationEnabled
()
);
// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
// This allows platform Views to hook into Flutter's overall accessibility system.
this
.
flutterEngine
.
getPlatformViewsController
().
attachAccessibilityBridge
(
accessibilityBridge
);
// Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached.
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
textInputPlugin
.
getInputMethodManager
().
restartInput
(
this
);
// Push View and Context related information from Android to Flutter.
sendUserSettingsToFlutter
();
sendLocalesToFlutter
(
getResources
().
getConfiguration
());
sendViewportMetricsToFlutter
();
// Notify engine attachment listeners of the attachment.
for
(
FlutterView
.
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();
// }
}
/**
* Disconnects this {@code FlutterView} from a previously attached {@link FlutterEngine}.
*
* This {@code FlutterView} will clear its UI and stop forwarding all events to the previously-attached
* {@link FlutterEngine}. This includes touch events, accessibility events, keyboard events,
* and others.
*
* See {@link #attachToFlutterEngine(FlutterEngine)} for information on how to attach a
* {@link FlutterEngine}.
*/
public
void
detachFromFlutterEngine
()
{
Log
.
d
(
TAG
,
"Detaching from a FlutterEngine: "
+
flutterEngine
);
if
(!
isAttachedToFlutterEngine
())
{
Log
.
d
(
TAG
,
"Not attached to an engine. Doing nothing."
);
return
;
}
// Notify engine attachment listeners of the detachment.
for
(
FlutterView
.
FlutterEngineAttachmentListener
listener
:
flutterEngineAttachmentListeners
)
{
listener
.
onFlutterEngineDetachedFromFlutterView
();
}
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
flutterEngine
.
getPlatformViewsController
().
detachAccessibiltyBridge
();
flutterEngine
.
getPlatformViewsController
().
detachFromView
();
// Disconnect and clean up the AccessibilityBridge.
accessibilityBridge
.
release
();
accessibilityBridge
=
null
;
// Inform the Android framework that it should retrieve a new InputConnection
// now that the engine is detached. The new InputConnection will be null, which
// signifies that this View does not process input (until a new engine is attached).
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
FlutterRenderer
flutterRenderer
=
flutterEngine
.
getRenderer
();
isFlutterUiDisplayed
=
false
;
flutterRenderer
.
removeIsDisplayingFlutterUiListener
(
flutterUiDisplayListener
);
flutterRenderer
.
stopRenderingToSurface
();
flutterRenderer
.
setSemanticsEnabled
(
false
);
renderSurface
.
detachFromRenderer
();
flutterEngine
=
null
;
}
public
void
release
(){
if
(
textInputPlugin
!=
null
){
textInputPlugin
.
release
(
this
);
}
}
/**
* Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
public
boolean
isAttachedToFlutterEngine
()
{
return
flutterEngine
!=
null
;
}
/**
* Returns the {@link FlutterEngine} to which this {@code FlutterView} is currently attached,
* or null if this {@code FlutterView} is not currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
@Nullable
public
FlutterEngine
getAttachedFlutterEngine
()
{
return
flutterEngine
;
}
/**
* attached to/detaches from a {@link FlutterEngine}.
*/
@VisibleForTesting
public
void
addFlutterEngineAttachmentListener
(
@NonNull
FlutterView
.
FlutterEngineAttachmentListener
listener
)
{
flutterEngineAttachmentListeners
.
add
(
listener
);
}
/**
*/
@VisibleForTesting
public
void
removeFlutterEngineAttachmentListener
(
@NonNull
FlutterView
.
FlutterEngineAttachmentListener
listener
)
{
flutterEngineAttachmentListeners
.
remove
(
listener
);
}
/**
* Send the current {@link Locale} configuration to Flutter.
*
* FlutterEngine must be non-null when this method is invoked.
*/
@SuppressWarnings
(
"deprecation"
)
private
void
sendLocalesToFlutter
(
@NonNull
Configuration
config
)
{
List
<
Locale
>
locales
=
new
ArrayList
<>();
if
(
Build
.
VERSION
.
SDK_INT
>=
android
.
os
.
Build
.
VERSION_CODES
.
N
)
{
LocaleList
localeList
=
config
.
getLocales
();
int
localeCount
=
localeList
.
size
();
for
(
int
index
=
0
;
index
<
localeCount
;
++
index
)
{
Locale
locale
=
localeList
.
get
(
index
);
locales
.
add
(
locale
);
}
}
else
{
locales
.
add
(
config
.
locale
);
}
if
(
flutterEngine
!=
null
&&
flutterEngine
.
getLocalizationChannel
()!=
null
){
flutterEngine
.
getLocalizationChannel
().
sendLocales
(
locales
);
}
}
/**
* Send various user preferences of this Android device to Flutter.
*
* For example, sends the user's "text scale factor" preferences, as well as the user's clock
* format preference.
*
* FlutterEngine must be non-null when this method is invoked.
*/
private
void
sendUserSettingsToFlutter
()
{
if
(
flutterEngine
!=
null
&&
flutterEngine
.
getSettingsChannel
()!=
null
){
flutterEngine
.
getSettingsChannel
().
startMessage
()
.
setTextScaleFactor
(
getResources
().
getConfiguration
().
fontScale
)
.
setUse24HourFormat
(
DateFormat
.
is24HourFormat
(
getContext
()))
.
send
();
}
}
// TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI
private
void
sendViewportMetricsToFlutter
()
{
if
(!
isAttachedToFlutterEngine
())
{
Log
.
w
(
TAG
,
"Tried to send viewport metrics from Android to Flutter but this "
+
"FlutterView was not attached to a FlutterEngine."
);
return
;
}
if
(
viewportMetrics
.
width
==
0
||
viewportMetrics
.
height
==
0
)
return
;
viewportMetrics
.
devicePixelRatio
=
getResources
().
getDisplayMetrics
().
density
;
flutterEngine
.
getRenderer
().
setViewportMetrics
(
viewportMetrics
);
}
}
\ No newline at end of file
android/src/main/java/com/idlefish/flutterboost/XInputConnectionAdaptor.java
deleted
100644 → 0
View file @
ec7c6e0b
package
com.idlefish.flutterboost
;
import
android.content.Context
;
import
android.text.Editable
;
import
android.text.Selection
;
import
android.view.KeyEvent
;
import
android.view.View
;
import
android.view.inputmethod.BaseInputConnection
;
import
android.view.inputmethod.EditorInfo
;
import
android.view.inputmethod.InputMethodManager
;
import
io.flutter.embedding.engine.systemchannels.TextInputChannel
;
import
io.flutter.plugin.common.ErrorLogResult
;
import
io.flutter.plugin.common.MethodChannel
;
import
android.content.Context
;
import
android.text.DynamicLayout
;
import
android.text.Editable
;
import
android.text.Layout
;
import
android.text.Layout.Directions
;
import
android.text.Selection
;
import
android.text.TextPaint
;
import
android.view.KeyEvent
;
import
android.view.View
;
import
android.view.inputmethod.BaseInputConnection
;
import
android.view.inputmethod.EditorInfo
;
import
android.view.inputmethod.InputMethodManager
;
import
io.flutter.embedding.engine.systemchannels.TextInputChannel
;
import
io.flutter.Log
;
import
io.flutter.plugin.common.ErrorLogResult
;
import
io.flutter.plugin.common.MethodChannel
;
class
XInputConnectionAdaptor
extends
BaseInputConnection
{
private
final
View
mFlutterView
;
private
final
int
mClient
;
private
final
TextInputChannel
textInputChannel
;
private
final
Editable
mEditable
;
private
int
mBatchCount
;
private
InputMethodManager
mImm
;
private
final
Layout
mLayout
;
@SuppressWarnings
(
"deprecation"
)
public
XInputConnectionAdaptor
(
View
view
,
int
client
,
TextInputChannel
textInputChannel
,
Editable
editable
)
{
super
(
view
,
true
);
mFlutterView
=
view
;
mClient
=
client
;
this
.
textInputChannel
=
textInputChannel
;
mEditable
=
editable
;
mBatchCount
=
0
;
// We create a dummy Layout with max width so that the selection
// shifting acts as if all text were in one line.
mLayout
=
new
DynamicLayout
(
mEditable
,
new
TextPaint
(),
Integer
.
MAX_VALUE
,
Layout
.
Alignment
.
ALIGN_NORMAL
,
1.0f
,
0.0f
,
false
);
mImm
=
(
InputMethodManager
)
view
.
getContext
().
getSystemService
(
Context
.
INPUT_METHOD_SERVICE
);
}
// Send the current state of the editable to Flutter.
private
void
updateEditingState
()
{
// If the IME is in the middle of a batch edit, then wait until it completes.
if
(
mBatchCount
>
0
)
return
;
int
selectionStart
=
Selection
.
getSelectionStart
(
mEditable
);
int
selectionEnd
=
Selection
.
getSelectionEnd
(
mEditable
);
int
composingStart
=
BaseInputConnection
.
getComposingSpanStart
(
mEditable
);
int
composingEnd
=
BaseInputConnection
.
getComposingSpanEnd
(
mEditable
);
mImm
.
updateSelection
(
mFlutterView
,
selectionStart
,
selectionEnd
,
composingStart
,
composingEnd
);
textInputChannel
.
updateEditingState
(
mClient
,
mEditable
.
toString
(),
selectionStart
,
selectionEnd
,
composingStart
,
composingEnd
);
}
@Override
public
Editable
getEditable
()
{
return
mEditable
;
}
@Override
public
boolean
beginBatchEdit
()
{
mBatchCount
++;
return
super
.
beginBatchEdit
();
}
@Override
public
boolean
endBatchEdit
()
{
boolean
result
=
super
.
endBatchEdit
();
mBatchCount
--;
updateEditingState
();
return
result
;
}
@Override
public
boolean
commitText
(
CharSequence
text
,
int
newCursorPosition
)
{
boolean
result
=
super
.
commitText
(
text
,
newCursorPosition
);
updateEditingState
();
return
result
;
}
@Override
public
boolean
deleteSurroundingText
(
int
beforeLength
,
int
afterLength
)
{
if
(
Selection
.
getSelectionStart
(
mEditable
)
==
-
1
)
return
true
;
boolean
result
=
super
.
deleteSurroundingText
(
beforeLength
,
afterLength
);
updateEditingState
();
return
result
;
}
@Override
public
boolean
setComposingRegion
(
int
start
,
int
end
)
{
boolean
result
=
super
.
setComposingRegion
(
start
,
end
);
updateEditingState
();
return
result
;
}
@Override
public
boolean
setComposingText
(
CharSequence
text
,
int
newCursorPosition
)
{
boolean
result
;
if
(
text
.
length
()
==
0
)
{
result
=
super
.
commitText
(
text
,
newCursorPosition
);
}
else
{
result
=
super
.
setComposingText
(
text
,
newCursorPosition
);
}
updateEditingState
();
return
result
;
}
@Override
public
boolean
setSelection
(
int
start
,
int
end
)
{
boolean
result
=
super
.
setSelection
(
start
,
end
);
updateEditingState
();
return
result
;
}
// Sanitizes the index to ensure the index is within the range of the
// contents of editable.
private
static
int
clampIndexToEditable
(
int
index
,
Editable
editable
)
{
int
clamped
=
Math
.
max
(
0
,
Math
.
min
(
editable
.
length
(),
index
));
if
(
clamped
!=
index
)
{
Log
.
d
(
"flutter"
,
"Text selection index was clamped ("
+
index
+
"->"
+
clamped
+
") to remain in bounds. This may not be your fault, as some keyboards may select outside of bounds."
);
}
return
clamped
;
}
@Override
public
boolean
sendKeyEvent
(
KeyEvent
event
)
{
if
(
event
.
getAction
()
==
KeyEvent
.
ACTION_DOWN
)
{
if
(
event
.
getKeyCode
()
==
KeyEvent
.
KEYCODE_DEL
)
{
int
selStart
=
clampIndexToEditable
(
Selection
.
getSelectionStart
(
mEditable
),
mEditable
);
int
selEnd
=
clampIndexToEditable
(
Selection
.
getSelectionEnd
(
mEditable
),
mEditable
);
if
(
selEnd
>
selStart
)
{
// Delete the selection.
Selection
.
setSelection
(
mEditable
,
selStart
);
mEditable
.
delete
(
selStart
,
selEnd
);
updateEditingState
();
return
true
;
}
else
if
(
selStart
>
0
)
{
// Delete to the left/right of the cursor depending on direction of text.
// TODO(garyq): Explore how to obtain per-character direction. The
// isRTLCharAt() call below is returning blanket direction assumption
// based on the first character in the line.
boolean
isRtl
=
mLayout
.
isRtlCharAt
(
mLayout
.
getLineForOffset
(
selStart
));
try
{
if
(
isRtl
)
{
Selection
.
extendRight
(
mEditable
,
mLayout
);
}
else
{
Selection
.
extendLeft
(
mEditable
,
mLayout
);
}
}
catch
(
IndexOutOfBoundsException
e
)
{
// On some Chinese devices (primarily Huawei, some Xiaomi),
// on initial app startup before focus is lost, the
// Selection.extendLeft and extendRight calls always extend
// from the index of the initial contents of mEditable. This
// try-catch will prevent crashing on Huawei devices by falling
// back to a simple way of deletion, although this a hack and
// will not handle emojis.
Selection
.
setSelection
(
mEditable
,
selStart
,
selStart
-
1
);
}
int
newStart
=
clampIndexToEditable
(
Selection
.
getSelectionStart
(
mEditable
),
mEditable
);
int
newEnd
=
clampIndexToEditable
(
Selection
.
getSelectionEnd
(
mEditable
),
mEditable
);
Selection
.
setSelection
(
mEditable
,
Math
.
min
(
newStart
,
newEnd
));
// Min/Max the values since RTL selections will start at a higher
// index than they end at.
mEditable
.
delete
(
Math
.
min
(
newStart
,
newEnd
),
Math
.
max
(
newStart
,
newEnd
));
updateEditingState
();
return
true
;
}
}
else
if
(
event
.
getKeyCode
()
==
KeyEvent
.
KEYCODE_DPAD_LEFT
)
{
int
selStart
=
Selection
.
getSelectionStart
(
mEditable
);
int
newSel
=
Math
.
max
(
selStart
-
1
,
0
);
setSelection
(
newSel
,
newSel
);
return
true
;
}
else
if
(
event
.
getKeyCode
()
==
KeyEvent
.
KEYCODE_DPAD_RIGHT
)
{
int
selStart
=
Selection
.
getSelectionStart
(
mEditable
);
int
newSel
=
Math
.
min
(
selStart
+
1
,
mEditable
.
length
());
setSelection
(
newSel
,
newSel
);
return
true
;
}
else
{
// Enter a character.
int
character
=
event
.
getUnicodeChar
();
if
(
character
!=
0
)
{
int
selStart
=
Math
.
max
(
0
,
Selection
.
getSelectionStart
(
mEditable
));
int
selEnd
=
Math
.
max
(
0
,
Selection
.
getSelectionEnd
(
mEditable
));
if
(
selEnd
!=
selStart
)
mEditable
.
delete
(
selStart
,
selEnd
);
mEditable
.
insert
(
selStart
,
String
.
valueOf
((
char
)
character
));
setSelection
(
selStart
+
1
,
selStart
+
1
);
}
return
true
;
}
}
return
false
;
}
@Override
public
boolean
performEditorAction
(
int
actionCode
)
{
switch
(
actionCode
)
{
case
EditorInfo
.
IME_ACTION_NONE
:
textInputChannel
.
newline
(
mClient
);
break
;
case
EditorInfo
.
IME_ACTION_UNSPECIFIED
:
textInputChannel
.
unspecifiedAction
(
mClient
);
break
;
case
EditorInfo
.
IME_ACTION_GO
:
textInputChannel
.
go
(
mClient
);
break
;
case
EditorInfo
.
IME_ACTION_SEARCH
:
textInputChannel
.
search
(
mClient
);
break
;
case
EditorInfo
.
IME_ACTION_SEND
:
textInputChannel
.
send
(
mClient
);
break
;
case
EditorInfo
.
IME_ACTION_NEXT
:
textInputChannel
.
next
(
mClient
);
break
;
case
EditorInfo
.
IME_ACTION_PREVIOUS
:
textInputChannel
.
previous
(
mClient
);
break
;
default
:
case
EditorInfo
.
IME_ACTION_DONE
:
textInputChannel
.
done
(
mClient
);
break
;
}
return
true
;
}
}
\ No newline at end of file
android/src/main/java/com/idlefish/flutterboost/XPlatformPlugin.java
deleted
100644 → 0
View file @
ec7c6e0b
package
com.idlefish.flutterboost
;
import
android.app.Activity
;
import
android.app.ActivityManager
;
import
android.content.ClipData
;
import
android.content.ClipboardManager
;
import
android.content.Context
;
import
android.graphics.Rect
;
import
android.os.Build
;
import
android.support.annotation.Nullable
;
import
android.support.annotation.NonNull
;
import
android.view.HapticFeedbackConstants
;
import
android.view.SoundEffectConstants
;
import
android.view.View
;
import
android.view.Window
;
import
java.util.ArrayList
;
import
java.util.List
;
import
io.flutter.embedding.engine.systemchannels.PlatformChannel
;
public
class
XPlatformPlugin
{
public
static
final
int
DEFAULT_SYSTEM_UI
=
View
.
SYSTEM_UI_FLAG_LAYOUT_STABLE
|
View
.
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
;
private
Activity
activity
;
private
PlatformChannel
platformChannel
;
private
PlatformChannel
.
SystemChromeStyle
currentTheme
;
private
int
mEnabledOverlays
;
private
PlatformChannel
.
PlatformMessageHandler
mPlatformMessageHandler
=
new
PlatformChannel
.
PlatformMessageHandler
()
{
@Override
public
void
playSystemSound
(
@NonNull
PlatformChannel
.
SoundType
soundType
)
{
XPlatformPlugin
.
this
.
playSystemSound
(
soundType
);
}
@Override
public
void
vibrateHapticFeedback
(
@NonNull
PlatformChannel
.
HapticFeedbackType
feedbackType
)
{
XPlatformPlugin
.
this
.
vibrateHapticFeedback
(
feedbackType
);
}
@Override
public
void
setPreferredOrientations
(
int
androidOrientation
)
{
setSystemChromePreferredOrientations
(
androidOrientation
);
}
@Override
public
void
setApplicationSwitcherDescription
(
@NonNull
PlatformChannel
.
AppSwitcherDescription
description
)
{
setSystemChromeApplicationSwitcherDescription
(
description
);
}
@Override
public
void
showSystemOverlays
(
@NonNull
List
<
PlatformChannel
.
SystemUiOverlay
>
overlays
)
{
setSystemChromeEnabledSystemUIOverlays
(
overlays
);
}
@Override
public
void
restoreSystemUiOverlays
()
{
restoreSystemChromeSystemUIOverlays
();
}
@Override
public
void
setSystemUiOverlayStyle
(
@NonNull
PlatformChannel
.
SystemChromeStyle
systemUiOverlayStyle
)
{
setSystemChromeSystemUIOverlayStyle
(
systemUiOverlayStyle
);
}
@Override
public
void
popSystemNavigator
()
{
XPlatformPlugin
.
this
.
popSystemNavigator
();
}
@Override
public
CharSequence
getClipboardData
(
@Nullable
PlatformChannel
.
ClipboardContentFormat
format
)
{
return
XPlatformPlugin
.
this
.
getClipboardData
(
format
);
}
@Override
public
void
setClipboardData
(
@NonNull
String
text
)
{
XPlatformPlugin
.
this
.
setClipboardData
(
text
);
}
@Override
public
List
<
Rect
>
getSystemGestureExclusionRects
()
{
return
XPlatformPlugin
.
this
.
getSystemGestureExclusionRects
();
}
@Override
public
void
setSystemGestureExclusionRects
(
@NonNull
ArrayList
<
Rect
>
rects
)
{
XPlatformPlugin
.
this
.
setSystemGestureExclusionRects
(
rects
);
}
};
public
XPlatformPlugin
(
PlatformChannel
platformChannel
)
{
this
.
platformChannel
=
platformChannel
;
this
.
platformChannel
.
setPlatformMessageHandler
(
mPlatformMessageHandler
);
mEnabledOverlays
=
DEFAULT_SYSTEM_UI
;
}
public
void
attachToActivity
(
Activity
activity
){
this
.
activity
=
activity
;
}
/**
* Releases all resources held by this {@code PlatformPlugin}.
* <p>
* Do not invoke any methods on a {@code PlatformPlugin} after invoking this method.
*/
public
void
detachActivity
(
Activity
activity
)
{
if
(
activity
==
this
.
activity
)
{
this
.
activity
=
null
;
}
}
private
void
playSystemSound
(
PlatformChannel
.
SoundType
soundType
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
if
(
soundType
==
PlatformChannel
.
SoundType
.
CLICK
)
{
View
view
=
getActivity
().
getWindow
().
getDecorView
();
view
.
playSoundEffect
(
SoundEffectConstants
.
CLICK
);
}
}
private
void
vibrateHapticFeedback
(
PlatformChannel
.
HapticFeedbackType
feedbackType
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
View
view
=
getActivity
().
getWindow
().
getDecorView
();
switch
(
feedbackType
)
{
case
STANDARD:
view
.
performHapticFeedback
(
HapticFeedbackConstants
.
LONG_PRESS
);
break
;
case
LIGHT_IMPACT:
view
.
performHapticFeedback
(
HapticFeedbackConstants
.
VIRTUAL_KEY
);
break
;
case
MEDIUM_IMPACT:
view
.
performHapticFeedback
(
HapticFeedbackConstants
.
KEYBOARD_TAP
);
break
;
case
HEAVY_IMPACT:
// HapticFeedbackConstants.CONTEXT_CLICK from API level 23.
view
.
performHapticFeedback
(
6
);
break
;
case
SELECTION_CLICK:
view
.
performHapticFeedback
(
HapticFeedbackConstants
.
CLOCK_TICK
);
break
;
}
}
private
void
setSystemChromePreferredOrientations
(
int
androidOrientation
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
getActivity
().
setRequestedOrientation
(
androidOrientation
);
}
@SuppressWarnings
(
"deprecation"
)
private
void
setSystemChromeApplicationSwitcherDescription
(
PlatformChannel
.
AppSwitcherDescription
description
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
LOLLIPOP
)
{
return
;
}
// Linter refuses to believe we're only executing this code in API 28 unless we use distinct if blocks and
// hardcode the API 28 constant.
if
(
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
P
&&
Build
.
VERSION
.
SDK_INT
>
Build
.
VERSION_CODES
.
LOLLIPOP
)
{
getActivity
().
setTaskDescription
(
new
ActivityManager
.
TaskDescription
(
description
.
label
,
/*icon=*/
null
,
description
.
color
));
}
if
(
Build
.
VERSION
.
SDK_INT
>=
28
)
{
ActivityManager
.
TaskDescription
taskDescription
=
new
ActivityManager
.
TaskDescription
(
description
.
label
,
0
,
description
.
color
);
getActivity
().
setTaskDescription
(
taskDescription
);
}
}
private
void
setSystemChromeEnabledSystemUIOverlays
(
List
<
PlatformChannel
.
SystemUiOverlay
>
overlaysToShow
)
{
// Start by assuming we want to hide all system overlays (like an immersive game).
int
enabledOverlays
=
DEFAULT_SYSTEM_UI
|
View
.
SYSTEM_UI_FLAG_FULLSCREEN
|
View
.
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
View
.
SYSTEM_UI_FLAG_HIDE_NAVIGATION
;
if
(
overlaysToShow
.
size
()
==
0
)
{
enabledOverlays
|=
View
.
SYSTEM_UI_FLAG_IMMERSIVE_STICKY
;
}
// Re-add any desired system overlays.
for
(
int
i
=
0
;
i
<
overlaysToShow
.
size
();
++
i
)
{
PlatformChannel
.
SystemUiOverlay
overlayToShow
=
overlaysToShow
.
get
(
i
);
switch
(
overlayToShow
)
{
case
TOP_OVERLAYS:
enabledOverlays
&=
~
View
.
SYSTEM_UI_FLAG_FULLSCREEN
;
break
;
case
BOTTOM_OVERLAYS:
enabledOverlays
&=
~
View
.
SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
;
enabledOverlays
&=
~
View
.
SYSTEM_UI_FLAG_HIDE_NAVIGATION
;
break
;
}
}
mEnabledOverlays
=
enabledOverlays
;
updateSystemUiOverlays
();
}
/**
* Refreshes Android's window system UI (AKA system chrome) to match Flutter's desired
* {@link PlatformChannel.SystemChromeStyle}.
* <p>
* Updating the system UI Overlays is accomplished by altering the decor view of the
* {@link Window} associated with the {@link Activity} that was provided to this
* {@code PlatformPlugin}.
*/
public
void
updateSystemUiOverlays
(){
if
(
getActivity
()
==
null
)
{
return
;
}
getActivity
().
getWindow
().
getDecorView
().
setSystemUiVisibility
(
mEnabledOverlays
);
if
(
currentTheme
!=
null
)
{
setSystemChromeSystemUIOverlayStyle
(
currentTheme
);
}
}
private
void
restoreSystemChromeSystemUIOverlays
()
{
updateSystemUiOverlays
();
}
private
void
setSystemChromeSystemUIOverlayStyle
(
PlatformChannel
.
SystemChromeStyle
systemChromeStyle
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
Window
window
=
getActivity
().
getWindow
();
View
view
=
window
.
getDecorView
();
int
flags
=
view
.
getSystemUiVisibility
();
// You can change the navigation bar color (including translucent colors)
// in Android, but you can't change the color of the navigation buttons until Android O.
// LIGHT vs DARK effectively isn't supported until then.
// Build.VERSION_CODES.O
if
(
Build
.
VERSION
.
SDK_INT
>=
26
)
{
if
(
systemChromeStyle
.
systemNavigationBarIconBrightness
!=
null
)
{
switch
(
systemChromeStyle
.
systemNavigationBarIconBrightness
)
{
case
DARK:
//View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
flags
|=
0x10
;
break
;
case
LIGHT:
flags
&=
~
0x10
;
break
;
}
}
if
(
systemChromeStyle
.
systemNavigationBarColor
!=
null
)
{
window
.
setNavigationBarColor
(
systemChromeStyle
.
systemNavigationBarColor
);
}
}
// Build.VERSION_CODES.M
if
(
Build
.
VERSION
.
SDK_INT
>=
23
)
{
if
(
systemChromeStyle
.
statusBarIconBrightness
!=
null
)
{
switch
(
systemChromeStyle
.
statusBarIconBrightness
)
{
case
DARK:
// View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
flags
|=
0x2000
;
break
;
case
LIGHT:
flags
&=
~
0x2000
;
break
;
}
}
if
(
systemChromeStyle
.
statusBarColor
!=
null
)
{
window
.
setStatusBarColor
(
systemChromeStyle
.
statusBarColor
);
}
}
if
(
systemChromeStyle
.
systemNavigationBarDividerColor
!=
null
)
{
// Not available until Android P.
// window.setNavigationBarDividerColor(systemNavigationBarDividerColor);
}
view
.
setSystemUiVisibility
(
flags
);
currentTheme
=
systemChromeStyle
;
}
private
void
popSystemNavigator
()
{
if
(
getActivity
()
==
null
)
{
return
;
}
getActivity
().
finish
();
}
private
CharSequence
getClipboardData
(
PlatformChannel
.
ClipboardContentFormat
format
)
{
if
(
getActivity
()
==
null
)
{
return
null
;
}
ClipboardManager
clipboard
=
(
ClipboardManager
)
getActivity
().
getSystemService
(
Context
.
CLIPBOARD_SERVICE
);
ClipData
clip
=
clipboard
.
getPrimaryClip
();
if
(
clip
==
null
)
return
null
;
if
(
format
==
null
||
format
==
PlatformChannel
.
ClipboardContentFormat
.
PLAIN_TEXT
)
{
return
clip
.
getItemAt
(
0
).
coerceToText
(
getActivity
());
}
return
null
;
}
private
void
setClipboardData
(
String
text
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
ClipboardManager
clipboard
=
(
ClipboardManager
)
getActivity
().
getSystemService
(
Context
.
CLIPBOARD_SERVICE
);
ClipData
clip
=
ClipData
.
newPlainText
(
"text label?"
,
text
);
clipboard
.
setPrimaryClip
(
clip
);
}
private
List
<
Rect
>
getSystemGestureExclusionRects
()
{
if
(
getActivity
()
==
null
)
{
return
null
;
}
if
(
Build
.
VERSION
.
SDK_INT
>=
29
)
{
Window
window
=
getActivity
().
getWindow
();
View
view
=
window
.
getDecorView
();
return
view
.
getSystemGestureExclusionRects
();
}
return
null
;
}
private
void
setSystemGestureExclusionRects
(
ArrayList
<
Rect
>
rects
)
{
if
(
getActivity
()
==
null
)
{
return
;
}
if
(
Build
.
VERSION
.
SDK_INT
<
29
)
{
return
;
}
Window
window
=
getActivity
().
getWindow
();
View
view
=
window
.
getDecorView
();
view
.
setSystemGestureExclusionRects
(
rects
);
}
@Nullable
private
Activity
getActivity
()
{
if
(
activity
!=
null
)
{
return
activity
;
}
return
FlutterBoost
.
instance
().
currentActivity
();
}
}
android/src/main/java/com/idlefish/flutterboost/XTextInputPlugin.java
deleted
100644 → 0
View file @
ec7c6e0b
// 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
com.idlefish.flutterboost
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.os.Build
;
import
android.provider.Settings
;
import
android.support.annotation.NonNull
;
import
android.support.annotation.Nullable
;
import
android.support.annotation.VisibleForTesting
;
import
android.text.Editable
;
import
android.text.InputType
;
import
android.text.Selection
;
import
android.view.View
;
import
android.view.inputmethod.BaseInputConnection
;
import
android.view.inputmethod.EditorInfo
;
import
android.view.inputmethod.InputConnection
;
import
android.view.inputmethod.InputMethodManager
;
import
android.view.inputmethod.InputMethodSubtype
;
import
io.flutter.embedding.engine.dart.DartExecutor
;
import
io.flutter.embedding.engine.systemchannels.TextInputChannel
;
import
io.flutter.plugin.platform.PlatformViewsController
;
/**
* Android implementation of the text input plugin.
*/
public
class
XTextInputPlugin
{
@NonNull
private
View
mView
;
@NonNull
private
InputMethodManager
mImm
;
@NonNull
private
TextInputChannel
textInputChannel
;
@NonNull
private
InputTarget
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
NO_TARGET
,
0
);
@Nullable
private
TextInputChannel
.
Configuration
configuration
;
@Nullable
private
Editable
mEditable
;
private
boolean
mRestartInputPending
;
@Nullable
private
InputConnection
lastInputConnection
;
@NonNull
private
PlatformViewsController
platformViewsController
;
private
boolean
restartAlwaysRequired
;
// When true following calls to createInputConnection will return the cached lastInputConnection if the input
// target is a platform view. See the comments on lockPlatformViewInputConnection for more details.
private
boolean
isInputConnectionLocked
;
private
static
XTextInputPlugin
xTextInputPlugin
;
public
static
XTextInputPlugin
getTextInputPlugin
(
DartExecutor
dartExecutor
,
@NonNull
PlatformViewsController
platformViewsController
){
if
(
xTextInputPlugin
!=
null
)
return
xTextInputPlugin
;
xTextInputPlugin
=
new
XTextInputPlugin
(
dartExecutor
,
platformViewsController
);
return
xTextInputPlugin
;
}
public
XTextInputPlugin
(
@NonNull
DartExecutor
dartExecutor
,
@NonNull
PlatformViewsController
platformViewsController
)
{
textInputChannel
=
new
TextInputChannel
(
dartExecutor
);
textInputChannel
.
requestExistingInputState
();
this
.
platformViewsController
=
platformViewsController
;
// this.platformViewsController.attachTextInputPlugin(this);
}
public
void
release
(
View
v
){
if
(
mView
!=
null
&&
mView
.
hashCode
()==
v
.
hashCode
()){
mView
=
null
;
}
}
public
void
updateView
(
View
view
){
mView
=
view
;
mImm
=
(
InputMethodManager
)
view
.
getContext
().
getSystemService
(
Context
.
INPUT_METHOD_SERVICE
);
textInputChannel
.
setTextInputMethodHandler
(
new
TextInputChannel
.
TextInputMethodHandler
()
{
@Override
public
void
show
()
{
showTextInput
(
mView
);
}
@Override
public
void
hide
()
{
hideTextInput
(
mView
);
}
@Override
public
void
setClient
(
int
textInputClientId
,
TextInputChannel
.
Configuration
configuration
)
{
setTextInputClient
(
textInputClientId
,
configuration
);
}
@Override
public
void
setPlatformViewClient
(
int
platformViewId
)
{
setPlatformViewTextInputClient
(
platformViewId
);
}
@Override
public
void
setEditingState
(
TextInputChannel
.
TextEditState
editingState
)
{
setTextInputEditingState
(
mView
,
editingState
);
}
@Override
public
void
clearClient
()
{
clearTextInputClient
();
}
});
restartAlwaysRequired
=
isRestartAlwaysRequired
();
}
@NonNull
public
InputMethodManager
getInputMethodManager
()
{
return
mImm
;
}
/***
* Use the current platform view input connection until unlockPlatformViewInputConnection is called.
*
* The current input connection instance is cached and any following call to @{link createInputConnection} returns
* the cached connection until unlockPlatformViewInputConnection is called.
*
* This is a no-op if the current input target isn't a platform view.
*
* This is used to preserve an input connection when moving a platform view from one virtual display to another.
*/
public
void
lockPlatformViewInputConnection
()
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
)
{
isInputConnectionLocked
=
true
;
}
}
/**
* Unlocks the input connection.
*
* See also: @{link lockPlatformViewInputConnection}.
*/
public
void
unlockPlatformViewInputConnection
()
{
isInputConnectionLocked
=
false
;
}
/**
* Detaches the text input plugin from the platform views controller.
*
* The TextInputPlugin instance should not be used after calling this.
*/
public
void
destroy
()
{
platformViewsController
.
detachTextInputPlugin
();
}
private
static
int
inputTypeFromTextInputType
(
TextInputChannel
.
InputType
type
,
boolean
obscureText
,
boolean
autocorrect
,
boolean
enableSuggestions
,
TextInputChannel
.
TextCapitalization
textCapitalization
)
{
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
DATETIME
)
{
return
InputType
.
TYPE_CLASS_DATETIME
;
}
else
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
NUMBER
)
{
int
textType
=
InputType
.
TYPE_CLASS_NUMBER
;
if
(
type
.
isSigned
)
{
textType
|=
InputType
.
TYPE_NUMBER_FLAG_SIGNED
;
}
if
(
type
.
isDecimal
)
{
textType
|=
InputType
.
TYPE_NUMBER_FLAG_DECIMAL
;
}
return
textType
;
}
else
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
PHONE
)
{
return
InputType
.
TYPE_CLASS_PHONE
;
}
int
textType
=
InputType
.
TYPE_CLASS_TEXT
;
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
MULTILINE
)
{
textType
|=
InputType
.
TYPE_TEXT_FLAG_MULTI_LINE
;
}
else
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
EMAIL_ADDRESS
)
{
textType
|=
InputType
.
TYPE_TEXT_VARIATION_EMAIL_ADDRESS
;
}
else
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
URL
)
{
textType
|=
InputType
.
TYPE_TEXT_VARIATION_URI
;
}
else
if
(
type
.
type
==
TextInputChannel
.
TextInputType
.
VISIBLE_PASSWORD
)
{
textType
|=
InputType
.
TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
;
}
if
(
obscureText
)
{
// Note: both required. Some devices ignore TYPE_TEXT_FLAG_NO_SUGGESTIONS.
textType
|=
InputType
.
TYPE_TEXT_FLAG_NO_SUGGESTIONS
;
textType
|=
InputType
.
TYPE_TEXT_VARIATION_PASSWORD
;
}
else
{
if
(
autocorrect
)
textType
|=
InputType
.
TYPE_TEXT_FLAG_AUTO_CORRECT
;
if
(!
enableSuggestions
)
textType
|=
InputType
.
TYPE_TEXT_FLAG_NO_SUGGESTIONS
;
}
if
(
textCapitalization
==
TextInputChannel
.
TextCapitalization
.
CHARACTERS
)
{
textType
|=
InputType
.
TYPE_TEXT_FLAG_CAP_CHARACTERS
;
}
else
if
(
textCapitalization
==
TextInputChannel
.
TextCapitalization
.
WORDS
)
{
textType
|=
InputType
.
TYPE_TEXT_FLAG_CAP_WORDS
;
}
else
if
(
textCapitalization
==
TextInputChannel
.
TextCapitalization
.
SENTENCES
)
{
textType
|=
InputType
.
TYPE_TEXT_FLAG_CAP_SENTENCES
;
}
return
textType
;
}
public
InputConnection
createInputConnection
(
View
view
,
EditorInfo
outAttrs
)
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
NO_TARGET
)
{
lastInputConnection
=
null
;
return
null
;
}
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
)
{
if
(
isInputConnectionLocked
)
{
return
lastInputConnection
;
}
lastInputConnection
=
platformViewsController
.
getPlatformViewById
(
inputTarget
.
id
).
onCreateInputConnection
(
outAttrs
);
return
lastInputConnection
;
}
outAttrs
.
inputType
=
inputTypeFromTextInputType
(
configuration
.
inputType
,
configuration
.
obscureText
,
configuration
.
autocorrect
,
configuration
.
enableSuggestions
,
configuration
.
textCapitalization
);
outAttrs
.
imeOptions
=
EditorInfo
.
IME_FLAG_NO_FULLSCREEN
;
int
enterAction
;
if
(
configuration
.
inputAction
==
null
)
{
// If an explicit input action isn't set, then default to none for multi-line fields
// and done for single line fields.
enterAction
=
(
InputType
.
TYPE_TEXT_FLAG_MULTI_LINE
&
outAttrs
.
inputType
)
!=
0
?
EditorInfo
.
IME_ACTION_NONE
:
EditorInfo
.
IME_ACTION_DONE
;
}
else
{
enterAction
=
configuration
.
inputAction
;
}
if
(
configuration
.
actionLabel
!=
null
)
{
outAttrs
.
actionLabel
=
configuration
.
actionLabel
;
outAttrs
.
actionId
=
enterAction
;
}
outAttrs
.
imeOptions
|=
enterAction
;
XInputConnectionAdaptor
connection
=
new
XInputConnectionAdaptor
(
view
,
inputTarget
.
id
,
textInputChannel
,
mEditable
);
outAttrs
.
initialSelStart
=
Selection
.
getSelectionStart
(
mEditable
);
outAttrs
.
initialSelEnd
=
Selection
.
getSelectionEnd
(
mEditable
);
lastInputConnection
=
connection
;
return
lastInputConnection
;
}
@Nullable
public
InputConnection
getLastInputConnection
()
{
return
lastInputConnection
;
}
/**
* Clears a platform view text input client if it is the current input target.
*
* This is called when a platform view is disposed to make sure we're not hanging to a stale input
* connection.
*/
public
void
clearPlatformViewClient
(
int
platformViewId
)
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
&&
inputTarget
.
id
==
platformViewId
)
{
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
NO_TARGET
,
0
);
hideTextInput
(
mView
);
mImm
.
restartInput
(
mView
);
mRestartInputPending
=
false
;
}
}
private
void
showTextInput
(
View
view
)
{
view
.
requestFocus
();
mImm
.
showSoftInput
(
view
,
0
);
}
private
void
hideTextInput
(
View
view
)
{
// Note: a race condition may lead to us hiding the keyboard here just after a platform view has shown it.
// This can only potentially happen when switching focus from a Flutter text field to a platform view's text
// field(by text field here I mean anything that keeps the keyboard open).
// See: https://github.com/flutter/flutter/issues/34169
mImm
.
hideSoftInputFromWindow
(
view
.
getApplicationWindowToken
(),
0
);
}
@VisibleForTesting
void
setTextInputClient
(
int
client
,
TextInputChannel
.
Configuration
configuration
)
{
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
FRAMEWORK_CLIENT
,
client
);
this
.
configuration
=
configuration
;
mEditable
=
Editable
.
Factory
.
getInstance
().
newEditable
(
""
);
// setTextInputClient will be followed by a call to setTextInputEditingState.
// Do a restartInput at that time.
mRestartInputPending
=
true
;
unlockPlatformViewInputConnection
();
}
private
void
setPlatformViewTextInputClient
(
int
platformViewId
)
{
// We need to make sure that the Flutter view is focused so that no imm operations get short circuited.
// Not asking for focus here specifically manifested in a but on API 28 devices where the platform view's
// request to show a keyboard was ignored.
mView
.
requestFocus
();
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
PLATFORM_VIEW
,
platformViewId
);
mImm
.
restartInput
(
mView
);
mRestartInputPending
=
false
;
}
private
void
applyStateToSelection
(
TextInputChannel
.
TextEditState
state
)
{
int
selStart
=
state
.
selectionStart
;
int
selEnd
=
state
.
selectionEnd
;
if
(
selStart
>=
0
&&
selStart
<=
mEditable
.
length
()
&&
selEnd
>=
0
&&
selEnd
<=
mEditable
.
length
())
{
Selection
.
setSelection
(
mEditable
,
selStart
,
selEnd
);
}
else
{
Selection
.
removeSelection
(
mEditable
);
}
}
@VisibleForTesting
void
setTextInputEditingState
(
View
view
,
TextInputChannel
.
TextEditState
state
)
{
if
(!
restartAlwaysRequired
&&
!
mRestartInputPending
&&
state
.
text
.
equals
(
mEditable
.
toString
()))
{
applyStateToSelection
(
state
);
mImm
.
updateSelection
(
mView
,
Math
.
max
(
Selection
.
getSelectionStart
(
mEditable
),
0
),
Math
.
max
(
Selection
.
getSelectionEnd
(
mEditable
),
0
),
BaseInputConnection
.
getComposingSpanStart
(
mEditable
),
BaseInputConnection
.
getComposingSpanEnd
(
mEditable
));
}
else
{
mEditable
.
replace
(
0
,
mEditable
.
length
(),
state
.
text
);
applyStateToSelection
(
state
);
mImm
.
restartInput
(
view
);
mRestartInputPending
=
false
;
}
}
// Samsung's Korean keyboard has a bug where it always attempts to combine characters based on
// its internal state, ignoring if and when the cursor is moved programmatically. The same bug
// also causes non-korean keyboards to occasionally duplicate text when tapping in the middle
// of existing text to edit it.
//
// Fully restarting the IMM works around this because it flushes the keyboard's internal state
// and stops it from trying to incorrectly combine characters. However this also has some
// negative performance implications, so we don't want to apply this workaround in every case.
@SuppressLint
(
"NewApi"
)
// New API guard is inline, the linter can't see it.
@SuppressWarnings
(
"deprecation"
)
private
boolean
isRestartAlwaysRequired
()
{
InputMethodSubtype
subtype
=
mImm
.
getCurrentInputMethodSubtype
();
// Impacted devices all shipped with Android Lollipop or newer.
if
(
subtype
==
null
||
Build
.
VERSION
.
SDK_INT
<
Build
.
VERSION_CODES
.
LOLLIPOP
||
!
Build
.
MANUFACTURER
.
equals
(
"samsung"
))
{
return
false
;
}
String
keyboardName
=
Settings
.
Secure
.
getString
(
mView
.
getContext
().
getContentResolver
(),
Settings
.
Secure
.
DEFAULT_INPUT_METHOD
);
// The Samsung keyboard is called "com.sec.android.inputmethod/.SamsungKeypad" but look
// for "Samsung" just in case Samsung changes the name of the keyboard.
if
(
keyboardName
==
null
)
return
false
;
return
keyboardName
.
contains
(
"Samsung"
);
}
private
void
clearTextInputClient
()
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
)
{
// Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node
// that lost focus may be notified before or after a node that gained focus.
// When moving the focus from a Flutter text field to an AndroidView, it is possible that the Flutter text
// field's focus node will be notified that it lost focus after the AndroidView was notified that it gained
// focus. When this happens the text field will send a clearTextInput command which we ignore.
// By doing this we prevent the framework from clearing a platform view input client(the only way to do so
// is to set a new framework text client). I don't see an obvious use case for "clearing" a platform views
// text input client, and it may be error prone as we don't know how the platform view manages the input
// connection and we probably shouldn't interfere.
// If we ever want to allow the framework to clear a platform view text client we should probably consider
// changing the focus manager such that focus nodes that lost focus are notified before focus nodes that
// gained focus as part of the same focus event.
return
;
}
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
NO_TARGET
,
0
);
unlockPlatformViewInputConnection
();
}
static
private
class
InputTarget
{
enum
Type
{
NO_TARGET
,
// InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter framework.
FRAMEWORK_CLIENT
,
// InputConnection is managed by an embedded platform view.
PLATFORM_VIEW
}
public
InputTarget
(
@NonNull
Type
type
,
int
id
)
{
this
.
type
=
type
;
this
.
id
=
id
;
}
@NonNull
Type
type
;
// The ID of the input target.
//
// For framework clients this is the framework input connection client ID.
// For platform views this is the platform view's ID.
int
id
;
}
}
android/src/main/java/com/idlefish/flutterboost/containers/BoostFlutterActivity.java
View file @
3a7185d0
...
...
@@ -2,9 +2,9 @@ package com.idlefish.flutterboost.containers;
import
android.app.Activity
;
import
android
.arch
.lifecycle.Lifecycle
;
import
android
.arch
.lifecycle.LifecycleOwner
;
import
android
.arch
.lifecycle.LifecycleRegistry
;
import
android
x
.lifecycle.Lifecycle
;
import
android
x
.lifecycle.LifecycleOwner
;
import
android
x
.lifecycle.LifecycleRegistry
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.content.pm.ActivityInfo
;
...
...
@@ -15,13 +15,11 @@ import android.graphics.drawable.ColorDrawable;
import
android.graphics.drawable.Drawable
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android
.support
.annotation.NonNull
;
import
android
.support
.annotation.Nullable
;
import
android
x
.annotation.NonNull
;
import
android
x
.annotation.Nullable
;
import
android.view.*
;
import
android.widget.*
;
import
com.idlefish.flutterboost.FlutterBoost
;
import
com.idlefish.flutterboost.XFlutterView
;
import
com.idlefish.flutterboost.XPlatformPlugin
;
import
com.idlefish.flutterboost.interfaces.IFlutterViewContainer
;
import
io.flutter.Log
;
...
...
@@ -58,8 +56,6 @@ public class BoostFlutterActivity extends Activity
// Default configuration.
protected
static
final
String
DEFAULT_BACKGROUND_MODE
=
BackgroundMode
.
opaque
.
name
();
private
static
XPlatformPlugin
sXPlatformPlugin
;
public
static
Intent
createDefaultIntent
(
@NonNull
Context
launchContext
)
{
return
withNewEngine
().
build
(
launchContext
);
}
...
...
@@ -247,7 +243,7 @@ public class BoostFlutterActivity extends Activity
}
protected
X
FlutterView
getFlutterView
()
{
protected
FlutterView
getFlutterView
()
{
return
delegate
.
getFlutterView
();
}
...
...
@@ -445,9 +441,13 @@ public class BoostFlutterActivity extends Activity
}
@Nullable
@Override
public
XPlatformPlugin
providePlatformPlugin
(
@NonNull
FlutterEngine
flutterEngine
)
{
return
BoostViewUtils
.
getPlatformPlugin
(
flutterEngine
.
getPlatformChannel
());
public
PlatformPlugin
providePlatformPlugin
(
@Nullable
Activity
activity
,
@NonNull
FlutterEngine
flutterEngine
)
{
if
(
activity
!=
null
)
{
return
new
PlatformPlugin
(
getActivity
(),
flutterEngine
.
getPlatformChannel
());
}
else
{
return
null
;
}
}
/**
...
...
android/src/main/java/com/idlefish/flutterboost/containers/BoostViewUtils.java
deleted
100644 → 0
View file @
ec7c6e0b
package
com.idlefish.flutterboost.containers
;
import
com.idlefish.flutterboost.XPlatformPlugin
;
import
io.flutter.embedding.engine.systemchannels.PlatformChannel
;
class
BoostViewUtils
{
private
static
volatile
XPlatformPlugin
mInstance
;
private
BoostViewUtils
()
{
}
public
static
XPlatformPlugin
getPlatformPlugin
(
PlatformChannel
channel
)
{
if
(
mInstance
==
null
)
{
synchronized
(
BoostViewUtils
.
class
)
{
if
(
mInstance
==
null
)
{
mInstance
=
new
XPlatformPlugin
(
channel
);
}
}
}
return
mInstance
;
}
}
android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java
View file @
3a7185d0
...
...
@@ -2,15 +2,14 @@ package com.idlefish.flutterboost.containers;
import
android.annotation.SuppressLint
;
import
android.app.Activity
;
import
android
.arch
.lifecycle.Lifecycle
;
import
android
x
.lifecycle.Lifecycle
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.graphics.Color
;
import
android.graphics.PixelFormat
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android
.support
.annotation.NonNull
;
import
android
.support
.annotation.Nullable
;
import
android
x
.annotation.NonNull
;
import
android
x
.annotation.Nullable
;
import
android.view.*
;
import
java.io.Serializable
;
...
...
@@ -21,9 +20,6 @@ import java.util.Map;
import
com.idlefish.flutterboost.FlutterBoost
;
import
com.idlefish.flutterboost.Utils
;
import
com.idlefish.flutterboost.XFlutterView
;
import
com.idlefish.flutterboost.XPlatformPlugin
;
import
com.idlefish.flutterboost.interfaces.IContainerRecord
;
import
com.idlefish.flutterboost.interfaces.IFlutterViewContainer
;
import
com.idlefish.flutterboost.interfaces.IOperateSyncer
;
import
io.flutter.Log
;
...
...
@@ -48,9 +44,9 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
@Nullable
private
FlutterSplashView
flutterSplashView
;
@Nullable
private
X
FlutterView
flutterView
;
private
FlutterView
flutterView
;
@Nullable
private
X
PlatformPlugin
platformPlugin
;
private
PlatformPlugin
platformPlugin
;
private
boolean
isFlutterEngineFromHost
;
...
...
@@ -75,7 +71,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
return
flutterEngine
;
}
public
X
FlutterView
getFlutterView
()
{
public
FlutterView
getFlutterView
()
{
return
flutterView
;
}
...
...
@@ -96,7 +92,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
// TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes
// control of the entire window. This is unacceptable for non-fullscreen
// use-cases.
platformPlugin
=
host
.
providePlatformPlugin
(
flutterEngine
);
platformPlugin
=
host
.
providePlatformPlugin
(
host
.
getActivity
(),
flutterEngine
);
host
.
configureFlutterEngine
(
flutterEngine
);
...
...
@@ -133,7 +129,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
mSyncer
=
FlutterBoost
.
instance
().
containerManager
().
generateSyncer
(
this
);
ensureAlive
();
flutterView
=
new
X
FlutterView
(
host
.
getActivity
(),
FlutterBoost
.
instance
().
platform
().
renderMode
(),
host
.
getTransparencyMode
());
flutterView
=
new
FlutterView
(
host
.
getActivity
(),
FlutterBoost
.
instance
().
platform
().
renderMode
(),
host
.
getTransparencyMode
());
flutterSplashView
=
new
FlutterSplashView
(
host
.
getContext
());
...
...
@@ -183,17 +179,22 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
}
if
(
platformPlugin
!=
null
)
platformPlugin
.
attachToActivity
(
host
.
getActivity
());
}
public
void
onPostResume
()
{
void
onPostResume
()
{
Log
.
v
(
TAG
,
"onPostResume()"
);
ensureAlive
();
if
(
flutterEngine
!=
null
)
{
if
(
platformPlugin
!=
null
)
{
platformPlugin
.
updateSystemUiOverlays
();
}
}
else
{
Log
.
w
(
TAG
,
"onPostResume() invoked before FlutterFragment was attached to an Activity."
);
}
}
...
...
@@ -219,22 +220,17 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
ensureAlive
();
flutterView
.
release
();
//
flutterView.release();
}
public
void
onDetach
()
{
Log
.
v
(
TAG
,
"onDetach()"
);
ensureAlive
();
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
// and this Fragment's Activity.
if
(
platformPlugin
!=
null
)
{
platformPlugin
.
de
tachActivity
(
getContextActivity
()
);
platformPlugin
.
de
stroy
(
);
platformPlugin
=
null
;
}
if
(
ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE
!=
0
&&
ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE
==
this
.
host
.
getActivity
().
hashCode
()){
flutterEngine
.
getActivityControlSurface
().
detachFromActivityForConfigChanges
();
...
...
@@ -244,11 +240,22 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
}
// public void onBackPressed() {
// ensureAlive();
// if (flutterEngine != null) {
// Log.v(TAG, "Forwarding onBackPressed() to FlutterEngine.");
// flutterEngine.getNavigationChannel().popRoute();
// } else {
// Log.w(TAG, "Invoked onBackPressed() before FlutterFragment was attached to an Activity.");
// }
// }
public
void
onBackPressed
()
{
mSyncer
.
onBackPressed
();
ensureAlive
();
}
...
...
@@ -466,9 +473,8 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
* Hook for the host to create/provide a {@link PlatformPlugin} if the associated
* Flutter experience should control system chrome.
*/
@Nullable
XPlatformPlugin
providePlatformPlugin
(
@NonNull
FlutterEngine
flutterEngine
);
PlatformPlugin
providePlatformPlugin
(
@Nullable
Activity
activity
,
@NonNull
FlutterEngine
flutterEngine
);
/**
* Hook for the host to configure the {@link FlutterEngine} as desired.
*/
...
...
android/src/main/java/com/idlefish/flutterboost/containers/FlutterFragment.java
View file @
3a7185d0
package
com.idlefish.flutterboost.containers
;
import
android.app.Activity
;
import
android
.arch
.lifecycle.Lifecycle
;
import
android
x
.lifecycle.Lifecycle
;
import
android.content.Context
;
import
android.content.Intent
;
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
x
.annotation.NonNull
;
import
android
x
.annotation.Nullable
;
import
android
x.fragment
.app.Fragment
;
import
android
x.fragment
.app.FragmentActivity
;
import
android.view.LayoutInflater
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
com.idlefish.flutterboost.FlutterBoost
;
import
com.idlefish.flutterboost.XFlutterView
;
import
com.idlefish.flutterboost.XPlatformPlugin
;
import
io.flutter.embedding.android.*
;
import
io.flutter.embedding.engine.FlutterEngine
;
import
io.flutter.embedding.engine.FlutterShellArgs
;
import
io.flutter.plugin.platform.PlatformPlugin
;
import
java.util.HashMap
;
import
java.util.Map
;
...
...
@@ -233,7 +232,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
private
FlutterActivityAndFragmentDelegate
delegate
;
protected
X
FlutterView
getFlutterView
()
{
protected
FlutterView
getFlutterView
()
{
return
delegate
.
getFlutterView
();
}
...
...
@@ -467,8 +466,13 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
@Nullable
@Override
public
XPlatformPlugin
providePlatformPlugin
(
@NonNull
FlutterEngine
flutterEngine
)
{
return
BoostViewUtils
.
getPlatformPlugin
(
flutterEngine
.
getPlatformChannel
());
public
PlatformPlugin
providePlatformPlugin
(
@Nullable
Activity
activity
,
@NonNull
FlutterEngine
flutterEngine
)
{
if
(
activity
!=
null
)
{
return
new
PlatformPlugin
(
getActivity
(),
flutterEngine
.
getPlatformChannel
());
}
else
{
return
null
;
}
}
/**
...
...
android/src/main/java/com/idlefish/flutterboost/containers/FlutterSplashView.java
View file @
3a7185d0
...
...
@@ -4,8 +4,8 @@ import android.content.Context;
import
android.graphics.Color
;
import
android.os.Bundle
;
import
android.os.Handler
;
import
android
.support
.annotation.NonNull
;
import
android
.support
.annotation.Nullable
;
import
android
x
.annotation.NonNull
;
import
android
x
.annotation.Nullable
;
import
android.util.AttributeSet
;
import
android.view.View
;
import
android.widget.FrameLayout
;
...
...
@@ -29,7 +29,7 @@ public class FlutterSplashView extends FrameLayout {
@Nullable
private
SplashScreen
splashScreen
;
@Nullable
private
X
FlutterView
flutterView
;
private
FlutterView
flutterView
;
@Nullable
private
View
splashScreenView
;
@Nullable
...
...
@@ -103,7 +103,7 @@ public class FlutterSplashView extends FrameLayout {
* If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the
* given {@code flutterView} on its own.
*/
public
void
displayFlutterViewWithSplash
(
@NonNull
X
FlutterView
flutterView
,
@Nullable
SplashScreen
splashScreen
)
{
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
);
...
...
example/android/app/build.gradle
View file @
3a7185d0
...
...
@@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply
from:
"$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android
{
compileSdkVersion
2
8
compileSdkVersion
2
9
lintOptions
{
disable
'InvalidPackage'
...
...
@@ -38,7 +38,7 @@ android {
targetSdkVersion
28
versionCode
flutterVersionCode
.
toInteger
()
versionName
flutterVersionName
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner
'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes
{
...
...
@@ -56,11 +56,11 @@ flutter {
dependencies
{
testImplementation
'junit:junit:4.12'
androidTestImplementation
'
com.android.support.test:runner:1.0.2
'
androidTestImplementation
'
com.android.support.test.espresso:espresso-core:3.0.2
'
implementation
'
com.android.support:appcompat-v7:28
.0.0'
implementation
'com.
android.support:design:28
.0.0'
implementation
'
com.android.support:support-v4:28
.0.0'
androidTestImplementation
'
androidx.test.ext:junit:1.1.1
'
androidTestImplementation
'
androidx.test.espresso:espresso-core:3.1.0
'
implementation
'
androidx.appcompat:appcompat:1
.0.0'
implementation
'com.
google.android.material:material:1
.0.0'
implementation
'
androidx.legacy:legacy-support-v4:1
.0.0'
implementation
'com.alibaba:fastjson:1.2.41'
}
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/FitSystemWindowFrameLayout.java
View file @
3a7185d0
...
...
@@ -3,10 +3,10 @@ package com.taobao.idlefish.flutterboostexample;
import
android.annotation.TargetApi
;
import
android.content.Context
;
import
android.os.Build
;
import
android
.support
.annotation.NonNull
;
import
android
.support
.annotation.Nullable
;
import
android
.support
.annotation.RequiresApi
;
import
android
.support.v4
.view.ViewCompat
;
import
android
x
.annotation.NonNull
;
import
android
x
.annotation.Nullable
;
import
android
x
.annotation.RequiresApi
;
import
android
x.core
.view.ViewCompat
;
import
android.util.AttributeSet
;
import
android.view.View
;
import
android.view.ViewGroup
;
...
...
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/FlutterFragmentPageActivity.java
View file @
3a7185d0
...
...
@@ -6,9 +6,9 @@ import android.graphics.Color;
import
android.graphics.drawable.Drawable
;
import
android.os.Build
;
import
android.os.Bundle
;
import
android
.support
.annotation.Nullable
;
import
android
.support.v7
.app.ActionBar
;
import
android
.support.v7
.app.AppCompatActivity
;
import
android
x
.annotation.Nullable
;
import
android
x.appcompat
.app.ActionBar
;
import
android
x.appcompat
.app.AppCompatActivity
;
import
android.view.View
;
import
android.view.Window
;
import
android.view.WindowManager
;
...
...
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/MainActivity.java
View file @
3a7185d0
package
com.taobao.idlefish.flutterboostexample
;
import
android.os.Bundle
;
import
android
.support
.annotation.Nullable
;
import
android
.support.v7
.app.AppCompatActivity
;
import
android
x
.annotation.Nullable
;
import
android
x.appcompat
.app.AppCompatActivity
;
import
android.view.View
;
import
android.widget.TextView
;
...
...
example/android/app/src/main/java/com/taobao/idlefish/flutterboostexample/NativePageActivity.java
View file @
3a7185d0
package
com.taobao.idlefish.flutterboostexample
;
import
android.os.Bundle
;
import
android
.support
.annotation.Nullable
;
import
android
.support.v7
.app.AppCompatActivity
;
import
android
x
.annotation.Nullable
;
import
android
x.appcompat
.app.AppCompatActivity
;
import
android.view.View
;
import
android.widget.TextView
;
...
...
example/android/build.gradle
View file @
3a7185d0
...
...
@@ -5,7 +5,7 @@ buildscript {
}
dependencies
{
classpath
'com.android.tools.build:gradle:3.
1.2
'
classpath
'com.android.tools.build:gradle:3.
2.0
'
}
}
...
...
example/android/gradle.properties
View file @
3a7185d0
org.gradle.jvmargs
=
-Xmx1536M
android.enableR8
=
true
android.useAndroidX
=
true
android.enableJetifier
=
true
example/android/gradle/wrapper/gradle-wrapper.properties
View file @
3a7185d0
...
...
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
zipStorePath
=
wrapper/dists
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-4.
4
-all.zip
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-4.
6
-all.zip
example/android/settings_aar.gradle
0 → 100644
View file @
3a7185d0
include
':app'
example/ios/Runner.xcworkspace/xcuserdata/wubian.xcuserdatad/UserInterfaceState.xcuserstate
0 → 100644
View file @
3a7185d0
File added
example/lib/main.dart
View file @
3a7185d0
...
...
@@ -18,7 +18,8 @@ class _MyAppState extends State<MyApp> {
super
.
initState
();
FlutterBoost
.
singleton
.
registerPageBuilders
({
'embeded'
:
(
pageName
,
params
,
_
)=>
EmbededFirstRouteWidget
(),
'/'
:
(
pageName
,
params
,
_
)
=>
Container
(),
'embeded'
:
(
pageName
,
params
,
_
)
=>
EmbededFirstRouteWidget
(),
'first'
:
(
pageName
,
params
,
_
)
=>
FirstRouteWidget
(),
'firstFirst'
:
(
pageName
,
params
,
_
)
=>
FirstFirstRouteWidget
(),
'second'
:
(
pageName
,
params
,
_
)
=>
SecondRouteWidget
(),
...
...
@@ -26,49 +27,77 @@ class _MyAppState extends State<MyApp> {
'tab'
:
(
pageName
,
params
,
_
)
=>
TabRouteWidget
(),
'platformView'
:
(
pageName
,
params
,
_
)
=>
PlatformRouteWidget
(),
'flutterFragment'
:
(
pageName
,
params
,
_
)
=>
FragmentRouteWidget
(
params
),
///可以在native层通过 getContainerParams 来传递参数
'flutterPage'
:
(
pageName
,
params
,
_
)
{
print
(
"flutterPage params:
$params
"
);
return
FlutterRouteWidget
(
params:
params
);
return
FlutterRouteWidget
(
params:
params
);
},
'f2f_first'
:
(
pageName
,
params
,
_
)
=>
F2FFirstPage
(),
'f2f_first'
:
(
pageName
,
params
,
_
)
=>
F2FFirstPage
(),
'f2f_second'
:
(
pageName
,
params
,
_
)
=>
F2FSecondPage
(),
});
FlutterBoost
.
singleton
.
addBoostNavigatorObserver
(
TestBoostNavigatorObserver
());
FlutterBoost
.
singleton
.
addContainerObserver
((
ContainerOperation
operation
,
BoostContainerSettings
settings
){
FlutterBoost
.
singleton
.
addBoostNavigatorObserver
(
TestBoostNavigatorObserver
());
FlutterBoost
.
singleton
.
addContainerObserver
(
(
ContainerOperation
operation
,
BoostContainerSettings
settings
)
{
operation
;
settings
;
});
FlutterBoostAPI
.
singleton
.
routeSettingsBuilder
=
(
String
url
,
{
Map
<
String
,
dynamic
>
urlParams
,
Map
<
String
,
dynamic
>
exts
})
=>
BoostRouteSettings
(
uniqueId:
'
${url}
_
${DateTime.now().millisecondsSinceEpoch}
'
,
name:
url
,
params:
urlParams
,
);
{
Map
<
String
,
dynamic
>
urlParams
,
Map
<
String
,
dynamic
>
exts
})
=>
BoostRouteSettings
(
uniqueId:
'
${url}
_
${DateTime.now().millisecondsSinceEpoch}
'
,
name:
url
,
params:
urlParams
,
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Material
App
(
return
Widgets
App
(
title:
'Flutter Boost example'
,
builder:
FlutterBoost
.
init
(
postPush:
_onRoutePushed
),
home:
Container
(
color:
Colors
.
white
));
color:
Colors
.
white
,
localizationsDelegates:
[
DefaultMaterialLocalizations
.
delegate
],
onUnknownRoute:
(
RouteSettings
settings
){
if
(
settings
.
name
==
"/"
)
return
unKnownRoute
(
settings
);
},
// home: Container(),
);
}
void
_onRoutePushed
(
String
pageName
,
String
uniqueId
,
Map
params
,
Route
route
,
Future
_
)
{
Route
unKnownRoute
(
RouteSettings
settings
){
return
new
PageRouteBuilder
<
dynamic
>(
pageBuilder:
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
){
return
new
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
const
Text
(
"First Page"
,
textDirection:
TextDirection
.
ltr
,),
const
Padding
(
padding:
const
EdgeInsets
.
all
(
10.0
)),
new
GestureDetector
(
onTap:
()
=>
Navigator
.
of
(
context
).
pop
(),
child:
new
Container
(
padding:
const
EdgeInsets
.
all
(
10.0
),
color:
Colors
.
blue
,
child:
const
Text
(
"Back"
),
),
)
]
);
}
);
}
void
_onRoutePushed
(
String
pageName
,
String
uniqueId
,
Map
params
,
Route
route
,
Future
_
)
{}
}
class
TestBoostNavigatorObserver
extends
ContainerNavigatorObserver
{
class
TestBoostNavigatorObserver
extends
ContainerNavigatorObserver
{
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
route
.
settings
.
name
!=
"/"
;
route
.
settings
.
name
!=
"/"
;
//1. 底下
//新页面已经push完成
...
...
@@ -86,10 +115,8 @@ class TestBoostNavigatorObserver extends ContainerNavigatorObserver{
void
didReplace
({
Route
<
dynamic
>
newRoute
,
Route
<
dynamic
>
oldRoute
})
{
print
(
"flutterboost#didReplace"
);
}
void
willPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
void
willPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
print
(
"flutterboost#willPush"
);
}
}
lib/container/boost_container.dart
View file @
3a7185d0
...
...
@@ -183,6 +183,9 @@ class BoostContainerState extends NavigatorState {
@override
Future
<
bool
>
maybePop
<
T
extends
Object
>([
T
result
])
async
{
if
(
routerHistory
.
isEmpty
)
pop
(
result
);
final
Route
<
T
>
route
=
routerHistory
.
last
;
final
RoutePopDisposition
disposition
=
await
route
.
willPop
();
if
(
mounted
)
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment