Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
weixin_43355755
engine
提交
5c59a1d1
E
engine
项目概览
weixin_43355755
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
5c59a1d1
编写于
6月 10, 2021
作者:
E
Emmanuel Garcia
提交者:
GitHub
6月 10, 2021
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Allow Flutter focus to interop with Android view hierarchies (#26602)
上级
91b1460d
变更
5
隐藏空白更改
内联
并排
Showing
5 changed file
with
243 addition
and
29 deletion
+243
-29
shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java
...er/embedding/engine/mutatorsstack/FlutterMutatorView.java
+54
-4
shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java
...ter/embedding/engine/systemchannels/TextInputChannel.java
+13
-3
shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
...rm/android/io/flutter/plugin/editing/TextInputPlugin.java
+42
-22
shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
...d/io/flutter/plugin/platform/PlatformViewsController.java
+15
-0
shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java
...mbedding/engine/mutatorsstack/FlutterMutatorViewTest.java
+119
-0
未找到文件。
shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java
浏览文件 @
5c59a1d1
package
io.flutter.embedding.engine.mutatorsstack
;
import
static
android
.
view
.
View
.
OnFocusChangeListener
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.graphics.Canvas
;
import
android.graphics.Matrix
;
import
android.graphics.Path
;
import
android.view.MotionEvent
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.view.ViewTreeObserver
;
import
android.widget.FrameLayout
;
import
androidx.annotation.NonNull
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
io.flutter.embedding.android.AndroidTouchProcessor
;
/**
...
...
@@ -31,7 +38,7 @@ public class FlutterMutatorView extends FrameLayout {
public
FlutterMutatorView
(
@NonNull
Context
context
,
float
screenDensity
,
@N
onNull
AndroidTouchProcessor
androidTouchProcessor
)
{
@N
ullable
AndroidTouchProcessor
androidTouchProcessor
)
{
super
(
context
,
null
);
this
.
screenDensity
=
screenDensity
;
this
.
androidTouchProcessor
=
androidTouchProcessor
;
...
...
@@ -39,9 +46,52 @@ public class FlutterMutatorView extends FrameLayout {
/** Initialize the FlutterMutatorView. */
public
FlutterMutatorView
(
@NonNull
Context
context
)
{
super
(
context
,
null
);
this
.
screenDensity
=
1
;
this
.
androidTouchProcessor
=
null
;
this
(
context
,
1
,
/* androidTouchProcessor=*/
null
);
}
/**
* Determines if the current view or any descendant view has focus.
*
* @param root The root view.
* @return True if the current view or any descendant view has focus.
*/
@VisibleForTesting
public
static
boolean
childHasFocus
(
@Nullable
View
root
)
{
if
(
root
==
null
)
{
return
false
;
}
if
(
root
.
hasFocus
())
{
return
true
;
}
if
(
root
instanceof
ViewGroup
)
{
final
ViewGroup
viewGroup
=
(
ViewGroup
)
root
;
for
(
int
idx
=
0
;
idx
<
viewGroup
.
getChildCount
();
idx
++)
{
if
(
childHasFocus
(
viewGroup
.
getChildAt
(
idx
)))
{
return
true
;
}
}
}
return
false
;
}
/**
* Adds a focus change listener that notifies when the current view or any of its descendant views
* have received focus.
*
* @param focusListener The focus listener.
*/
public
void
addOnFocusChangeListener
(
@NonNull
OnFocusChangeListener
focusListener
)
{
final
View
mutatorView
=
this
;
final
ViewTreeObserver
observer
=
this
.
getViewTreeObserver
();
if
(
observer
.
isAlive
())
{
observer
.
addOnGlobalFocusChangeListener
(
new
ViewTreeObserver
.
OnGlobalFocusChangeListener
()
{
@Override
public
void
onGlobalFocusChanged
(
View
oldFocus
,
View
newFocus
)
{
focusListener
.
onFocusChange
(
mutatorView
,
childHasFocus
(
mutatorView
));
}
});
}
}
/**
...
...
shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java
浏览文件 @
5c59a1d1
...
...
@@ -82,8 +82,16 @@ public class TextInputChannel {
result
.
success
(
null
);
break
;
case
"TextInput.setPlatformViewClient"
:
final
int
id
=
(
int
)
args
;
textInputMethodHandler
.
setPlatformViewClient
(
id
);
try
{
final
JSONObject
arguments
=
(
JSONObject
)
args
;
final
int
platformViewId
=
arguments
.
getInt
(
"platformViewId"
);
final
boolean
usesVirtualDisplay
=
arguments
.
optBoolean
(
"usesVirtualDisplay"
,
false
);
textInputMethodHandler
.
setPlatformViewClient
(
platformViewId
,
usesVirtualDisplay
);
result
.
success
(
null
);
}
catch
(
JSONException
exception
)
{
result
.
error
(
"error"
,
exception
.
getMessage
(),
null
);
}
break
;
case
"TextInput.setEditingState"
:
try
{
...
...
@@ -360,8 +368,10 @@ public class TextInputChannel {
* different client is set.
*
* @param id the ID of the platform view to be set as a text input client.
* @param usesVirtualDisplay True if the platform view uses a virtual display, false if it uses
* hybrid composition.
*/
void
setPlatformViewClient
(
int
id
);
void
setPlatformViewClient
(
int
id
,
boolean
usesVirtualDisplay
);
/**
* Sets the size and the transform matrix of the current text input client.
...
...
shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
浏览文件 @
5c59a1d1
...
...
@@ -102,7 +102,11 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
@Override
public
void
hide
()
{
hideTextInput
(
mView
);
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
HC_PLATFORM_VIEW
)
{
notifyViewExited
();
}
else
{
hideTextInput
(
mView
);
}
}
@Override
...
...
@@ -129,8 +133,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
}
@Override
public
void
setPlatformViewClient
(
int
platformViewId
)
{
setPlatformViewTextInputClient
(
platformViewId
);
public
void
setPlatformViewClient
(
int
platformViewId
,
boolean
usesVirtualDisplay
)
{
setPlatformViewTextInputClient
(
platformViewId
,
usesVirtualDisplay
);
}
@Override
...
...
@@ -189,7 +193,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
* display to another.
*/
public
void
lockPlatformViewInputConnection
()
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
)
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
VD_
PLATFORM_VIEW
)
{
isInputConnectionLocked
=
true
;
}
}
...
...
@@ -284,7 +288,11 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
return
null
;
}
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
)
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
HC_PLATFORM_VIEW
)
{
return
null
;
}
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
VD_PLATFORM_VIEW
)
{
if
(
isInputConnectionLocked
)
{
return
lastInputConnection
;
}
...
...
@@ -342,9 +350,12 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
* input connection.
*/
public
void
clearPlatformViewClient
(
int
platformViewId
)
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
&&
inputTarget
.
id
==
platformViewId
)
{
if
((
inputTarget
.
type
==
InputTarget
.
Type
.
VD_PLATFORM_VIEW
||
inputTarget
.
type
==
InputTarget
.
Type
.
HC_PLATFORM_VIEW
)
&&
inputTarget
.
id
==
platformViewId
)
{
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
NO_TARGET
,
0
);
hideTextInput
(
mView
);
notifyViewExited
();
mImm
.
hideSoftInputFromWindow
(
mView
.
getApplicationWindowToken
(),
0
);
mImm
.
restartInput
(
mView
);
mRestartInputPending
=
false
;
}
...
...
@@ -361,8 +372,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
private
void
hideTextInput
(
View
view
)
{
notifyViewExited
();
// Note:
a race condition may lead to us hiding the keyboard here just after a platform view has
// shown it.
// Note:
when a virtual display is used, 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).
...
...
@@ -393,16 +404,20 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
mEditable
.
addEditingStateListener
(
this
);
}
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
setPlatformViewTextInputClient
(
int
platformViewId
,
boolean
usesVirtualDisplay
)
{
if
(
usesVirtualDisplay
)
{
// 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
.
VD_PLATFORM_VIEW
,
platformViewId
);
mImm
.
restartInput
(
mView
);
mRestartInputPending
=
false
;
}
else
{
inputTarget
=
new
InputTarget
(
InputTarget
.
Type
.
HC_PLATFORM_VIEW
,
platformViewId
);
lastInputConnection
=
null
;
}
}
private
static
boolean
composingChanged
(
...
...
@@ -493,7 +508,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
@VisibleForTesting
void
clearTextInputClient
()
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
PLATFORM_VIEW
)
{
if
(
inputTarget
.
type
==
InputTarget
.
Type
.
VD_PLATFORM_VIEW
)
{
// This only applies to platform views that use a virtual display.
// 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.
...
...
@@ -530,8 +546,12 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
// 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
// InputConnection is managed by an embedded platform view that is backed by a virtual
// display (VD).
VD_PLATFORM_VIEW
,
// InputConnection is managed by an embedded platform view that is embeded in the Android view
// hierarchy, and uses hybrid composition (HC).
HC_PLATFORM_VIEW
,
}
public
InputTarget
(
@NonNull
Type
type
,
int
id
)
{
...
...
shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java
浏览文件 @
5c59a1d1
...
...
@@ -337,6 +337,11 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
@Override
public
void
clearFocus
(
int
viewId
)
{
final
PlatformView
platformView
=
platformViews
.
get
(
viewId
);
if
(
platformView
!=
null
)
{
platformView
.
getView
().
clearFocus
();
return
;
}
ensureValidAndroidVersion
(
Build
.
VERSION_CODES
.
KITKAT_WATCH
);
View
view
=
vdControllers
.
get
(
viewId
).
getView
();
view
.
clearFocus
();
...
...
@@ -732,6 +737,16 @@ public class PlatformViewsController implements PlatformViewsAccessibilityDelega
final
FlutterMutatorView
parentView
=
new
FlutterMutatorView
(
context
,
context
.
getResources
().
getDisplayMetrics
().
density
,
androidTouchProcessor
);
parentView
.
addOnFocusChangeListener
(
(
view
,
hasFocus
)
->
{
if
(
hasFocus
)
{
platformViewsChannel
.
invokeViewFocused
(
viewId
);
}
else
{
textInputPlugin
.
clearPlatformViewClient
(
viewId
);
}
});
platformViewParent
.
put
(
viewId
,
parentView
);
parentView
.
addView
(
platformView
.
getView
());
((
FlutterView
)
flutterView
).
addView
(
parentView
);
...
...
shell/platform/android/test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java
浏览文件 @
5c59a1d1
package
io.flutter.embedding.engine.mutatorsstack
;
import
static
android
.
view
.
View
.
OnFocusChangeListener
;
import
static
junit
.
framework
.
TestCase
.*;
import
static
org
.
mockito
.
Mockito
.*;
import
android.graphics.Matrix
;
import
android.view.MotionEvent
;
import
android.view.View
;
import
android.view.ViewGroup
;
import
android.view.ViewTreeObserver
;
import
io.flutter.embedding.android.AndroidTouchProcessor
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
...
...
@@ -76,4 +80,119 @@ public class FlutterMutatorViewTest {
assertTrue
(
matrixCaptor
.
getValue
().
equals
(
screenMatrix
));
}
}
@Test
public
void
childHasFocus_rootHasFocus
()
{
final
View
rootView
=
mock
(
View
.
class
);
when
(
rootView
.
hasFocus
()).
thenReturn
(
true
);
assertTrue
(
FlutterMutatorView
.
childHasFocus
(
rootView
));
}
@Test
public
void
childHasFocus_rootDoesNotHaveFocus
()
{
final
View
rootView
=
mock
(
View
.
class
);
when
(
rootView
.
hasFocus
()).
thenReturn
(
false
);
assertFalse
(
FlutterMutatorView
.
childHasFocus
(
rootView
));
}
@Test
public
void
childHasFocus_rootIsNull
()
{
assertFalse
(
FlutterMutatorView
.
childHasFocus
(
null
));
}
@Test
public
void
childHasFocus_childHasFocus
()
{
final
View
childView
=
mock
(
View
.
class
);
when
(
childView
.
hasFocus
()).
thenReturn
(
true
);
final
ViewGroup
rootView
=
mock
(
ViewGroup
.
class
);
when
(
rootView
.
getChildCount
()).
thenReturn
(
1
);
when
(
rootView
.
getChildAt
(
0
)).
thenReturn
(
childView
);
assertTrue
(
FlutterMutatorView
.
childHasFocus
(
rootView
));
}
@Test
public
void
childHasFocus_childDoesNotHaveFocus
()
{
final
View
childView
=
mock
(
View
.
class
);
when
(
childView
.
hasFocus
()).
thenReturn
(
false
);
final
ViewGroup
rootView
=
mock
(
ViewGroup
.
class
);
when
(
rootView
.
getChildCount
()).
thenReturn
(
1
);
when
(
rootView
.
getChildAt
(
0
)).
thenReturn
(
childView
);
assertFalse
(
FlutterMutatorView
.
childHasFocus
(
rootView
));
}
@Test
public
void
focusChangeListener_hasFocus
()
{
final
ViewTreeObserver
viewTreeObserver
=
mock
(
ViewTreeObserver
.
class
);
when
(
viewTreeObserver
.
isAlive
()).
thenReturn
(
true
);
final
FlutterMutatorView
view
=
new
FlutterMutatorView
(
RuntimeEnvironment
.
systemContext
)
{
@Override
public
ViewTreeObserver
getViewTreeObserver
()
{
return
viewTreeObserver
;
}
@Override
public
boolean
hasFocus
()
{
return
true
;
}
};
final
OnFocusChangeListener
focusListener
=
mock
(
OnFocusChangeListener
.
class
);
view
.
addOnFocusChangeListener
(
focusListener
);
final
ArgumentCaptor
<
ViewTreeObserver
.
OnGlobalFocusChangeListener
>
focusListenerCaptor
=
ArgumentCaptor
.
forClass
(
ViewTreeObserver
.
OnGlobalFocusChangeListener
.
class
);
verify
(
viewTreeObserver
).
addOnGlobalFocusChangeListener
(
focusListenerCaptor
.
capture
());
focusListenerCaptor
.
getValue
().
onGlobalFocusChanged
(
null
,
null
);
verify
(
focusListener
).
onFocusChange
(
view
,
true
);
}
@Test
public
void
focusChangeListener_doesNotHaveFocus
()
{
final
ViewTreeObserver
viewTreeObserver
=
mock
(
ViewTreeObserver
.
class
);
when
(
viewTreeObserver
.
isAlive
()).
thenReturn
(
true
);
final
FlutterMutatorView
view
=
new
FlutterMutatorView
(
RuntimeEnvironment
.
systemContext
)
{
@Override
public
ViewTreeObserver
getViewTreeObserver
()
{
return
viewTreeObserver
;
}
@Override
public
boolean
hasFocus
()
{
return
false
;
}
};
final
OnFocusChangeListener
focusListener
=
mock
(
OnFocusChangeListener
.
class
);
view
.
addOnFocusChangeListener
(
focusListener
);
final
ArgumentCaptor
<
ViewTreeObserver
.
OnGlobalFocusChangeListener
>
focusListenerCaptor
=
ArgumentCaptor
.
forClass
(
ViewTreeObserver
.
OnGlobalFocusChangeListener
.
class
);
verify
(
viewTreeObserver
).
addOnGlobalFocusChangeListener
(
focusListenerCaptor
.
capture
());
focusListenerCaptor
.
getValue
().
onGlobalFocusChanged
(
null
,
null
);
verify
(
focusListener
).
onFocusChange
(
view
,
false
);
}
@Test
public
void
focusChangeListener_viewTreeObserverIsAliveFalseDoesNotThrow
()
{
final
FlutterMutatorView
view
=
new
FlutterMutatorView
(
RuntimeEnvironment
.
systemContext
)
{
@Override
public
ViewTreeObserver
getViewTreeObserver
()
{
final
ViewTreeObserver
viewTreeObserver
=
mock
(
ViewTreeObserver
.
class
);
when
(
viewTreeObserver
.
isAlive
()).
thenReturn
(
false
);
return
viewTreeObserver
;
}
};
view
.
addOnFocusChangeListener
(
mock
(
OnFocusChangeListener
.
class
));
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录