监听页面显示隐藏【Flutter笔记】

4,570 阅读2分钟

监听页面显示隐藏【Flutter笔记】

是这样的,父页面跳转子页面,子页面修改数据后,回退父页面需要更新,跳转子页面的组件隐藏的太深,不适合监听路由的返回做响应,之前是提升 provider 作用域实现的,跨页面更新数据,因为父页面是列表渲染,而且有多个类似的页面,就把 provider 封到另一个单独的组件中去了,出现的问题就是当前【页面数据更新时机不好掌控】。

解决办法:在flutter中 像路由、输入框(恰好挖个坑)等是有焦点【FocusNode】概念的,那可以利用 FocusNode+WidgetsBinding(里面有个帧回调) 实现页面的显示隐藏状态监听。

WidgetVisibilityStateMixin

获取当前顶层的焦点条件判断

import 'package:flutter/material.dart';

///页面状态 显示\隐藏
enum VisibilityState { hide, show }
mixin WidgetVisibilityStateMixin<T extends StatefulWidget> on State<T> implements WidgetsBindingObserver {
  late FocusNode _ownFocusNode, _oldFocusNode, _newFocusNode;
  VisibilityState visibilityState = VisibilityState.hide;
  ///忽略的焦点列表
  List<FocusNode> _ignoreFocusList = [];

  List<FocusNode> get ignoreFocusList => _ignoreFocusList;

  set ignoreFocusList(List<FocusNode> list) => _ignoreFocusList = list;
  
  ///显示
  void onShow() {
    visibilityState = VisibilityState.show;
  }
  
  ///不显示
  void onHide() {
    visibilityState = VisibilityState.hide;
  }

  _addFocusNodeChangeCb() {
    _ownFocusNode = _oldFocusNode = _newFocusNode = FocusManager.instance.primaryFocus!;
    WidgetsBinding.instance!.addObserver(this);
    WidgetsBinding.instance!.addPersistentFrameCallback(focusNodeChangeCb);
    onShow();
  }
  
  ///焦点判断
  void focusNodeChangeCb(_) {
    _newFocusNode = FocusManager.instance.primaryFocus!;
    if (_newFocusNode == _oldFocusNode) return;
    _oldFocusNode = _newFocusNode;
    
    if (_judgeNeedIgnore(_newFocusNode)) return;
    if (_newFocusNode == _ownFocusNode) {
      if (visibilityState != VisibilityState.show) {
        onShow();
      }
    } else {
      if (visibilityState != VisibilityState.hide) {
        onHide();
      }
    }
  }

  ///忽略焦点值
  bool _judgeNeedIgnore(focusNode) {
    return _ignoreFocusList.contains(focusNode);
  }

  @override
  void initState() {
    super.initState();
    Future(_addFocusNodeChangeCb);
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }
}

简单使用

监听的页面需要实现 WidgetsBindingObserver

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

///监听的页面需要实现 WidgetsBindingObserver
class _HomePageState extends State<HomePage> with WidgetVisibilityStateMixin, WidgetsBindingObserver {
  final inputFocus = FocusNode();

  @override
  onHide() {
    super.onHide();
    print('隐藏');
  }

  @override
  void onShow() {
    super.onShow();
    print('显示');
  }

  @override
  void initState() {
    super.initState();
    ///添加需要忽略的TextField焦点
    ignoreFocusList = [inputFocus];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home"),
      ),
      body: ListView(
        children: [
          ListTile(
            title: Text("childPage"),
            onTap: () {
              Navigator.pushNamed(context, '/child');
            },
          ),
          ///输入框需要单独处理焦点,及时释放,以免影响页面层级焦点的获取
          TextField(
            focusNode: inputFocus,
          ),
          TextButton(
            onPressed: () {
              inputFocus.unfocus();
            },
            child: Text('释放输入框焦点测试'),
          )
        ],
      ),
    );
  }
}

class ChildPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("childPage"),
      ),
      body: Center(
        child: Text(
          "childPage",
          style: Theme.of(context).textTheme.headline3,
        ),
      ),
    );
  }
}

完整例子

import 'package:flutter/material.dart';

enum VisibilityState { hide, show }

mixin WidgetVisibilityStateMixin<T extends StatefulWidget> on State<T> implements WidgetsBindingObserver {
  late FocusNode _ownFocusNode, _oldFocusNode, _newFocusNode;
  VisibilityState visibilityState = VisibilityState.hide;
  ///忽略的焦点列表
  List<FocusNode> _ignoreFocusList = [];

  List<FocusNode> get ignoreFocusList => _ignoreFocusList;

  set ignoreFocusList(List<FocusNode> list) => _ignoreFocusList = list;

  void onShow() {
    visibilityState = VisibilityState.show;
  }

  void onHide() {
    visibilityState = VisibilityState.hide;
  }

  _addFocusNodeChangeCb() {
    _ownFocusNode = _oldFocusNode = _newFocusNode = FocusManager.instance.primaryFocus!;
    WidgetsBinding.instance!.addObserver(this);
    WidgetsBinding.instance!.addPersistentFrameCallback(focusNodeChangeCb);
    onShow();
  }

  void focusNodeChangeCb(_) {
    _newFocusNode = FocusManager.instance.primaryFocus!;
    if (_newFocusNode == _oldFocusNode) return;
    _oldFocusNode = _newFocusNode;

    if (_judgeNeedIgnore(_newFocusNode)) return;
    if (_newFocusNode == _ownFocusNode) {
      ///显示
      if (visibilityState != VisibilityState.show) {
        onShow();
      }
    } else {
      ///不显示
      if (visibilityState != VisibilityState.hide) {
        onHide();
      }
    }
  }

  bool _judgeNeedIgnore(focusNode) {
    return _ignoreFocusList.contains(focusNode);
  }

  @override
  void initState() {
    super.initState();
    Future(_addFocusNodeChangeCb);
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }
}

main() {
  runApp(TestPageOnShowApp());
}

class TestPageOnShowApp extends StatelessWidget {
  const TestPageOnShowApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        "/": (_) => HomePage(),
        "/child": (_) => ChildPage(),
      },
      // home: HomePage(),
      initialRoute: '/',
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetVisibilityStateMixin, WidgetsBindingObserver {
  final inputFocus = FocusNode();

  @override
  onHide() {
    super.onHide();
    print('隐藏');
  }

  @override
  void onShow() {
    super.onShow();
    print('显示');
  }

  @override
  void initState() {
    super.initState();
    ignoreFocusList = [inputFocus];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Home"),
      ),
      body: ListView(
        children: [
          ListTile(
            title: Text("childPage"),
            onTap: () {
              Navigator.pushNamed(context, '/child');
            },
          ),
          ///输入框需要单独处理焦点,及时释放,以免影响页面层级焦点的获取
          TextField(
            focusNode: inputFocus,
          ),
          TextButton(
            onPressed: () {
              inputFocus.unfocus();
            },
            child: Text('释放焦点'),
          )
        ],
      ),
    );
  }
}

class ChildPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("childPage"),
      ),
      body: Center(
        child: Text(
          "childPage",
          style: Theme.of(context).textTheme.headline3,
        ),
      ),
    );
  }
}