当前位置: 首页 > news >正文

Flutter局部刷新解决闪烁问题

在开发Flutter倒计时,setState刷新会造成页面的闪烁,如图

image-20220127202810611

一、setState全局刷新

1、setState页面问题分析

在flutter中常用的刷新方法有setState,然后这个会造成整个页面刷新,特别是绘制需要时间的组件会闪烁.

2、setState页面源码

login_demo_page.dart

import 'dart:async';
import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';

class LoginDemoPage extends StatefulWidget {
  LoginDemoPage({Key key});

  
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginDemoPage> {
  int _seconds = 0;
  bool _loadingImageUrl = true;
  String _imageUrl =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAaCAIAAABw/jbvAAABjElEQVR42s2Y7Q6DIAxF2dxm9v4P7JaYYNeP2wJFZ/ihFS2HlrZQtoGrlDL7W7VbFcq3VLLfl23sGoR0P7c6xCGvJIxwuq8sIx9TUKXv17029Y9Wh3FIwBmBx3buJHw8V9lUfUt57Q0M5basoLkehGdBJ5SQLp4FaRHS+27CiAf1E9K3AJISAkgmiRO6kArh9wMGWR8rofwRI6yKmwirsOJFFnkzIUVSCdWFKgl33YDQGhw1YHfaRLE0eM9+pxJKG7IYiAlTMplOSB2VIlmEch0GCdXpP5WQIamEFE+aRRIyNta/1T/9NGsRqjxMoqYK5n4uoZU50moJa/SuxMJjnE2E0j8TqkI1W1hhykr6QIFKKMMMSxKJVSHyUosQ1DQg0sjGtIAsP7SD6ahrZxDiGHM2YUoAwHuCRKWlo3LPIuze2qcR5mrqhnRL2WQvdXfW6T6cFkubIC804ywv5cc7/034M9rgaU9k1zPjOMs9p3APfkpcx+CKDx4r5TpqdNDHfHhbu+DUgmvQjLLbBy9uOkzsE/1jAAAAAElFTkSuQmCC';
  final phoneFormKey = GlobalKey<FormState>();
  final imageFormKey = GlobalKey<FormState>();
  final codeFormKey = GlobalKey<FormState>();

  final Map<String, String> formValue = new HashMap();

  Timer _timer;

  Color LabelBlackColor = Color(0xFF1A1A1A);

  
  void dispose() {
    super.dispose();
    if (_timer != null) {
      _timer.cancel();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('登录示例demo'),
      ),
      body: buildBody(),
    );
  }

  sendCode() async {
    _seconds = 60;
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (_seconds == 0) {
        timer.cancel(); // 取消重复计时
        return;
      }
      _seconds--;
      if (mounted) {
        setState(() {});
      }
    });
  }

  Widget buildBody() {
    return Container(
      padding: EdgeInsets.only(right: 20, top: 20),
      child: ListView(
        children: [
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Text(
              '快捷登录注册',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 26),
            ),
          ),
          SizedBox(height: 30),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: phoneFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '+86',
                        style: TextStyle(
                            fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                    ),
                    hintText: '请输入手机账号',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                  ),
                  onSaved: (mobile) {
                    if (mobile == null || mobile.isEmpty == true) {
                      return;
                    }
                    formValue['mobile'] = mobile;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: codeFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入图形验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_loadingImageUrl) {
                          return;
                        } else {
                          _loadingImageUrl = true;
                          if (mounted) {
                            setState(() {});
                          }
                        }
                      },
                      child: Container(
                        width: 100,
                        height: 30,
                        child: Image.memory(
                          base64Decode(_imageUrl
                              .split(',')[1]
                              .replaceAll('\r', '')
                              .replaceAll('\n', '')),
                          width: 100,
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['imageCode'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: imageFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入短信验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_seconds == 0) {
                          phoneFormKey.currentState.save();
                          codeFormKey.currentState.save();
                          sendCode();
                        }
                      },
                      child: Center(
                        widthFactor: 1,
                        child: Text(
                          _seconds == 0 ? '获取验证码' : '$_seconds秒',
                          style: TextStyle(fontSize: 16),
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['code'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: GestureDetector(
              onTap: () {
                phoneFormKey.currentState.save();
                imageFormKey.currentState.save();
              },
              behavior: HitTestBehavior.opaque,
              child: Container(
                height: 48,
                color: LabelBlackColor,
                alignment: Alignment.center,
                child: Text(
                  '登录/注册',
                  style: TextStyle(fontSize: 16, color: Colors.white),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

二、局部刷新

此时我们需要仅仅刷新倒计时组件即可,也就是使用局部刷新来解决问题

1、抽离成组件state(刷新部分抽离)

与React类似,咱们把想刷新的部分抽离成组件,因为React、Flutter的state都是针对整个class的。

1.1、页面调用子组件方法

这里需要用到父组件调用子组件的发送验证码方法,

首先在子组件中定义key

GlobalKey<_CutdownTimeWidgetState> childKey = GlobalKey();

同时子组件的key要通过super传递给父类,这个不能漏

CutdownTimeWidget({
  Key key,
}) : super(key: key);

然后父组件把定义的key导包进来作为入参即可

CutdownTimeWidget(key: childKey)

最后可以方便的调用子组件方法

if (childKey.currentState?.seconds == 0) {
  childKey.currentState?.sendCode();
}

1.2、组件源码

把刷新相关的功能集成到子组件cutdown_time_widge.dart

import 'dart:async';

import 'package:flutter/material.dart';

GlobalKey<_CutdownTimeWidgetState> childKey = GlobalKey();

class CutdownTimeWidget extends StatefulWidget {
  CutdownTimeWidget({
    Key key,
  }) : super(key: key);

  
  State<StatefulWidget> createState() {
    return _CutdownTimeWidgetState();
  }
}

class _CutdownTimeWidgetState extends State<CutdownTimeWidget> {
  int seconds = 0;
  Timer _timer;

  
  void dispose() {
    super.dispose();
    if (_timer != null) {
      _timer.cancel();
    }
  }

  sendCode() async {
    seconds = 60;
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (seconds == 0) {
        timer.cancel(); // 取消重复计时
        return;
      }
      seconds--;
      if (mounted) {
        setState(() {});
      }
    });
  }

  
  Widget build(BuildContext context) {
    return Text(
      seconds == 0 ? '获取验证码' : '$seconds秒',
      style: TextStyle(fontSize: 16),
    );
  }
}

1.3、页面源码

页面login_demo_page.dart直接调用组件的方法,即实现了局部刷新解决闪烁问题。

import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:scroll_tabbar_sample/widgets/cutdown_time_widge.dart';

class LoginDemoPage extends StatefulWidget {
  LoginDemoPage({Key key});

  
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginDemoPage> {
  bool _loadingImageUrl = true;
  String _imageUrl =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAaCAIAAABw/jbvAAABjElEQVR42s2Y7Q6DIAxF2dxm9v4P7JaYYNeP2wJFZ/ihFS2HlrZQtoGrlDL7W7VbFcq3VLLfl23sGoR0P7c6xCGvJIxwuq8sIx9TUKXv17029Y9Wh3FIwBmBx3buJHw8V9lUfUt57Q0M5basoLkehGdBJ5SQLp4FaRHS+27CiAf1E9K3AJISAkgmiRO6kArh9wMGWR8rofwRI6yKmwirsOJFFnkzIUVSCdWFKgl33YDQGhw1YHfaRLE0eM9+pxJKG7IYiAlTMplOSB2VIlmEch0GCdXpP5WQIamEFE+aRRIyNta/1T/9NGsRqjxMoqYK5n4uoZU50moJa/SuxMJjnE2E0j8TqkI1W1hhykr6QIFKKMMMSxKJVSHyUosQ1DQg0sjGtIAsP7SD6ahrZxDiGHM2YUoAwHuCRKWlo3LPIuze2qcR5mrqhnRL2WQvdXfW6T6cFkubIC804ywv5cc7/034M9rgaU9k1zPjOMs9p3APfkpcx+CKDx4r5TpqdNDHfHhbu+DUgmvQjLLbBy9uOkzsE/1jAAAAAElFTkSuQmCC';
  final phoneFormKey = GlobalKey<FormState>();
  final imageFormKey = GlobalKey<FormState>();
  final codeFormKey = GlobalKey<FormState>();

  final Map<String, String> formValue = new HashMap();

  Color LabelBlackColor = Color(0xFF1A1A1A);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('登录示例demo'),
      ),
      body: buildBody(),
    );
  }

  Widget buildBody() {
    return Container(
      padding: EdgeInsets.only(right: 20, top: 20),
      child: ListView(
        children: [
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Text(
              '快捷登录注册',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 26),
            ),
          ),
          SizedBox(height: 30),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: phoneFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '+86',
                        style: TextStyle(
                            fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                    ),
                    hintText: '请输入手机账号',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                  ),
                  onSaved: (mobile) {
                    if (mobile == null || mobile.isEmpty == true) {
                      return;
                    }
                    formValue['mobile'] = mobile;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: codeFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入图形验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_loadingImageUrl) {
                          return;
                        } else {
                          _loadingImageUrl = true;
                          if (mounted) {
                            setState(() {});
                          }
                        }
                      },
                      child: Container(
                        width: 100,
                        height: 30,
                        child: Image.memory(
                          base64Decode(_imageUrl
                              .split(',')[1]
                              .replaceAll('\r', '')
                              .replaceAll('\n', '')),
                          width: 100,
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['imageCode'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: imageFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入短信验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (childKey.currentState?.seconds == 0) {
                          phoneFormKey.currentState.save();
                          codeFormKey.currentState.save();
                          childKey.currentState?.sendCode();
                        }
                      },
                      child: Center(
                        widthFactor: 1,
                        child: CutdownTimeWidget(key: childKey),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['code'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: GestureDetector(
              onTap: () {
                phoneFormKey.currentState.save();
                imageFormKey.currentState.save();
              },
              behavior: HitTestBehavior.opaque,
              child: Container(
                height: 48,
                color: LabelBlackColor,
                alignment: Alignment.center,
                child: Text(
                  '登录/注册',
                  style: TextStyle(fontSize: 16, color: Colors.white),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

2、使用ValueNotifier(通知方式)

通过抽离组件确实能实现局部刷新,但是书写臃肿。

ValueNotifier的方式为通知的方式

2.1、ValueNotifier使用

首先使用ValueNotifier对象替换之前定义的int _seconds = 0;

ValueNotifier secondNotifier = ValueNotifier<int>(0);

对于需要操作_seconds变量的改为操作secondNotifier.value

secondNotifier.value = 60;

最后对于绑定视图的组件使用ValueListenableBuilder包裹起来即可

ValueListenableBuilder(
  valueListenable: secondNotifier,
  builder:
      (BuildContext context, value, Widget child) {
    return Text(
      value == 0 ? '获取验证码' : '$value秒',
      style: TextStyle(fontSize: 16),
    );
  },
)

2.2、页面源码

import 'dart:async';
import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';

class LoginDemoPage extends StatefulWidget {
  LoginDemoPage({Key key});

  
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginDemoPage> {
  ValueNotifier secondNotifier = ValueNotifier<int>(0);
  bool _loadingImageUrl = true;
  String _imageUrl =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAaCAIAAABw/jbvAAABjElEQVR42s2Y7Q6DIAxF2dxm9v4P7JaYYNeP2wJFZ/ihFS2HlrZQtoGrlDL7W7VbFcq3VLLfl23sGoR0P7c6xCGvJIxwuq8sIx9TUKXv17029Y9Wh3FIwBmBx3buJHw8V9lUfUt57Q0M5basoLkehGdBJ5SQLp4FaRHS+27CiAf1E9K3AJISAkgmiRO6kArh9wMGWR8rofwRI6yKmwirsOJFFnkzIUVSCdWFKgl33YDQGhw1YHfaRLE0eM9+pxJKG7IYiAlTMplOSB2VIlmEch0GCdXpP5WQIamEFE+aRRIyNta/1T/9NGsRqjxMoqYK5n4uoZU50moJa/SuxMJjnE2E0j8TqkI1W1hhykr6QIFKKMMMSxKJVSHyUosQ1DQg0sjGtIAsP7SD6ahrZxDiGHM2YUoAwHuCRKWlo3LPIuze2qcR5mrqhnRL2WQvdXfW6T6cFkubIC804ywv5cc7/034M9rgaU9k1zPjOMs9p3APfkpcx+CKDx4r5TpqdNDHfHhbu+DUgmvQjLLbBy9uOkzsE/1jAAAAAElFTkSuQmCC';
  final phoneFormKey = GlobalKey<FormState>();
  final imageFormKey = GlobalKey<FormState>();
  final codeFormKey = GlobalKey<FormState>();

  final Map<String, String> formValue = new HashMap();

  Timer _timer;

  Color LabelBlackColor = Color(0xFF1A1A1A);

  
  void dispose() {
    super.dispose();
    if (_timer != null) {
      _timer.cancel();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('登录示例demo'),
      ),
      body: buildBody(),
    );
  }

  sendCode() async {
    secondNotifier.value = 60;
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (secondNotifier.value == 0) {
        timer.cancel(); // 取消重复计时
        return;
      }
      secondNotifier.value--;
    });
  }

  Widget buildBody() {
    return Container(
      padding: EdgeInsets.only(right: 20, top: 20),
      child: ListView(
        children: [
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Text(
              '快捷登录注册',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 26),
            ),
          ),
          SizedBox(height: 30),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: phoneFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '+86',
                        style: TextStyle(
                            fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                    ),
                    hintText: '请输入手机账号',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                  ),
                  onSaved: (mobile) {
                    if (mobile == null || mobile.isEmpty == true) {
                      return;
                    }
                    formValue['mobile'] = mobile;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: codeFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入图形验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_loadingImageUrl) {
                          return;
                        } else {
                          _loadingImageUrl = true;
                        }
                      },
                      child: Container(
                        width: 100,
                        height: 30,
                        child: Image.memory(
                          base64Decode(_imageUrl
                              .split(',')[1]
                              .replaceAll('\r', '')
                              .replaceAll('\n', '')),
                          width: 100,
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['imageCode'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: imageFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入短信验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (secondNotifier.value == 0) {
                          phoneFormKey.currentState.save();
                          codeFormKey.currentState.save();
                          sendCode();
                        }
                      },
                      child: Center(
                        widthFactor: 1,
                        child: ValueListenableBuilder(
                          valueListenable: secondNotifier,
                          builder:
                              (BuildContext context, value, Widget child) {
                            return Text(
                              value == 0 ? '获取验证码' : '$value秒',
                              style: TextStyle(fontSize: 16),
                            );
                          },
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['code'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: GestureDetector(
              onTap: () {
                phoneFormKey.currentState.save();
                imageFormKey.currentState.save();
              },
              behavior: HitTestBehavior.opaque,
              child: Container(
                height: 48,
                color: LabelBlackColor,
                alignment: Alignment.center,
                child: Text(
                  '登录/注册',
                  style: TextStyle(fontSize: 16, color: Colors.white),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

3、使用Stream的(通知方式)

3.1、StreamController使用

声明StreamController对象

StreamController<String> secondStreamController= StreamController<String>();

对于需要操作_seconds变量不变,操作完成之后需要刷新视图的用secondStreamController.add即可,注意接受参数需要用toString()字符串化。

secondStreamController.add(_seconds.toString());

最后对于绑定视图的组件使用StreamBuilder包裹起来即可

StreamBuilder(
  stream: secondStreamController.stream,
  initialData: _seconds,
  builder: (context, AsyncSnapshot snapshot) {
    return Text(
      snapshot.data == 0 ? '获取验证码' : '${snapshot.data}秒',
      style: TextStyle(fontSize: 16),
    );
  },
)

3.2、页面源码

login_demo_page.dart

import 'dart:async';
import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';

class LoginDemoPage extends StatefulWidget {
  LoginDemoPage({Key key});

  
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginDemoPage> {
  int _seconds = 0;
  StreamController<String> secondStreamController= StreamController<String>();
  bool _loadingImageUrl = true;
  String _imageUrl =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAaCAIAAABw/jbvAAABjElEQVR42s2Y7Q6DIAxF2dxm9v4P7JaYYNeP2wJFZ/ihFS2HlrZQtoGrlDL7W7VbFcq3VLLfl23sGoR0P7c6xCGvJIxwuq8sIx9TUKXv17029Y9Wh3FIwBmBx3buJHw8V9lUfUt57Q0M5basoLkehGdBJ5SQLp4FaRHS+27CiAf1E9K3AJISAkgmiRO6kArh9wMGWR8rofwRI6yKmwirsOJFFnkzIUVSCdWFKgl33YDQGhw1YHfaRLE0eM9+pxJKG7IYiAlTMplOSB2VIlmEch0GCdXpP5WQIamEFE+aRRIyNta/1T/9NGsRqjxMoqYK5n4uoZU50moJa/SuxMJjnE2E0j8TqkI1W1hhykr6QIFKKMMMSxKJVSHyUosQ1DQg0sjGtIAsP7SD6ahrZxDiGHM2YUoAwHuCRKWlo3LPIuze2qcR5mrqhnRL2WQvdXfW6T6cFkubIC804ywv5cc7/034M9rgaU9k1zPjOMs9p3APfkpcx+CKDx4r5TpqdNDHfHhbu+DUgmvQjLLbBy9uOkzsE/1jAAAAAElFTkSuQmCC';
  final phoneFormKey = GlobalKey<FormState>();
  final imageFormKey = GlobalKey<FormState>();
  final codeFormKey = GlobalKey<FormState>();

  final Map<String, String> formValue = new HashMap();

  Timer _timer;

  Color LabelBlackColor = Color(0xFF1A1A1A);

  
  void dispose() {
    super.dispose();
    if (_timer != null) {
      _timer.cancel();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('登录示例demo'),
      ),
      body: buildBody(),
    );
  }

  sendCode() async {
    _seconds = 60;
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (_seconds == 0) {
        timer.cancel(); // 取消重复计时
        return;
      }
      _seconds--;
      secondStreamController.add(_seconds.toString());
    });
  }

  Widget buildBody() {
    return Container(
      padding: EdgeInsets.only(right: 20, top: 20),
      child: ListView(
        children: [
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Text(
              '快捷登录注册',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 26),
            ),
          ),
          SizedBox(height: 30),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: phoneFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '+86',
                        style: TextStyle(
                            fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                    ),
                    hintText: '请输入手机账号',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                  ),
                  onSaved: (mobile) {
                    if (mobile == null || mobile.isEmpty == true) {
                      return;
                    }
                    formValue['mobile'] = mobile;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: codeFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入图形验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_loadingImageUrl) {
                          return;
                        } else {
                          _loadingImageUrl = true;
                        }
                      },
                      child: Container(
                        width: 100,
                        height: 30,
                        child: Image.memory(
                          base64Decode(_imageUrl
                              .split(',')[1]
                              .replaceAll('\r', '')
                              .replaceAll('\n', '')),
                          width: 100,
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['imageCode'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: imageFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入短信验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_seconds == 0) {
                          phoneFormKey.currentState.save();
                          codeFormKey.currentState.save();
                          sendCode();
                        }
                      },
                      child: Center(
                        widthFactor: 1,
                        child: StreamBuilder(
                          stream: secondStreamController.stream,
                          initialData: _seconds,
                          builder: (context, AsyncSnapshot snapshot) {
                            return Text(
                              snapshot.data == 0 ? '获取验证码' : '${snapshot.data}秒',
                              style: TextStyle(fontSize: 16),
                            );
                          },
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['code'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: GestureDetector(
              onTap: () {
                phoneFormKey.currentState.save();
                imageFormKey.currentState.save();
              },
              behavior: HitTestBehavior.opaque,
              child: Container(
                height: 48,
                color: LabelBlackColor,
                alignment: Alignment.center,
                child: Text(
                  '登录/注册',
                  style: TextStyle(fontSize: 16, color: Colors.white),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

4、使用InHeritedWidget(数据共享方式)

InheritedWidget 组件是功能型组件,提供了沿树向下,共享数据的功能,即子组件可以获取父组件(InheritedWidget 的子类)的数据

InHeritedWidget的使用固定,可用于共享state

4.1、InHeritedWidget使用

首先我们需要创建我们的共享数据类TimerInheritedWidget类,写法相对固定,这里seconds即是我们的变量

timer_inherited_widget.dart

class TimerInheritedWidget extends InheritedWidget {
  final int seconds;

  TimerInheritedWidget(this.seconds, Widget child) : super(child: child);

  static TimerInheritedWidget of(BuildContext context, {bool rebuild = true}) {
    if (rebuild) {
      final TimerInheritedWidget widget=context.dependOnInheritedWidgetOfExactType<TimerInheritedWidget>();
      return widget;
    }
    return context.findAncestorWidgetOfExactType<TimerInheritedWidget>();
  }

  
  bool updateShouldNotify(TimerInheritedWidget old) {
    return seconds != old.seconds;
  }
}

然后使用在页面中使用我们的组件,通过builder即可实现局部刷新

TimerInheritedWidget(
    _seconds,
    Builder(
      builder: (BuildContext innerContext) {
        return Container(
            child:Text(
              _seconds == 0 ? '获取验证码' : '$_seconds秒',
              style: TextStyle(fontSize: 16),
            )
        );
      },
    )
)

如果要获取共享state中的值(注意:这里是上下文必须是build的上下文,即innerContext)

TimerInheritedWidget.of(innerContext).seconds

4.2、页面源码

import 'dart:async';
import 'dart:collection';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:scroll_tabbar_sample/widgets/timer_inherited_widget.dart';

class LoginDemoPage extends StatefulWidget {
  LoginDemoPage({Key key});

  
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginDemoPage> {
  int _seconds = 0;
  StreamController<String> secondStreamController = StreamController<String>();
  bool _loadingImageUrl = true;
  String _imageUrl =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAaCAIAAABw/jbvAAABjElEQVR42s2Y7Q6DIAxF2dxm9v4P7JaYYNeP2wJFZ/ihFS2HlrZQtoGrlDL7W7VbFcq3VLLfl23sGoR0P7c6xCGvJIxwuq8sIx9TUKXv17029Y9Wh3FIwBmBx3buJHw8V9lUfUt57Q0M5basoLkehGdBJ5SQLp4FaRHS+27CiAf1E9K3AJISAkgmiRO6kArh9wMGWR8rofwRI6yKmwirsOJFFnkzIUVSCdWFKgl33YDQGhw1YHfaRLE0eM9+pxJKG7IYiAlTMplOSB2VIlmEch0GCdXpP5WQIamEFE+aRRIyNta/1T/9NGsRqjxMoqYK5n4uoZU50moJa/SuxMJjnE2E0j8TqkI1W1hhykr6QIFKKMMMSxKJVSHyUosQ1DQg0sjGtIAsP7SD6ahrZxDiGHM2YUoAwHuCRKWlo3LPIuze2qcR5mrqhnRL2WQvdXfW6T6cFkubIC804ywv5cc7/034M9rgaU9k1zPjOMs9p3APfkpcx+CKDx4r5TpqdNDHfHhbu+DUgmvQjLLbBy9uOkzsE/1jAAAAAElFTkSuQmCC';
  final phoneFormKey = GlobalKey<FormState>();
  final imageFormKey = GlobalKey<FormState>();
  final codeFormKey = GlobalKey<FormState>();

  final Map<String, String> formValue = new HashMap();

  Timer _timer;

  Color LabelBlackColor = Color(0xFF1A1A1A);

  
  void dispose() {
    super.dispose();
    if (_timer != null) {
      _timer.cancel();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('登录示例demo'),
      ),
      body: buildBody(),
    );
  }

  sendCode() async {
    setState(() {
      _seconds = 60;
    });
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (_seconds == 0) {
        timer.cancel(); // 取消重复计时
        return;
      }
      setState(() {
        _seconds--;
      });
      secondStreamController.add(_seconds.toString());
    });
  }

  Widget buildBody() {
    return TimerInheritedWidget(
        _seconds,
        Builder(
          builder: (BuildContext innerContext) {
            return Container(
              padding: EdgeInsets.only(right: 20, top: 20),
              child: ListView(
                children: [
                  Padding(
                    padding: EdgeInsets.only(left: 20),
                    child: Text(
                      '快捷登录注册',
                      style: TextStyle(fontWeight: FontWeight.w600, fontSize: 26),
                    ),
                  ),
                  SizedBox(height: 30),
                  Padding(
                    padding: EdgeInsets.only(left: 20),
                    child: Form(
                      key: phoneFormKey,
                      child: ConstrainedBox(
                        constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                        ),
                        child: TextFormField(
                          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                          decoration: InputDecoration(
                            contentPadding:
                            const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                            fillColor: Colors.white,
                            focusedBorder: UnderlineInputBorder(
                              borderSide: BorderSide(color: LabelBlackColor, width: 1),
                            ),
                            enabledBorder: UnderlineInputBorder(
                              borderSide: BorderSide(
                                color: Color(0xFFF5F5F5),
                                width: 1.0,
                              ),
                            ),
                            border: UnderlineInputBorder(
                              borderSide: BorderSide(
                                color: Color(0xFFF5F5F5),
                                width: 1.0,
                              ),
                            ),
                            prefixIcon: Container(
                              width: 65,
                              alignment: Alignment.centerLeft,
                              child: Text(
                                '+86',
                                style: TextStyle(
                                    fontSize: 20, fontWeight: FontWeight.bold),
                              ),
                            ),
                            hintText: '请输入手机账号',
                            hintStyle:
                            TextStyle(fontSize: 16, color: Color(0xFF999999)),
                          ),
                          onSaved: (mobile) {
                            if (mobile == null || mobile.isEmpty == true) {
                              return;
                            }
                            formValue['mobile'] = mobile;
                          },
                        ),
                      ),
                    ),
                  ),
                  SizedBox(height: 20),
                  Padding(
                    padding: EdgeInsets.only(left: 20),
                    child: Form(
                      key: codeFormKey,
                      child: ConstrainedBox(
                        constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                        ),
                        child: TextFormField(
                          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                          decoration: InputDecoration(
                            contentPadding:
                            const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                            fillColor: Colors.white,
                            focusedBorder: UnderlineInputBorder(
                              borderSide: BorderSide(color: LabelBlackColor, width: 1),
                            ),
                            enabledBorder: UnderlineInputBorder(
                              borderSide: BorderSide(
                                color: Color(0xFFF5F5F5),
                                width: 1.0,
                              ),
                            ),
                            border: UnderlineInputBorder(
                              borderSide: BorderSide(
                                color: Color(0xFFF5F5F5),
                                width: 1.0,
                              ),
                            ),
                            prefixIcon: Container(
                              width: 65,
                              alignment: Alignment.centerLeft,
                              child: Text(
                                '验证码',
                                style: TextStyle(
                                    fontSize: 16, fontWeight: FontWeight.w500),
                              ),
                            ),
                            hintText: '请输入图形验证码',
                            hintStyle:
                            TextStyle(fontSize: 16, color: Color(0xFF999999)),
                            suffixIcon: GestureDetector(
                              behavior: HitTestBehavior.opaque,
                              onTap: () {
                                if (_loadingImageUrl) {
                                  return;
                                } else {
                                  _loadingImageUrl = true;
                                }
                              },
                              child: Container(
                                width: 100,
                                height: 30,
                                child: Image.memory(
                                  base64Decode(_imageUrl
                                      .split(',')[1]
                                      .replaceAll('\r', '')
                                      .replaceAll('\n', '')),
                                  width: 100,
                                ),
                              ),
                            ),
                          ),
                          onSaved: (text) {
                            if (text == null || text.isEmpty == true) {
                              return;
                            }
                            formValue['imageCode'] = text;
                          },
                        ),
                      ),
                    ),
                  ),
                  SizedBox(height: 20),
                  Padding(
                    padding: EdgeInsets.only(left: 20),
                    child: Form(
                      key: imageFormKey,
                      child: ConstrainedBox(
                        constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                        ),
                        child: TextFormField(
                          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                          decoration: InputDecoration(
                            contentPadding:
                            const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                            fillColor: Colors.white,
                            focusedBorder: UnderlineInputBorder(
                              borderSide: BorderSide(color: LabelBlackColor, width: 1),
                            ),
                            enabledBorder: UnderlineInputBorder(
                              borderSide: BorderSide(
                                color: Color(0xFFF5F5F5),
                                width: 1.0,
                              ),
                            ),
                            border: UnderlineInputBorder(
                              borderSide: BorderSide(
                                color: Color(0xFFF5F5F5),
                                width: 1.0,
                              ),
                            ),
                            prefixIcon: Container(
                              width: 65,
                              alignment: Alignment.centerLeft,
                              child: Text(
                                '验证码',
                                style: TextStyle(
                                    fontSize: 16, fontWeight: FontWeight.w500),
                              ),
                            ),
                            hintText: '请输入短信验证码',
                            hintStyle:
                            TextStyle(fontSize: 16, color: Color(0xFF999999)),
                            suffixIcon: GestureDetector(
                              behavior: HitTestBehavior.opaque,
                              onTap: () {
                                if (_seconds == 0) {
                                  phoneFormKey.currentState.save();
                                  codeFormKey.currentState.save();
                                  sendCode();
                                }
                              },
                              child: Center(
                                widthFactor: 1,
                                child: Text(
                                  _seconds == 0 ? '获取验证码' : '$_seconds秒',
                                  style: TextStyle(fontSize: 16),
                                ),
                              ),
                            ),
                          ),
                          onSaved: (text) {
                            if (text == null || text.isEmpty == true) {
                              return;
                            }
                            formValue['code'] = text;
                          },
                        ),
                      ),
                    ),
                  ),
                  SizedBox(height: 20),
                  Padding(
                    padding: EdgeInsets.only(left: 20),
                    child: GestureDetector(
                      onTap: () {
                        phoneFormKey.currentState.save();
                        imageFormKey.currentState.save();

                        debugPrint(
                            "_seconds:" +
                                TimerInheritedWidget.of(innerContext).seconds.toString());
                      },
                      behavior: HitTestBehavior.opaque,
                      child: Container(
                        height: 48,
                        color: LabelBlackColor,
                        alignment: Alignment.center,
                        child: Text(
                          '登录/注册',
                          style: TextStyle(fontSize: 16, color: Colors.white),
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            );
          },
        )
    );
  }
}

5、使用Provider(数据共享方式)

Provider其实就是对InHeritedWidget的封装,用法固定

5.1、Provider用法

  • step1: 先添加provider三方库依赖
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0
  • step2: 创建一个数据提供者ChangeNotifier。

我们先新建一个 CutdownTimeProvider,继承 ChangeNotifier,使之成为我们的数据提供者之一

cutdown_time_provider.dart

import 'package:flutter/foundation.dart';

class CutdownTimeProvider extends ChangeNotifier {
  int _seconds = 0;

  int get seconds => _seconds;

  void changeSeconds(seconds) {
    this._seconds = seconds;
    notifyListeners();
  }
}
  • step3: 页面使用provider数据的地方包裹

在要使用的地方使用ChangeNotifierProvider包裹,并且使用Selector控制是否刷新

ChangeNotifierProvider<CutdownTimeProvider>(
  create: (_) => CutdownTimeProvider(),
  builder: (context, widget) {
    return Selector<CutdownTimeProvider, int>(
      selector: (_, v) => v.seconds,
      builder: (_, data, child) {
        return buildBody(context);
      },
    );
  },
)

Step3: provider具体使用

声明CutdownTimeProvider的provider。

注意:这里的context一定要build声明周期的context

final provider = Provider.of<CutdownTimeProvider>(context, listen: false);

取数据

Text(
  provider.seconds == 0 ? '获取验证码' : '${provider.seconds}秒',
  style: TextStyle(fontSize: 16),
)

改变数据

至于这里改变为什么会局部刷新,是因为在数据提供者CutdownTimeProvider中调用了notifyListeners();方法

provider.changeSeconds(60);

5.2、页面源码

import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'widgets/cutdown_time_provider.dart';

class LoginDemoPage extends StatefulWidget {
  LoginDemoPage({Key key});

  
  State<StatefulWidget> createState() {
    return _LoginPageState();
  }
}

class _LoginPageState extends State<LoginDemoPage> {

  bool _loadingImageUrl = true;
  String _imageUrl =
      'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAAaCAIAAABw/jbvAAABjElEQVR42s2Y7Q6DIAxF2dxm9v4P7JaYYNeP2wJFZ/ihFS2HlrZQtoGrlDL7W7VbFcq3VLLfl23sGoR0P7c6xCGvJIxwuq8sIx9TUKXv17029Y9Wh3FIwBmBx3buJHw8V9lUfUt57Q0M5basoLkehGdBJ5SQLp4FaRHS+27CiAf1E9K3AJISAkgmiRO6kArh9wMGWR8rofwRI6yKmwirsOJFFnkzIUVSCdWFKgl33YDQGhw1YHfaRLE0eM9+pxJKG7IYiAlTMplOSB2VIlmEch0GCdXpP5WQIamEFE+aRRIyNta/1T/9NGsRqjxMoqYK5n4uoZU50moJa/SuxMJjnE2E0j8TqkI1W1hhykr6QIFKKMMMSxKJVSHyUosQ1DQg0sjGtIAsP7SD6ahrZxDiGHM2YUoAwHuCRKWlo3LPIuze2qcR5mrqhnRL2WQvdXfW6T6cFkubIC804ywv5cc7/034M9rgaU9k1zPjOMs9p3APfkpcx+CKDx4r5TpqdNDHfHhbu+DUgmvQjLLbBy9uOkzsE/1jAAAAAElFTkSuQmCC';
  final phoneFormKey = GlobalKey<FormState>();
  final imageFormKey = GlobalKey<FormState>();
  final codeFormKey = GlobalKey<FormState>();

  final Map<String, String> formValue = new HashMap();

  Timer _timer;

  Color LabelBlackColor = Color(0xFF1A1A1A);

  
  void dispose() {
    super.dispose();
    if (_timer != null) {
      _timer.cancel();
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('登录示例demo'),
      ),
      body: ChangeNotifierProvider<CutdownTimeProvider>(
        create: (_) => CutdownTimeProvider(),
        builder: (context, widget) {
          return Selector<CutdownTimeProvider, int>(
            selector: (_, v) => v.seconds,
            builder: (_, data, child) {
              return buildBody(context);
            },
          );
        },
      ),
    );
  }

  sendCode(BuildContext context) async {
    final provider = Provider.of<CutdownTimeProvider>(context, listen: false);
    provider.changeSeconds(60);
    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      if (provider.seconds == 0) {
        timer.cancel(); // 取消重复计时
        return;
      }
      provider.changeSeconds(provider.seconds - 1);
    });
  }

  Widget buildBody(BuildContext context) {
    final provider = Provider.of<CutdownTimeProvider>(context, listen: false);

    return Container(
      padding: EdgeInsets.only(right: 20, top: 20),
      child: ListView(
        children: [
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Text(
              '快捷登录注册',
              style: TextStyle(fontWeight: FontWeight.w600, fontSize: 26),
            ),
          ),
          SizedBox(height: 30),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: phoneFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '+86',
                        style: TextStyle(
                            fontSize: 20, fontWeight: FontWeight.bold),
                      ),
                    ),
                    hintText: '请输入手机账号',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                  ),
                  onSaved: (mobile) {
                    if (mobile == null || mobile.isEmpty == true) {
                      return;
                    }
                    formValue['mobile'] = mobile;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: codeFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入图形验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (_loadingImageUrl) {
                          return;
                        } else {
                          _loadingImageUrl = true;
                        }
                      },
                      child: Container(
                        width: 100,
                        height: 30,
                        child: Image.memory(
                          base64Decode(_imageUrl
                              .split(',')[1]
                              .replaceAll('\r', '')
                              .replaceAll('\n', '')),
                          width: 100,
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['imageCode'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: Form(
              key: imageFormKey,
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: 58, minHeight: 58
//                      maxWidth: 150,
                    ),
                child: TextFormField(
                  style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                  decoration: InputDecoration(
                    contentPadding:
                        const EdgeInsets.symmetric(vertical: 20, horizontal: 0),
                    fillColor: Colors.white,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: LabelBlackColor, width: 1),
                    ),
                    enabledBorder: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    border: UnderlineInputBorder(
                      borderSide: BorderSide(
                        color: Color(0xFFF5F5F5),
                        width: 1.0,
                      ),
                    ),
                    prefixIcon: Container(
                      width: 65,
                      alignment: Alignment.centerLeft,
                      child: Text(
                        '验证码',
                        style: TextStyle(
                            fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                    hintText: '请输入短信验证码',
                    hintStyle:
                        TextStyle(fontSize: 16, color: Color(0xFF999999)),
                    suffixIcon: GestureDetector(
                      behavior: HitTestBehavior.opaque,
                      onTap: () {
                        if (provider.seconds == 0) {
                          phoneFormKey.currentState.save();
                          codeFormKey.currentState.save();
                          sendCode(context);
                        }
                      },
                      child: Center(
                        widthFactor: 1,
                        child: Text(
                          provider.seconds == 0 ? '获取验证码' : '${provider.seconds}秒',
                          style: TextStyle(fontSize: 16),
                        ),
                      ),
                    ),
                  ),
                  onSaved: (text) {
                    if (text == null || text.isEmpty == true) {
                      return;
                    }
                    formValue['code'] = text;
                  },
                ),
              ),
            ),
          ),
          SizedBox(height: 20),
          Padding(
            padding: EdgeInsets.only(left: 20),
            child: GestureDetector(
              onTap: () {
                phoneFormKey.currentState.save();
                imageFormKey.currentState.save();
                debugPrint("_seconds:" + provider.seconds.toString());
              },
              behavior: HitTestBehavior.opaque,
              child: Container(
                height: 48,
                color: LabelBlackColor,
                alignment: Alignment.center,
                child: Text(
                  '登录/注册',
                  style: TextStyle(fontSize: 16, color: Colors.white),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

相关文章:

  • hive的数据库和表操作
  • 力扣第331场周赛题解
  • 【Java】Spring Cloud 教程
  • 44 计算概论与程序设计基础21h-北京大学
  • Android-Native开发系列之利用AAudio播放音频
  • 道路病害识别监测系统 CNN网络
  • 基于wxbot的微信聊天机器人
  • 【OLAP】ClickHouse学习笔记
  • Android 插件化中资源错乱的解决方案
  • 【游戏逆向】FPS网络游戏自动瞄准漏洞分析以及实现
  • crawlspider类的使用
  • unity实时渲染部分
  • 【Linux】冯诺依曼体系与操作系统(OS)概念
  • Trie树的构造与应用
  • softmaxsigmoid
  • 【每日一题Day109】LC1210穿过迷宫的最少移动次数 | BFS+dp
  • 基于蜣螂算法优化的广义神经网络(GRNN)预测-附代码
  • 基于linux5.15.5的IMX 参考手册 ---20
  • ((蓝桥杯 刷题全集)【备战(蓝桥杯)算法竞赛-第5天】( 从头开始重新做题,记录备战竞赛路上的每一道题 )距离蓝桥杯还有62天
  • 8、数据的拆分、筛选、加权与平均
  • 电加热油锅炉工作原理_电加热导油
  • 大型电蒸汽锅炉_工业电阻炉
  • 燃气蒸汽锅炉的分类_大连生物质蒸汽锅炉
  • 天津市维修锅炉_锅炉汽化处理方法
  • 蒸汽汽锅炉厂家_延安锅炉厂家
  • 山西热水锅炉厂家_酒店热水 锅炉
  • 蒸汽锅炉生产厂家_燃油蒸汽发生器
  • 燃煤锅炉烧热水_张家口 淘汰取缔燃煤锅炉
  • 生物质锅炉_炉
  • 锅炉天然气_天燃气热风炉