[Code with me]开发背单词App | Flutter框架 | 无需任何前置知识教程 #4

这节课中我将尝试在点击按钮时,跳转到一个新的界面上去。

1. 代码实现界面的更换

位置:quiz.dart---->  _QuizState类中


 a.一种尝试和犯错

         I.就在 _QuizState类下方,第一行添加如下代码

  var activeScreen = const StartScreen();

         效果:我们将StartScreen()类赋值给activeScreen变量

        II.就在 I 的代码之后,添加如下代码

  void switchScreen() {
    setState(() {
      activeScreen = const QuestionsScreen();
    });
  }

         这段代码是会出错的,因为根据var的自动推断,var会认为该变量为StartScreen()类型的,而StartScreen继承至StatelessWidget,但是QuestionsScreen()继承至StatefulWidget,所以会发生冲突。

b.a的纠错

        I. 与其用var自动推测,不如强制设定用Widget类,StatelessWidget和StatefulWidget都为Widget的子类

        修改a ---> I 中的代码为如下 

  Widget activeScreen = const StartScreen();

        这样就解决了这个问题! 

c.既然我们已经用变量来修改页面,那么就要舍弃之前的硬编码了

        位置:quiz.dart ---->  _QuizState类 ----> build()方法 ---> child属性

        修改为如下:

 child: activeScreen,

该Quiz.dart代码如下 

import 'package:adv_basics/questions.screen.dart';
import 'package:flutter/material.dart';
import 'package:adv_basics/start_screen.dart';

class Quiz extends StatefulWidget {
  const Quiz({super.key});

  @override
  State<Quiz> createState() {
    return _QuizState();
  }
}

class _QuizState extends State<Quiz> {
  Widget activeScreen = const StartScreen();

  void switchScreen() {
    setState(() {
      activeScreen = const QuestionsScreen();
    });
  }

  @override
  Widget build(Object context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          decoration: const BoxDecoration(
              gradient: LinearGradient(
            colors: [
              Color.fromARGB(255, 132, 84, 214),
              Color.fromARGB(255, 67, 17, 99)
            ],
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
          )),
          child: activeScreen,
        ),
      ),
    );
  }
}

 接下来,我们分析下,它会怎么运行:

①在启动app时,它找到activeScreen变量,里面存着StartScreen(),于是加载了StartScreen()

②如果switchScreen()方法被执行了,activeScreen变为了QuestionsScreen(),setState方法会触发flutter重新执行该类下的Widget build(Object context)方法

③flutter会对比旧的widget树和新的widget树的区别,并只更新发生了变化的地方,在这个例子中是child属性。

通过这样,flutter可以高效地更新UI,并且不浪费机能,因为它只更新变了的地方。


2.在按下按钮时,我们要能转换到一个新的界面

为了达到这个目的,引入一个概念叫做Lifting Up State(状态提升),这是一种设计模式。

大概意思就是将多个子组件的状态控制代码提升到同一个父组件上,在父组件上能更容易地控制子组件的状态。

 那么其实在上面的1.里面我们已经实现了状态提升,我们已经将如下,状态和状态控制代码放在了Quiz.dart中了

  Widget activeScreen = const StartScreen();

  void switchScreen() {
    setState(() {
      activeScreen = const QuestionsScreen();
    });
  }

问题在于,控制状态的switchScreen()代码放在Quiz.dart中,我们怎么在它的子部件中调用父部件的方法呢?

解决办法:在dart中,方法是可以当作参数来传的,我们可以将一个方法名作为参数传入另一个方法里。像这种可以传入一个函数作为参数的函数也叫做高阶函数。


 a.在StartScreen()的构造器中添加函数参数

位置:start_screen.dart ---> StartScreen构造器

既然按钮在StartScreen中,我们就得把switchScreen()传入其中。

        I.首先添加参数的名字,名字随便取,我取startQuiz,如下

const StartScreen(startQuiz,{super.key}); 

       II.在该名称前面添加Function,表明这是方法或函数

 const StartScreen(Function startQuiz,{super.key}); 

        III. 再添加void,表明这是个不返回任何类型的方法,在Function后面添加一个括号

const StartScreen(void Function() startQuiz,{super.key}); 

 这就是dart中,高阶函数的写法。

b.接下来,我们需要在StartScreen类中使用该函数

        I.构造器中的void Function() startQuiz参数是不能直接使用的,所以需要把它赋值给本地变量

        位置:start_screen.dart中,构造器下面

我们新增一个final修饰的Function变量,final表示在运行时才被赋值,且只能赋值一次

final void Function() startQuiz;

既然final保证了startQuiz必须被初始化,那么在构造器中startQuiz就可以去掉前面的头衔了。改用this.startQuiz了,它指向了下面这个startQuiz变量。

const StartScreen(this.startQuiz,{super.key}); 

final void Function() startQuiz;

         II.在按钮中的触发方法调用startQuiz()方法

        位置:start_screen.dart中 --> build()方法  ----> OutlinedButton.icon()方法下的onPressed属性

        有二种方法:

第一种为用匿名函数调用startQuiz()方法

OutlinedButton.icon(
    onPressed: () {
       startQuiz();
},

第二种为直接用startQuiz指针作为onPressed属性的参数

OutlinedButton.icon(
   onPressed: startQuiz,

3.引入initState()方法

注意到switchScreen作为参数放入也是报错的,为什么呢?

因为在这个_QuizState类中,在类被实例化的过程中,

  Widget activeScreen =  StartScreen(switchScreen);
  void switchScreen() {
    setState(() {
      activeScreen = const QuestionsScreen();
    });
  }

上述两块代码几乎就是同时生成的,编译器不能保证switchScreen()方法先于StartScreen()生成出来,并且能作为参数嵌入,所以就报警了。

解决办法就是initState()方法

此方法为flutter生命周期中的第一个方法,它会确保在变量和方法被创造之后,才调用此initState()方法并只调用一次,所以成为了常规初始化各种变量的绝佳地方,也保证了变量和方法在这个方法内是可用的。

稍微修改下代码如下:

  Widget? activeScreen;
  @override
  void initState() {
    super.initState();
    activeScreen = StartScreen(switchScreen);
  }

来试试把

在点击了Start Quiz,之后跳转到了questions_screen.dart文件中,在最左上角显示了QuestionScreen的文本控件。

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值