flutter學習--編寫簡單的app

創建 Flutter app

Android Studio上直接Flie->new->new Flutter Project創建一個應用
創建出來的項目結構如圖所示
在這裏插入圖片描述
在lib文件夾下,已經自動創建一個main.dart文件,

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.red,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  // This widget is the home page of your application. It is stateful, meaning
  // that it has a State object (defined below) that contains fields that affect
  // how it looks.

  // This class is the configuration for the state. It holds the values (in this
  // case the title) provided by the parent (in this case the App widget) and
  // used by the build method of the State. Fields in a Widget subclass are
  // always marked "final".

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // This call to setState tells the Flutter framework that something has
      // changed in this State, which causes it to rerun the build method below
      // so that the display can reflect the updated values. If we changed
      // _counter without calling setState(), then the build method would not be
      // called again, and so nothing would appear to happen.
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '添加button按鈕',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

運行main.dart後,出現
在這裏插入圖片描述
這樣一個簡單的flutter應用就創建好了。
接下來,嘗試開始自己寫一些代碼。

創建一個Hello world

在lib文件夾下新建一個dart文件,命名爲hello.dart

import 'package:flutter/material.dart';


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

class MyApp extends StatelessWidget{

  @override
  Widget build(BuildContext context) {//widget的主要工作是提供一個build()方法來描述如何根據其他較低級別的widget來顯示自己。
    return new MaterialApp(
      title: 'Hello,World',
      home: new Scaffold(//提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性
        appBar: new AppBar(
          title: new Text('我的第一個Flutter'),
        ),
        body: new Center(
          child: new Text('Hello,World!'),//Center widget可以將其子widget樹對其到屏幕中心。
        ),
      ),
    );
  }
}

運行hello.dart後,出現
在這裏插入圖片描述

分析

  • 本示例創建一個Material APP。Material是一種標準的移動端和web端的視覺設計語言。 Flutter提供了一套豐富的Material widgets。
  • main函數使用了(=>)符號, 這是Dart中單行函數或方法的簡寫。
  • 該應用程序繼承了 StatelessWidget,這將會使應用本身也成爲一個widget。 在Flutter中,大多數東西都是widget,包括對齊(alignment)、填充(padding)和佈局(layout)
  • Scaffold 是 Material library 中提供的一個widget, 它提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性。widget樹可以很複雜。
  • widget的主要工作是提供一個build()方法來描述如何根據其他較低級別的widget來顯示自己。
  • 本示例中的body的widget樹中包含了一個Center widget, Center widget又包含一個 Text 子widget。 Center widget可以將其子widget樹對其到屏幕中心。

使用外部包(package)

打開 pubspec.yaml 文件,在dependencies節點下添加english_words: ^3.1.0

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  english_words: ^3.1.0

點擊右上方Package get,加載依賴包。
在lib文件夾下,創建test1.dart文件

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

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

class MyApp extends StatelessWidget {


  @override
  Widget build(BuildContext context) {
    final word = WordPair.random();
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          //child: new Text('Hello World'),
          child: new Text(word.asPascalCase),
        ),
      ),
    );
  }
}

運行後出現
在這裏插入圖片描述

添加一個 有狀態的部件(Stateful widget)

Stateless widgets 是不可變的, 這意味着它們的屬性不能改變 - 所有的值都是最終的.

Stateful widgets 持有的狀態可能在widget生命週期中發生變化. 實現一個 stateful widget 至少需要兩個類:

1.一個 StatefulWidget類。
2.一個 State類。 StatefulWidget類本身是不變的,但是 State類在widget生命週期中始終存在.

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //widget的主要工作是提供一個build()方法來描述如何根據其他較低級別的widget來顯示自己。
    return new MaterialApp(
      title: 'Hello,World',
      home: new Scaffold(
        //提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性
        appBar: new AppBar(
          title: new Text('我的第一個Flutter'),
        ),
        body: new Center(
          child: new RandomWords(), //Center widget可以將其子widget樹對其到屏幕中心。
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  State createState() {
    return new RandomWordsState();
  }
}

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

這樣每次熱加載都會展示不同的文字
在這裏插入圖片描述

創建一個無限滾動ListView

擴展(繼承)RandomWordsState類,以生成並顯示單詞對列表。 當用戶滾動時,ListView中顯示的列表將無限增長。 ListView的builder工廠構造函數允許您按需建立一個懶加載的列表視圖。
1.向RandomWordsState類中添加一個_suggestions列表以保存建議的單詞對。 該變量以下劃線(_)開頭,在Dart語言中使用下劃線前綴標識符,會強制其變成私有的。
另外,添加一個biggerFont變量來增大字體大小

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

2.向RandomWordsState類添加一個 _buildSuggestions() 函數. 此方法構建顯示建議單詞對的ListView。

ListView類提供了一個builder屬性,itemBuilder 值是一個匿名回調函數, 接受兩個參數- BuildContext和行迭代器i。迭代器從0開始, 每調用一次該函數,i就會自增1,對於每個建議的單詞對都會執行一次。該模型允許建議的單詞對列表在用戶滾動時無限增長。

class RandomWordsState extends State<RandomWords> {
  ...
  Widget _buildSuggestions() {
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // 對於每個建議的單詞對都會調用一次itemBuilder,然後將單詞對添加到ListTile行中
      // 在偶數行,該函數會爲單詞對添加一個ListTile row.
      // 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
      // 注意,在小屏幕上,分割線看起來可能比較吃力。
      itemBuilder: (context, i) {
        // 在每一列之前,添加一個1像素高的分隔線widget
        if (i.isOdd) return new Divider();

        // 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i爲:1, 2, 3, 4, 5
        // 時,結果爲0, 1, 1, 2, 2, 這可以計算出ListView中減去分隔線後的實際單詞對數量
        final index = i ~/ 2;
        // 如果是建議列表中最後一個單詞對
        if (index >= _suggestions.length) {
          // ...接着再生成10個單詞對,然後添加到建議列表
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }
}

3.對於每一個單詞對,_buildSuggestions函數都會調用一次_buildRow。 這個函數在ListTile中顯示每個新詞對,這使您在下一步中可以生成更漂亮的顯示行

在RandomWordsState中添加一個_buildRow函數:

class RandomWordsState extends State<RandomWords> {
  ...

  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

4.更新RandomWordsState的build方法以使用_buildSuggestions(),而不是直接調用單詞生成庫。

@override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("ListView"),
      ),
      body: _buildSuggestions(),
    );
  }

5.更新MyApp的build方法。從MyApp中刪除Scaffold和AppBar實例。 這些將由RandomWordsState管理,這使得用戶在下一步中從一個屏幕導航到另一個屏幕時, 可以更輕鬆地更改導航欄中的的路由名稱。

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}

總體代碼:

import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';


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

class MyApp extends StatelessWidget{


  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}

class RandomWords extends StatefulWidget{

  @override
  State createState() {
    return new RandomWordsState();
  }
}

class RandomWordsState extends State<RandomWords>{

  final _suggestion = <WordPair>[];
  final _biggerFont = const TextStyle(fontSize: 18.0);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("ListView"),
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions(){
    return new ListView.builder(
      padding: const EdgeInsets.all(16.0),
      // 對於每個建議的單詞對都會調用一次itemBuilder,然後將單詞對添加到ListTile行中
      // 在偶數行,該函數會爲單詞對添加一個ListTile row.
      // 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
      // 注意,在小屏幕上,分割線看起來可能比較吃力。
      itemBuilder: (context,i){
        // 在每一列之前,添加一個1像素高的分隔線widget
        if (i.isOdd) return new Divider();
        // 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i爲:1, 2, 3, 4, 5
        // 時,結果爲0, 1, 1, 2, 2, 這可以計算出ListView中減去分隔線後的實際單詞對數量
        final index = i~/2;
        // 如果是建議列表中最後一個單詞對
        if(index >= _suggestion.length){
          // ...接着再生成10個單詞對,然後添加到建議列表
          _suggestion.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestion[index]);
      },
    );
  }

  Widget _buildRow(WordPair pair){
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }

}

效果如圖:
在這裏插入圖片描述

添加交互

爲每一行添加一個可點擊的心形 ❤️ 圖標。當用戶點擊列表中的條目,切換其“收藏”狀態時,將該詞對添加到或移除出“收藏夾”。

1.添加一個 _saved Set(集合) 到RandomWordsState。這個集合存儲用戶喜歡(收藏)的單詞對。 在這裏,Set比List更合適,因爲Set中不允許重複的值。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _saved = new Set<WordPair>();

  final _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

2.在 _buildRow 方法中添加 alreadySaved來檢查確保單詞對還沒有添加到收藏夾中。

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  ...
}

3.同時在 _buildRow()中, 添加一個心形 ❤️ 圖標到 ListTiles以啓用收藏功能。接下來,你就可以給心形 ❤️ 圖標添加交互能力了。

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: new Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
  );
}

4.重新啓動應用。你現在可以在每一行看到心形❤️圖標️,但它們還沒有交互。

5.在 _buildRow中讓心形❤️圖標變得可以點擊。如果單詞條目已經添加到收藏夾中, 再次點擊它將其從收藏夾中刪除。當心形❤️圖標被點擊時,函數調用setState()通知框架狀態已經改變。

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: new Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
    onTap: () {
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else {
          _saved.add(pair);
        }
      });
    },
  );
}

在這裏插入圖片描述

導航到新頁面

添加一個顯示收藏夾內容的新頁面(在Flutter中稱爲路由(route))
在Flutter中,導航器管理應用程序的路由棧。將路由推入(push)到導航器的棧中,將會顯示更新爲該路由頁面。 從導航器的棧中彈出(pop)路由,將顯示返回到前一個路由。

1.在RandomWordsState的build方法中爲AppBar添加一個列表圖標。當用戶點擊列表圖標時,包含收藏夾的新路由頁面入棧顯示。
將該圖標及其相應的操作添加到build方法中:

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

2.向RandomWordsState類添加一個 _pushSaved() 方法.

class RandomWordsState extends State<RandomWords> {
  ...
  void _pushSaved() {
  }
}

熱重載應用,列表圖標將會出現在導航欄中。現在點擊它不會有任何反應,因爲 _pushSaved 函數還是空的。

3.當用戶點擊導航欄中的列表圖標時,建立一個路由並將其推入到導航管理器棧中。此操作會切換頁面以顯示新路由。

新頁面的內容在在MaterialPageRoute的builder屬性中構建,builder是一個匿名函數。

添加Navigator.push調用,這會使路由入棧(以後路由入棧均指推入到導航管理器的棧)

void _pushSaved() {
  Navigator.of(context).push(
  );
}

4.添加MaterialPageRoute及其builder。 現在,添加生成ListTile行的代碼。ListTile的divideTiles()方法在每個ListTile之間添加1像素的分割線。 該 divided 變量持有最終的列表項。

void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute(
      builder: (context) {
        final tiles = _saved.map(
          (pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
          .toList();
      },
    ),
  );
}

5.builder返回一個Scaffold,其中包含名爲“Saved Suggestions”的新路由的應用欄。 新路由的body由包含ListTiles行的ListView組成; 每行之間通過一個分隔線分隔。

void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute(
      builder: (context) {
        final tiles = _saved.map(
          (pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
          .toList();

        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );
      },
    ),
  );
}

6.熱重載應用程序。收藏一些選項,並點擊應用欄中的列表圖標,在新路由頁面中顯示收藏的內容。 請注意,導航器會在應用欄中添加一個“返回”按鈕。你不必顯式實現Navigator.pop。點擊後退按鈕返回到主頁路由。

使用主題更改UI

使用主題。主題控制您應用程序的外觀和風格。您可以使用默認主題,該主題取決於物理設備或模擬器,也可以自定義主題以適應您的品牌。
1.可以通過配置ThemeData類輕鬆更改應用程序的主題。 您的應用程序目前使用默認主題,下面將更改primary color顏色爲白色。

將應用程序的主題更改爲白色:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      theme: new ThemeData(
        primaryColor: Colors.white,
      ),
      home: new RandomWords(),
    );
  }
}

2.熱重載應用。 請注意,整個背景將會變爲白色,包括應用欄。

使用 ThemeData 來改變UI的其他方面。 Material library中的 Colors類提供了許多可以使用的顏色常量, 可以使用熱重載來快速簡單地嘗試、實驗

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章