Flutter第一个应用

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

工程主要文件

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

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

       这不是我们要的,我们只想要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"),
        ),
      ),
    );
  }

}
界面运行如下:

引用外部的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。

使用有状态的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之后还可以向下滚动,白屏,如图。
       出现这种情况是因为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]);
        }
      }
  );
}
       现在我们想给列表每一列加上一条分割线,在这里我们也把分割线当做一个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]);
          }
        }
    );
  }

交互操作

       这里我们为每一行添加一个心型图,类似点赞,可以点赞和取消点赞的效果。首先我建一个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,
    ),
  );
}
       此时点击还没有任何效果,还需要给每个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集合移除。

跳转到新页面

       在页面导航栏加一个按钮,跳转到新的页面,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"),
                ),
              );
            })
    );
  }
      效果如下:


猜你喜欢

转载自blog.csdn.net/liujibin1836591303/article/details/80236250