Flutter源码解析-TextField(2) -FocusNode焦点

说明

本文源码基于flutter 1.7.8
承接上一篇,这次着重来分析FocusNode,来看看焦点是怎么管理的

使用

/// 创建
final FocusNode focusNode = FocusNode();

/// 获得焦点
FocusScope.of(context).requestFocus(focusNode);
focusNode.requestFocus();  //直接使用这个会报错的

/// 失去焦点
focusNode.unfocus();

/// 判断是否获取到焦点
focusNode.hasFocus;

分析

先从结果来看,分析hasFocus这个方法

bool get hasPrimaryFocus => _manager?.primaryFocus == this;

bool get hasFocus {
    if (_manager?.primaryFocus == null) {
      return false;
    }
    if (hasPrimaryFocus) {
      return true;
    }
    return _manager.primaryFocus.ancestors.contains(this);
  }

这里有2个不太清楚,_manager和primaryFocus。
没关系,先记住这2个,然后来分析 FocusScope.of(context).requestFocus(focusNode) 获取焦点

//这个其实就是想获取一个最近的FocusScopeNode,不存在则返回根FocusScopeNode
static FocusScopeNode of(BuildContext context) {
    final _FocusMarker marker = context.inheritFromWidgetOfExactType(_FocusMarker);
    return marker?.notifier?.nearestScope ?? context.owner.focusManager.rootScope;
}

我们直接分析rootScope,其他的也和他相似

1. rootScope创建流程

在runApp的过程中会调用一下代码:

  //创建
  BuildOwner get buildOwner => _buildOwner;
  final BuildOwner _buildOwner = BuildOwner();
  
  //使用
  void attachRootWidget(Widget rootWidget) {
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: renderView,
      debugShortDescription: '[root]',
      child: rootWidget,
    ).attachToRenderTree(buildOwner, renderViewElement);
  }

BuildOwner内部会创建一个focusManager,顾名思义,焦点管理

class BuildOwner {
  
  FocusManager focusManager = FocusManager();
  ...  
}

在这个焦点管理的类里就有创建 rootScope对象

class FocusManager with DiagnosticableTreeMixin {
  //给根节点的_manager赋值
  FocusManager() {
    rootScope._manager = this;
    RawKeyboard.instance.addListener(_handleRawKeyEvent);
  }

  //根节点
  final FocusScopeNode rootScope = FocusScopeNode(debugLabel: 'Root Focus Scope');
  
  ...
}

2. _manager的赋值

回调之前,咱们再来看requestFocus方法,后面的分析都以当前是rootScope为例子

  void requestFocus([FocusNode node]) {
    if (node != null) {
      if (node._parent == null) {
        //一个刚创建的空白node,需要去建立各个关系
        _reparent(node);
      }
      //执行请求焦点操作
      node._doRequestFocus();
      return;
    }
   //未建立关系的node直接执行请求焦点操作会报错
    _doRequestFocus();
  }

_reparent里做了什么?

  @mustCallSuper
  void _reparent(FocusNode child) {
    if (child._parent == this) {
      return;
    }
    //node的祖先,第一个是FocusScopeNode的节点
    final FocusScopeNode oldScope = child.enclosingScope;
    final bool hadFocus = child.hasFocus;
    //以前的_parent存在的话则移出当前node,因为需要更换parent
    child._parent?._removeChild(child);
    // rootScope的_children列表中添加当前node
    _children.add(child);
    //建立_parent关系,node 的 _parent为rootScope
    child._parent = this;
    //node更新FocusManager对象,就是传递_manager对象
    child._updateManager(_manager);
    if (hadFocus) {
      _manager?.primaryFocus?._setAsFocusedChild();
    }
    if (oldScope != null && child.context != null && child.enclosingScope != oldScope) {
      DefaultFocusTraversal.of(child.context, nullOk: true)?.changedScope(node: child, oldScope: oldScope);
    }
  }

上面的模型有些类似树的结构,父节点可以有多个子节点,而子节点只有一个父节点
node对象接替了rootScope中的_manager

  void _updateManager(FocusManager manager) {
    //node的_manager赋值
    _manager = manager;
    for (FocusNode descendant in descendants) {
      descendant._manager = manager;
    }
  }

通过上面的分析可以得出,所有的FocusNode的_manager当映射关系建立后,都是rootScope中的_manager。也即_manager是唯一的

3. 获取焦点

上面的requestFocus方法中执行了_doRequestFocus方法

  void _doRequestFocus() {
    _setAsFocusedChild();
    if (hasPrimaryFocus) {
      return;
    }
    _hasKeyboardToken = true;
    _markAsDirty(newFocus: this);
  }

建立获取焦点的孩子集合

void _setAsFocusedChild() {
    //this就是上面的node对象
    FocusNode scopeFocus = this;
    //这里进行了一次父类遍历
    for (FocusScopeNode ancestor in ancestors.whereType<FocusScopeNode>()) {
      ancestor._focusedChildren.remove(scopeFocus);
      //将node移至尾端
      ancestor._focusedChildren.add(scopeFocus);
      scopeFocus = ancestor;
    }
  }

这个遍历的效果是,最近的FocusScopeNode添加了node,而上一层的FocusScopeNode添加了最近的FocusScopeNode
你可能奇怪_focusedChildren有什么用?和上边的_children有什么区别?

FocusNode get focusedChild {
    return _focusedChildren.isNotEmpty ? _focusedChildren.last : null;
  }

_focusedChildren列表的最后一个就是当前获取焦点的节点

4. primaryFocus的赋值

继续看_doRequestFocus中的另一个方法_markAsDirty

void _markAsDirty({FocusNode newFocus}) {
    if (_manager != null) {
     //经过上面的步骤,_manager有值了
      _manager._dirtyNodes?.add(this);
     //node传递过来
      _manager._markNeedsUpdate(newFocus: newFocus);
    } else {
      newFocus?._setAsFocusedChild();
      newFocus?._notify();
      if (newFocus != this) {
        _notify();
      }
    }
  }

void _markNeedsUpdate({FocusNode newFocus}) {
    //_nextFocus为当前的node对象
    _nextFocus = newFocus ?? _nextFocus;
    if (_haveScheduledUpdate) {
      return;
    }
    _haveScheduledUpdate = true;
    scheduleMicrotask(_applyFocusChange);
  }

scheduleMicrotask是一个异步调用,_applyFocusChange请求焦点改变

  void _applyFocusChange() {
    _haveScheduledUpdate = false;
    final FocusNode previousFocus = _primaryFocus;
    if (_primaryFocus == null && _nextFocus == null) {
      _nextFocus = rootScope;
    }
     //当前_primaryFocus 为 null , _nextFocus为node对象
    if (_nextFocus != null && _nextFocus != _primaryFocus) {
      //_primaryFocus值为node对象
      _primaryFocus = _nextFocus;
      final Set<FocusNode> previousPath = previousFocus?.ancestors?.toSet() ?? <FocusNode>{};
      final Set<FocusNode> nextPath = _nextFocus.ancestors.toSet();
      _dirtyNodes.addAll(nextPath.difference(previousPath));
      _dirtyNodes.addAll(previousPath.difference(nextPath));
      _nextFocus = null;
    }
    if (previousFocus != _primaryFocus) {
      if (previousFocus != null) {
        _dirtyNodes.add(previousFocus);
      }
      if (_primaryFocus != null) {
        _dirtyNodes.add(_primaryFocus);
      }
    }
    for (FocusNode node in _dirtyNodes) {
      node._notify();
    }
    _dirtyNodes.clear();
  }

分析到这获取焦点基本完成,再来看失去焦点

  void unfocus() {
    //当前情况下是满足这个条件的
    if (hasPrimaryFocus) {
      //scope即为rootScope,因为当前情况下没有其他的映射关系
      final FocusScopeNode scope = enclosingScope;
      //焦点列表中移除node
      scope._focusedChildren.remove(this);
      _manager?._willUnfocusNode(this);
      return;
    }
    if (hasFocus) {
      _manager.primaryFocus.unfocus();
    }
  }

同时_primaryFocus也需要置空

void _willUnfocusNode(FocusNode node) {
    if (_primaryFocus == node) {
      _primaryFocus = null;
      _dirtyNodes.add(node);
      _markNeedsUpdate();
    }
    if (_nextFocus == node) {
      _nextFocus = null;
      _dirtyNodes.add(node);
      _markNeedsUpdate();
    }
  }

最后

我们发现TextField中获取焦点并不是用的FocusScope.of(context).requestFocus(focusNode);

@override
  void initState() {
    super.initState();
    widget.controller.addListener(_didChangeTextEditingValue);
    _focusAttachment = widget.focusNode.attach(context);
    widget.focusNode.addListener(_handleFocusChanged);
    ...
  }

然后在build方法中调用reparent方法

  @override
  Widget build(BuildContext context) {   
    _focusAttachment.reparent();
    super.build(context); 
    ...
  }

focusAttachment.reparent方法中和之前的基本类似

  void reparent({FocusNode parent}) {
    if (isAttached) {
      assert(_node.context != null);
      parent ??= Focus.of(_node.context, nullOk: true);
      parent ??= FocusScope.of(_node.context);
      assert(parent != null);
      //建立映射关系
      parent._reparent(_node);
    }
  }

调用

//直接使用 _doRequestFocus()
widget.focusNode.requestFocus();

总结

整个焦点体系是一个树,管理获取焦点的是一个列表
根节点是唯一的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容