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

每个flutter组件(Widget)都有一个内置的生命周期:所谓生命周期,就是一系列的由flutter在特定时间点会自动执行的方法,好像人的婴儿期,青年期,中年期,老年期一样。

有三个最重要的widget生命周期方法如下:

initState():在StatefulWidget的状态对象初始化完成之后,由flutter执行

build():当Widget第一次build的时候,或是setState()方法被调用时,由flutter调用此方法

dispose():就在该widget被删除之前,被flutter调用该方法

我们是否存在第二种方法,不去调用生命周期方法就能实现这个变量的初始化呢?

当然是有的:

1.与其将激活的屏幕作为变量存储起来,我们不如使用一种标记屏幕的标识符作为变量

位置:quiz.dart --> _QuizState() 里面的第一行

  Widget? activeScreen;

替换为:

  var activeScreen = 'start-screen';

我们借用字符串start-screen标记了我们需要激活的屏幕类,当然这个字符串可以由你自己命名。

2.删掉生命周期:initState()

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

3.修改switchScreen()方法内的参数

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

替换为:

  void switchScreen() {
    setState(() {
      activeScreen = 'questions-screen';
    });
  }

此处用字符串questions-screen替换QuestionScreen()这个类

4.利用三元表达式来根据条件显示屏幕

位置:Quiz.dart --> build()方法 --> child属性

child: activeScreen == 'start-screen'
   ? StartScreen(switchScreen)
   : const QuestionsScreen(),

所谓三元表达式,即语法为: 条件 ? 条件正确 : 条件错误

如上,如果此时activeScreenstart-screen,那么执行StartScreen(switchScreen),否则执行const QuestionsScreen()

保存下,重新执行,依然还是一样的。

这种利用标识符和三元表达式来实现屏幕切换的方法,相比用生命周期initState方法来说,代码量更少,而且无需使用到该生命周期initState(),看个人喜欢那种都可以。

-或者使用if来条件显示屏幕:

class _QuizState extends State<Quiz> {
  var activeScreen = 'start-screen';

  void switchScreen() {
    setState(() {
      activeScreen = 'questions-screen';
    });
  }

  @override
  Widget build(Object context) {
    Widget screenWidget = StartScreen(switchScreen);

    if (activeScreen == 'questions-screen') {
      screenWidget = const QuestionsScreen();
    }

    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: screenWidget,
        ),
      ),
    );
  }
}

一开始activeScreen变量中存储着'start-screen',紧接着当用户按下按钮触发switchScreen方法,此方法将新的'questions-screen'字符串覆盖进入activeScreen变量中,并且调用了setState方法,强制重新执行build方法,并触发if条件句,由于activeScreen变量已经变了,就会导致screenWidget这个变量也变了,就会导致屏幕切换为questions-screen。

5.开始整背单词答题卡

        I.所以我们需要问题以及答案的模板对吧?为了让问题和答案以一个显示模板显示出来

位置:在左侧根目录文件夹中--> 在lib文件夹下,新增一个models文件夹以放置该问题模板---->在models文件夹下面,新增一个quiz_question.dart文件,在这个文件下,我们开始构建这个数据结构。

class QuizQuestion {
  const QuizQuestion(this.text, this.answers);

  final String text;
  final List<String> answers;
}

解释:我们创建了答题卡的数据结构,text为每一个答题卡的标题,answer为每一个答题卡的答案,答案不止一个,它将由一个列表的数个字符串答案组成,其中一个为正常答案。

我们还创建了构造器QuizQuestion(this.text, this.answers),并在创造该答题卡数据结构的同时初始化这些数据。

        II.然后我们需要问题和答案作为数据

位置:在左侧根目录文件夹下-->在lib文件夹下,新增一个data文件夹以放置问题和答案数据--->在data文件夹下面,新增一个questions.dart,里面填入以下内容。

import 'package:adv_basics/models/quiz_question.dart';

const questions = [
  QuizQuestion('Apple', ['苹果', '梨子', '香蕉', '荔枝']),
  QuizQuestion('venison', ['鹿肉', '猪肉', '牛肉', '毒药']),
  QuizQuestion('Squab', ['鹿肉', '猪肉', '牛肉', '毒药']),
  QuizQuestion('flank', ['侧翼', '飞机', '牛排', '肚子']),
  QuizQuestion('heresy', ['异端', '后代', '继承', '酒']),
  QuizQuestion('coyote', ['郊狼', '浣熊', '麋鹿', '臭鼬']),
];

第一行:我引入了quiz_questions.dart,因为要用到quiz_question.dart中刚刚创建的数据结构。

第三行:question变量为一个列表,它接受另个数据结构也就是QuizQuestion()作为参数,QuizQuestion()的数据结构为第一个参数是标题,第二个参数为一个字符串列表,此为答案。这样就实现了我们的答题卡数据结构。

需要注意的是,第二个参数应该将正确答案放置在第一个位置上,但是之后我们会在实际使用中打乱这些答案的排序。

        III.调整下questions_screen.dart中的内容

位置:questions_screen.dart中,_QuestionScreenState类修改如下

class _QuestionScreenState extends State<QuestionsScreen> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('The question...'),
        const SizedBox(
          height: 30,
        ),
        ElevatedButton(onPressed: () {}, child: const Text('Answer 1')),
        ElevatedButton(onPressed: () {}, child: const Text('Answer 1')),
        ElevatedButton(onPressed: () {}, child: const Text('Answer 1')),
      ],
    );
  }
}

解释:我们将原来的return的Text组件修改为Column组件,因为我们要放置单词答题卡,从上往下为Text()组件显示标题,SizedBox()组件创造一个30的高度隔开标题和答案,接着是3个elevatedButton按钮组件以显示可点击答案。

效果如下:有点惨,不是吗? 它并没有占满整个空间

        IV.我们要借助SizedBox组件,来填满整个空间

位置:questions_screen.dart中 ---> _QuestionScreenState类中

在Column处点击ctrl+shift+R,会出现如下显示,选择Wrap with SizedBox

然后在SizedBox()下面,添加一个属性width,并赋值double.infinity,以取得最大宽度空间。

其次,我们在column()下面,添加一个属性mainAxisAlignment,并赋值MainAxisAlignment.center,使其在y轴上居中。

class _QuestionScreenState extends State<QuestionsScreen> {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity, //尽量多地占用空间
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center, // 使其在y轴上居中
        children: [
          const Text('The question...'),
          const SizedBox(
            height: 30,
          ),
          ElevatedButton(onPressed: () {}, child: const Text('Answer 1')),
          ElevatedButton(onPressed: () {}, child: const Text('Answer 1')),
          ElevatedButton(onPressed: () {}, child: const Text('Answer 1')),
        ],
      ),
    );
  }
}

我们在看下显示如何,如下,所以还不错。 

6.我们想将字体和样式改得美观些

 I.位置:questions_screen.dart中 ---> _QuestionScreenState类中

  在Text()组件内新增一个style属性,然后赋值TextStyle(color: Colors.white),就可以将标题的颜色改变了

同理可得,在ElevatedButton()组件内也可以新增style属性,然后如法炮制,但是如果ElevatedButton组件有很多呢,这个例子中只有3个,但如果其他项目可能有数十个,那么一个个复制黏贴就很麻烦了对吧?

所以,我们能不能自定义一个组件,然后重复利用它呢?

答案是当然可以,接下来,我们就来实现它吧。

II.位置:在lib文件夹下 ----> 创建一个answer_button.dart文件

import 'package:flutter/material.dart';

class AnswerButton extends StatelessWidget {
  const AnswerButton(
      {required this.answerText, required this.onTap, super.key});

  final String answerText;
  final void Function() onTap;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
        onPressed: onTap,
        style: ElevatedButton.styleFrom(),
        child: Text(answerText));
  }
}

第③行:因为这个AnswerButton无需任何变化,所以我们继承StatelessWidget;

第④行:创造一个构造器,并输入三个参数,第一个为answerText:显示内容;第二个为onTap:作为按钮按下去触发的方法;第三个是key:作为传向父类的一些必要信息。

第⑦行:此为按钮上显示的字符串

第⑧行:此为按钮按下去触发的方法,没有返回类型

第(12)行:该ElevatedButton()方法是从questions.screen.dart复制过来,并加上了style属性用来改良此按钮的外观,Text()方法中的answerText信息来自构造器,可以传参进来,onPressed的onTap方法可以来自构造器,从外界传参进来。

经过这样的改良,我们自定义了自己的按钮组件AnswerButton(),并且可以重复利用。


同时别忘了在questions_screen.dart中

之前的ElevatedButton应该修改为AnswerButton()

大致如下:

class _QuestionScreenState extends State<QuestionsScreen> {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity, //尽量多地占用空间
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center, // 使其在y轴上居中
        children: [
          const Text(
            'The question...',
            style: TextStyle(color: Colors.white),
          ),
          const SizedBox(
            height: 30,
          ),
          AnswerButton(answerText: 'Answer 1', onTap: () {}),
          AnswerButton(answerText: 'Answer 2', onTap: () {}),
          AnswerButton(answerText: 'Answer 3', onTap: () {}),
        ],
      ),
    );
  }
}

III.继续美化界面

位置:answer_button.dart中 ---->  build()  ----> style:ElevatedButton.styleFrom()中

style: ElevatedButton.styleFrom(
     padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 40),
     backgroundColor: const Color.fromARGB(255, 33, 1, 95),
     foregroundColor: Colors.white,
     shape: RoundedRectangleBorder(
     borderRadius: BorderRadius.circular(40),
   )),

第一个padding属性用于给按钮调整填充

第二个backgroundcolor属性用于按钮的背景色

第三个foregroundcolor属性用于按钮字体的颜色

第四个shape属性用于按钮的圆角外形

效果如下:

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值