Focus(焦点)树
焦点API介绍
FocusScopeNode :App的根节点(焦点树的根节点),拥有一组focus的父节点。FocusScopeNode的参数列表中包含:children(包括的全部焦点事件)、focusedChildren(请求焦点的焦点事件)等属性。 FocusScope :作为一个Widget的焦点节点(父节点),管理该Widget下的所有children的焦点节点(子节点)。 FocusScope 属于1中的子焦点。 Focus :创建Widget,作为 FocusScope 的子焦点,提供给自身child焦点。 FocusNode :焦点自身事件,也是输入框组件需要的焦点事件。FocusNode构造函数中有debugLabel等参数,其中可以通过debugLabel在焦点树中找到对应的焦点事件。温馨提示 :感兴趣可以去看focus_manager.dart类源码,给点耐心和时间看就可以了。
//官网demo(解释FocusScope与Focus的用法)
@override
Widget build(BuildContext context) {
//创建FocusScope,自己想管理child之间焦点抢占逻辑,并且依附在根节点上(FocusScopeNode)
return FocusScope(
debugLabel: 'Scope',
autofocus: true,
child: DefaultTextStyle(
//提供给child一个焦点事件。
child: Focus(
debugLabel: 'Button',
child: Builder(
builder: (BuildContext context) {
//获取附近的Focus的焦点事件
final FocusNode focusNode = Focus.of(context);
final bool hasFocus = focusNode.hasFocus;
return GestureDetector(
onTap: () {
if (hasFocus) {
//取消焦点获取
focusNode.unfocus();
} else {
//当前Build Widget获取焦点。
focusNode.requestFocus();
child: *******,
//小白用法demo
///创建一个焦点事件,最好带上debugLabel,要不然找的时候一堆focus,不知道哪个Focus是哪个页面的,变成野指针类似了。
final FocusNode _verifyFocus = FocusNode(debugLabel: "verify");
///告诉焦点树的根节点,当前的_verifyFocus需要焦点,把焦点夺过来。
FocusScope.of(focusContext()).requestFocus(_verifyFocus);
///当Widget从树从被干掉后,也要记得把focus从焦点树中移除。(后续会补充FocusAttachment与FocusManager的讲解)
@override
void dispose() {
_verifyFocus.dispose();
super.dispose();
如何拥有自己的FocusScopeNode
在创建FocusScope的时候就指定一个node,后续指定可以用这个FocusScopeNode来添加想要请求焦点的FocusNode。
方法二(麻烦点):
当你想拥有一个自己的FocusScopeNode的话,必须一同使用FocusScope + Builder,Builder组件应该是用来切换上下文,形成一个独立的context。因为在widget的build中的context都是根节点的context,如果你想自己的widget拥有一组独立的FocusScopeNode,就必须配合Builder一同使用。
当你拥有了自己的FocusScopeNode后,该widget所有焦点逻辑都应该有该FocusScopeNode来管理。而不是通过根节点的FocusScopeNode。
3.代码演示如下:
///方法一:创建FocusScope时候自定focusScopeNode,后续的FocusNode直接可以用
final FocusScopeNode _phoneFocusScope = FocusScopeNode(debugLabel: "phone_widgets");
@override
Widget build(BuildContext context) {
return FocusScope(
autofocus: true,
node: _phoneFocusScope,
child:xxxxxxxx
//请求焦点时候,因为当前widget下的全部焦点在_phoneFocusScope下了,所以直接request就可以了。
_verifyFocus.requestFocus(); // or _phoneFocusScope.requestFocus(_verifyFocus);
///方法二 : 不指定的话内部会自行生成一个
FocusScopeNode _phoneFocusScope;
return FocusScope(
autofocus: true,
child: Builder(builder: (BuildContext context){
//这里假如不加入Builder,直接用FocusScope.of(context)是获取根节点的FocusScopeNode
_phoneFocusScope = FocusScope.of(context);
return xxxxx;
FocusScope.of与Focus.of区别
FocusScope.of(context),通过context寻找树中最近的FocusScope。
Focus.of(context),通过context寻找树中最近的focus。
如何查找对应的FocusNode。
所有的焦点的创建都已attach到根节点(FocusScopeNode)上,所以当我们要在某些场景下通过接口回调操作某个焦点事件时,例如TabView的切换我想把上个页面的焦点事件给取消。
普通FocusNode的形式
通过FocusScope.of(context)拿到整个焦点树的根节点,然后通过children去遍历所需要寻找的焦点。代码示例如下:
//这里的num是上个页面的焦点事件,当index == 1的时候就是切到下个页面了。应该把软键盘给收起来。所以要执行unfocus。
_tabController.addListener(() {
_autoRequestFocus();
_autoRequestFocus() {
FocusScope.of(context).children.where((FocusNode node) => node.debugLabel == "num").forEach((FocusNode node) {
if (node.debugLabel == "num") {
if (_tabController.index == 1) {
//整个组的焦点都放弃掉,源码里面会给child
node.unfocus();
} else {
FocusScope.of(context).requestFocus(node);
} else {
node.unfocus();
FocusScope的形式
//创建FocusScopeNode,自己管理当前widget下xxxx布局的全部焦点
final FocusScopeNode _phoneFocusScope = FocusScopeNode(debugLabel: "phone_widgets");
return FocusScope(
autofocus: true,
node: _phoneFocusScope,
child: xxxxx
//当widget不可见后,把抢占焦点行为给放弃
_tabController.addListener(() {
_autoRequestFocus();
_autoRequestFocus() {
FocusScope.of(context).children.where((FocusNode node) => node.debugLabel == "phone_widgets").forEach((FocusNode node) {
if (node.debugLabel == "phone_widgets") {
if (_tabController.index == 1) {
//里面会把child给remove掉,及全部child都失去焦点
if(node.hasFocus) node.unfocus();
} else {
//该焦点组获取到焦点,至于焦点落在那个View上,看哪个焦点事件去请求。
if(!node.hasFocus) FocusScope.of(context).requestFocus(node);
} else {
node.unfocus();
顺带知识点
TabView中如果某个widget还存在焦点情况下,页切换至不可见也不会走dispose,应该类似与java的强引用一样,焦点树的根节点是一个强引用,不解除依赖关系页面不会被清除。