欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Flutter第一个应用

程序员文章站 2022-05-29 19:45:45
...

       这里是根据官方给的案例重新稍作整理,对基础工程以及界面交互等做一个初步的了解,具体知识点会在后面的文章做详细的介绍。通过本文可以了解到以下几点知识:

工程主要文件

       首先我们创建一个Flutter项目,项目名称单词首字母小写,下划线连接,例如first_project,而不是firstProject或者FirstProject。创建项目完成后,如下图。今天我们主要会用到两个文件lib/main.dart、pubspec.yaml。main.dart是main方法所在的文件,项目的入口,pubspec.yaml里面是对第三方Packages的引用依赖。

Flutter第一个应用       项目创建后,main.dart文件里面会默认生成一些构建简单页面的代码以及一些注释。连接上设备,run一下,可以看到效果,如下图。

Flutter第一个应用       这不是我们要的,我们只想要Hello World页面(#^.^#)。我们把main.dart里面的代码全部清掉,自己写一个Hello World页面,代码如下。导入flutter的material包,定义main方法并指定运行的入口页面类,定义一个入口页面类继承StatelessWidget,创建Material app,Material库的Scaffold提供了默认的导航栏appBar以及构成主页面的body属性。下面我们通过Widget来了解页面树结构的构建。
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new MaterialApp(
      title: "demo app",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Welcome to flutter"),
        ),
        body: new Center(
          child: new Text("Hello World"),
        ),
      ),
    );
  }

}
界面运行如下:
Flutter第一个应用

引用外部的Package

       这里以一个开源的软件包english_words来做个示例,这个包里包含了数千个常用的英文单词和一些功能。可以在pub.dartlang.org上查到很多有用的开源工具包。
       pubspec.yaml文件管理着Flutter应用程序的静态资源。在文件中添加english_words依赖如下:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0
       点击右上角的Packages get,将包拉到项目中。控制台输出如下表示完成。
--no-color packages get
Running "flutter packages get" in first_project...
Process finished with exit code 0
       将包导入到lib/main.dart中。
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
       下面我们在代码中使用这个包里面的WordPair类,生成随机的单词,代替Hello World,代码如下。红线是新加的代码,然后点一下工具栏的Flutter Hot Reload按钮,就可以直接热重载界面。生成随机单词代替了Hello World。
Flutter第一个应用Flutter第一个应用

使用有状态的Widget

       通过上面的代码可以看到,app继承了一个使它自己变成一个widget的StatelessWidget类。在Flutter里面,大多时候可以把一切都看作widget。widget主要方法是提供了一个build()方法,描述如何根据其他更低级别的widget来对这个widget进行展示。上面那段示例包含了Text child widget、Center widget,Center widget可以将它所有的子树都对齐到屏幕中心。
       Stateless widget是不可以改变的,这就意味着它的属性也不能改变,所有的值都是final修饰的。Stateful widget保持的状态在生命周期内可能会发生变化,要实现一个有状态的widget至少需要两个条件:1)一个StatefulWidget类;2)这个类创建一个State类的实例。StatefulWidget类本身是不可变的,但是State类可以存在整个widget生命周期中。
       此处的示例我们会在main.dart里创建一个有状态的widget类RandomWords继承StatefulWidget,再创建一个继承了State类的RandomWordsState类,然后在RandomWords类里实例化后者,RandomWordsState会保存RandomWords的状态,实现一个有状态的widget。代码如下。

class RandomWords extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new RandomWordsState();
  }
}

class RandomWordsState extends State<RandomWords>{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
  }
}
       下面将这个widget添加到我们的页面上去,在RandomWordsState的build里面描述要展示的内容。然后把这个widget展示到body里面去,展示的效果不变,代码如下:
class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new MaterialApp(
      title: "demo app",
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text("Welcome to flutter"),
        ),
        body: new Center(
          child: new RandomWords()
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget{
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return new RandomWordsState();
  }
}

class RandomWordsState extends State<RandomWords>{
  final wordPair = new WordPair.random();
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Text(wordPair.asPascalCase);
  }
}

       大部分操作都是在RandomWordsState里面完成的,在这里RandomWords不需要做太多事。

ListView的使用

       列表在页面布局里面也是很常见的,这里进一步扩展下RandomWordsState类,生成并展示词组列表。
       首先来定义一个保存推荐词组的数组,还有个字体样式。

final _suggestions = <WordPair>[];
final _fontSize = new TextStyle(fontSize: 16.0);
       创建一个_buildRow函数,传入参数WordPair,渲染ListView的每一行展示的widget树。
Widget _buildRow(WordPair pair){
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _fontSize,
      ),
    );
  }
       创建一个_buildSuggestions()函数,来构建ListView。ListView提供了一个itemBuilder属性,这是一个工厂build,并作为匿名函数回调。它有两个参数,context上下文和i行迭代器,对应数组里的每一个推荐词都会执行一次函数调用,迭代器从0开始,每调一次就会增加1。在本次示例里我会先添加20个推荐词到数组里面,然后渲染到ListView里面,_buildSuggestions代码如下。
Widget _buildSuggestions(){
  _suggestions.addAll(generateWordPairs().take(20));
  return new ListView.builder(
      itemBuilder: (context,i){
        return _buildRow(_suggestions[i]);
      }
  );
}
       这时候运行一下,发现列表出来了。但是上拉滑动的时候发现问题了,超出20个item之后还可以向下滚动,白屏,如图。
Flutter第一个应用       出现这种情况是因为ListView的builder构造器运行按需建立延时加载的view。只需要判断下迭代器,超出长度后不渲染view就可以了,代码如下。现在运行就正常了。
Widget _buildSuggestions(){
  _suggestions.addAll(generateWordPairs().take(20));
  return new ListView.builder(
      itemBuilder: (context,i){
        if(i < _suggestions.length){
          return _buildRow(_suggestions[i]);
        }
      }
  );
}
Flutter第一个应用       现在我们想给列表每一列加上一条分割线,在这里我们也把分割线当做一个row添加到ListView里面去,一行数据隔一行分割线,具体原来代码里有注释。
Widget _buildSuggestions(){
    _suggestions.addAll(generateWordPairs().take(20));
    return new ListView.builder(
        itemBuilder: (context,i){
          if(i.isOdd){return new Divider();}//如果i是奇数,就添加分割线,如果是偶数,才添加带数据的row
          final index = i ~/ 2;//效果和整除差不多,i为0、1、2、3、4、5...,算到的index为0、0、1、1、2、2...
          //index的计算,主要是因为插入了分割线以后,迭代器迭代的次数是原来的双倍,一半是渲染分割线,一半是渲染带数据的row
          if(index < _suggestions.length){
            return _buildRow(_suggestions[index]);
          }
        }
    );
  }
Flutter第一个应用

交互操作

       这里我们为每一行添加一个心型图,类似点赞,可以点赞和取消点赞的效果。首先我建一个Set集合来保存我们点过赞的单词,Set集合不允许重复数据,比List合适。

final _saved = new Set<WordPair>();
       在每一个row添加心型图,并声明一个aleardySaved判断是否被点赞。
Widget _buildRow(WordPair pair){
  final aleardySaved = _saved.contains(pair);//判断是否包含当前单词,判断是否被赞
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _fontSize,
    ),
    trailing: new Icon(
      aleardySaved ? Icons.favorite : Icons.favorite_border,
      color: aleardySaved ? Colors.red:null,
    ),
  );
}
Flutter第一个应用       此时点击还没有任何效果,还需要给每个row添加点击事件。
Widget _buildRow(WordPair pair){
  final aleardySaved = _saved.contains(pair);//判断是否包含当前单词,判断是否被赞
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _fontSize,
    ),
    trailing: new Icon(
      aleardySaved ? Icons.favorite : Icons.favorite_border,
      color: aleardySaved ? Colors.red:null,
    ),
    onTap: () => _favourite(pair),
  );
}

void _favourite(WordPair pair){
  setState((){
    if(_saved.contains(pair)){
      _saved.remove(pair);
    }else{
      _saved.add(pair);
    }
  });
}
       现在就达到我们要的效果,点击变红色并保存,再次点击变成默认的,并从set集合移除。
Flutter第一个应用

跳转到新页面

       在页面导航栏加一个按钮,跳转到新的页面,Navigator管理着包含了应用程序所有路由的堆栈,将一个路由push到堆栈,将显示更新到新的页面路由,将一个路由pull出堆栈,将返回显示前一个页面路由。

return new Scaffold(
    appBar: new AppBar(
      title: new Text("Welcome to flutter"),
      actions: <Widget>[new IconButton(icon: new Icon(Icons.list), onPressed: _pushNavi)],
    ),
    body:  _buildSuggestions()
);
void _pushNavi(){
    Navigator.of(context).push(
        new MaterialPageRoute(
            builder:(context){
              return new Scaffold(
                appBar: new AppBar(
                  title: new Text("Welcome to new page"),
                ),
                body: new Center(
                  child: new Text("new page"),
                ),
              );
            })
    );
  }
      效果如下:
Flutter第一个应用
Flutter第一个应用