Flutter 完整示例

經過這一段對 Flutter 的瞭解和接觸,掌握如何完整使用 Flutter 開發一個項目。實際上,在 Flutter 中,一切皆 widget,我們的界面都是由各種 widget 堆疊出來的。

Flutter 完整示例

一個 Flutter 工程涉及以下幾個要點:

  • 工程項目代碼分層
  • 主題風格
  • 插件
  • 路由
  • 網絡數據交互
  • 界面佈局及刷新
    Flutter 完整示例

    一、工程項目代碼分層

一個正式的工程項目,它的代碼必須做到分層,代碼的分層體現了開始者的架構能力。
Flutter 完整示例

Flutter 工程的主要工作 lib 目錄及 pubspec.yaml :

  • main.dart:Flutter 的入口函數
  • loading.dart:啓動頁,一般生存週期爲3-5秒
  • app.dart:工程主文件
  • conf : 配置文件目前或一些宏定義數據文件目錄
  • model : 數據模型目錄
  • pages : 各 UI ,即 Widget 文件
  • service : 網絡請求目錄
  • style : 自定義風格文件(顏色、字體等)
  • utils : 工具目錄

代碼分層設計設計的合不合理,直接影響代碼的可維護性和穩定性。

二、主題風格

Flutter 默認的主題是 藍白 的風格,其他主題則需要配置。依項目而定,根據當前需要,配置一個 紅灰 風格:

#main.dart
void main() => runApp(MaterialApp(
  debugShowCheckedModeBanner: false,
  title: 'Flutter實戰',
  //自定義主題
  theme: mDefaultTheme,

));

//自定義主題
final ThemeData mDefaultTheme = ThemeData(
  primaryColor: Colors.redAccent,
);

Colors.redAccent 爲系統主題,在 colors.dart 中定義:

static const MaterialAccentColor redAccent = MaterialAccentColor(
    _redAccentValue,
    <int, Color>{
      100: Color(0xFFFF8A80),
      200: Color(_redAccentValue),
      400: Color(0xFFFF1744),
      700: Color(0xFFD50000),
    },
  );
  static const int _redAccentValue = 0xFFFF5252;

當然也可以自定義一些風格或顏色,在工程 style 中 color.dart:

//產品顏色
class ProductColors{
  static const Color bgColor = Color(0xFFFFFFFF);
  static const divideLineColor = Color.fromRGBO(245, 245, 245, 1.0);
  static const typeColor = Color.fromRGBO(182, 9, 9, 1.0);
  static const piontColor = Color.fromRGBO(132, 95, 63, 1.0);
}

三、插件

開發者不可能對每個功能都自已造輪子,選取合適的輪子,對於項目來說,可以達到事半功倍的效果。

3.1 添加第三方庫

打開 pubspec.yaml 文件添加三方庫,可以 在 https://pub.dev/flutter (需要FQ)上找到許多開源軟件包

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
  http: ^0.12.0
  flutter_webview_plugin: ^0.3.0
  flutter_swiper: 1.1.4
  • http : 網絡請求插件
  • flutter_webview_plugin : 一些靜態頁面的加載,使用webview
  • flutter_swiper : 動畫輪播效果

3.2 導入

swiper 插件

點擊 Packages get 獲取剛添加的包。

Flutter 完整示例

這樣就可以使用這個庫了。

四、路由

路由主要用於界面的切換及跳轉,分爲 靜態路由 和 動態路由

  • 靜態路由:界面的跳轉不帶附加參數
  • 動態路由:界面的跳轉可攜帶附加參數

4.1 路由初始化

void main() => runApp(MaterialApp(
  debugShowCheckedModeBanner: false,
  title: 'Flutter實戰',
  //自定義主題
  theme: mDefaultTheme,

  //添加路由
  routes: <String,WidgetBuilder>{
    "app": (BuildContext context) => App(),
    "company_info":(BuildContext context) => WebviewScaffold(
      url: "https://www.baidu.com",
      appBar: AppBar(
        title: Text('公司介紹'),
        leading: IconButton(
            icon: Icon(Icons.home),
            onPressed: (){
              //路由到主界面
              Navigator.of(context).pushReplacementNamed('app');
            },
        ),
      ),
    ),
  },
  //指定加載頁面
  home: LoadingPage(),

));

4.2 靜態路由

Navigator.of(context).pushReplacementNamed('company_info');

4.3 動態路由

Navigator.push(context,MaterialPageRoute(builder: (context) => AboutContactPage()));

或者

Navigator.push(context,MaterialPageRoute(builder: (context) => NewsDetailPage(item: item)),

數據接收處理:

class NewsDetailPage extends StatelessWidget{
  final NewsItemModal item;

  NewsDetailPage({Key key,@required this.item}) : super(key:key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(item.title),
      ),
      body:  Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(item.content),
      ),
    );
  }
}

五、網絡數據交互

網絡數據交互一般涉及 URL、數據模型、數據交互模式(Get、Post)等

5.1 URL 定義

在配置中定義宏變量

class Config{
  static const String IP = '192.168.2.5';
  static const String PORT = '8080';
}

5.2 數據模型

//新聞列表項數據轉換
class NewsItemModal{
  String author;//作者
  String title;//標題
  String content;//內容

  NewsItemModal({
    this.author,
    this.title,
    this.content,
  });

  factory NewsItemModal.fromJson(dynamic json){
    return NewsItemModal(
      author: json['author'],
      title: json['title'],
      content: json['content'],
    );
  }
}

//新聞列表數據轉換
class NewsListModal{

  List<NewsItemModal> data;
  NewsListModal(this.data);

  factory NewsListModal.fromJson(List json){
    return NewsListModal(
        json.map((i) => NewsItemModal.fromJson((i))).toList()
    );
  }
}

5.3 數據交互模式

import 'package:http/http.dart' as http;
import 'dart:convert';
import '../conf/configure.dart';

//獲取新聞數據
getNewsResult() async {
  String url = 'http://' + Config.IP + ':' + Config.PORT + '/?action=getNews';

  var res = await http.get(url);
  String body = res.body;

  var json= jsonDecode(body);
  print(json);

  return json['items'] as List;
}

六、界面佈局及刷新

6.1 啓動頁加載

一個存在 3 – 5 秒的界面

class LoadingPage extends StatefulWidget{
  @override
  _LoadingState createState() => _LoadingState();
}

class _LoadingState extends State<LoadingPage>{

  @override
  void initState(){
    super.initState();

    //在加載頁面停頓3秒
    Future.delayed(Duration(seconds: 3),(){
      print('Flutter企業站啓動...');
      Navigator.of(context).pushReplacementNamed("app");
    });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Center(
        child: Stack(
          children: <Widget>[
            //加載頁面背景圖
            Image.asset(
              'assets/images/loading.jpeg'
            ),

            Center(
              child: Text(
                'Flutter',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 36.0,
                  decoration: TextDecoration.none
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

6.2 輪播圖片

輪播圖片應用很廣泛,如廣告宣傳之類。

在資源配置圖片中,添加圖片

 # To add assets to your application, add an assets section, like this:
  assets:
  - assets/images/loading.jpeg
  - assets/images/company.jpg

  #輪播圖片
  - assets/images/banners/1.jpeg
  - assets/images/banners/2.jpeg
  - assets/images/banners/3.jpeg
  - assets/images/banners/4.jpeg

使用 Swiper 插件

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

class BannerWidget extends StatelessWidget{

  //圖片路徑
  List<String> banners = <String>[
    'assets/images/banners/1.jpeg',
    'assets/images/banners/2.jpeg',
    'assets/images/banners/3.jpeg',
    'assets/images/banners/4.jpeg',
  ];

  @override
  Widget build(BuildContext context) {

    //計算寬高 按比例
    double width = MediaQuery.of(context).size.width;
    double height = width * 540.0 / 1080.0;
    return Container(
      width: width,
      height: height,
      //輪播組件
      child: Swiper(
        itemBuilder: (BuildContext context, index){
          return Container(
            //圖片左右內邊距
            margin: EdgeInsets.only(left: 5, right: 5),
            child: Image.asset(
              banners[index],
              width: width,
              height: height,
              fit: BoxFit.cover,
            ),
          );
        },
        //輪播數量
        itemCount: banners.length,
        //方向
        scrollDirection: Axis.horizontal,
        //是否自動播放
        autoplay: true,
      ),
    );
  }
}

6.3 主界面(含導航欄)

import 'package:flutter/material.dart';
import 'pages/about_us_page.dart';
import 'pages/home_page.dart';
import 'pages/news_page.dart';
import 'pages/product_page.dart';

class App extends StatefulWidget {
  @override
  AppState createState() => AppState();
}

class AppState extends State<App> {
  //當前選擇頁面索引
  var _currentIndex = 0;

  HomePage homePage;
  ProductPage productPage;
  NewsPage newsPage;
  AboutUsPage aboutUsPage;

  //根據當前索引返回不同的頁面
  currentPage(){
    switch(_currentIndex){
      case 0:
        if(homePage == null){
          homePage = HomePage();
        }
        return homePage;
      case 1:
        if(productPage == null){
          productPage = ProductPage();
        }
        return productPage;

      case 2:
        if(newsPage == null){
          newsPage = NewsPage();
        }
        return newsPage;
      case 3:
        if(aboutUsPage == null){
          aboutUsPage = AboutUsPage();
        }
        return aboutUsPage;

    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter企業站實戰'),
        leading: Icon(Icons.home),
        actions: <Widget>[
          //右側內邊距
          Padding(
            padding: EdgeInsets.only(right: 20.0),
            child: GestureDetector(
              onTap: () {},
              child: Icon(
                Icons.search,
              ),
            ),
          ),
        ],
      ),

      body: currentPage(),

      //底部導航欄
      bottomNavigationBar: BottomNavigationBar(
          //通過fixedColor設置選中item 的顏色
          type: BottomNavigationBarType.fixed,
          currentIndex: _currentIndex,
          onTap: ((index) {
            setState(() {
              _currentIndex = index;
            });
          }),
          //底部導航欄
          items: [
            BottomNavigationBarItem(
              title: Text(
                '首頁',
              ),
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              title: Text(
                '產品',
              ),
              icon: Icon(Icons.apps),
            ),
            BottomNavigationBarItem(
              title: Text(
                '新聞',
              ),
              icon: Icon(Icons.fiber_new),
            ),
            BottomNavigationBarItem(
              title: Text(
                '關於我們',
              ),
              icon: Icon(Icons.insert_comment),
            ),
          ]),
    );
  }
}

6.4 ListView 的應用

import 'package:flutter/material.dart';
import '../model/news.dart';
import '../services/news.dart';
import 'news_detail_page.dart';

//新聞頁面
class NewsPage extends StatefulWidget {
  @override
  NewsPageState createState() => NewsPageState();
}

class NewsPageState extends State<NewsPage> {
  NewsListModal listData = NewsListModal([]);

  //獲取新聞列表數據
  void getNewsList() async {
    var data = await getNewsResult();
    NewsListModal list = NewsListModal.fromJson(data);

    setState(() {
      listData.data.addAll(list.data);
    });
  }

  @override
  void initState() {
    getNewsList();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //帶分隔線的List
      body: ListView.separated(
        //排列方向 垂直和水平
        scrollDirection: Axis.vertical,
        //分隔線構建器
        separatorBuilder: (BuildContext contex, int index) => Divider(
              height: 1.0,
              color: Colors.grey,
            ),
        itemCount: listData.data.length,
        //列表項構建器
        itemBuilder: (BuildContext contex, int index) {

          //新聞列表項數據
          NewsItemModal item = listData.data[index];

          return ListTile(
            title: Text(item.title),
            subtitle: Text(item.content),
            leading: Icon(Icons.fiber_new),
            trailing: Icon(Icons.arrow_forward),
            contentPadding: EdgeInsets.all(10.0),
            enabled: true,
            //跳轉至新聞詳情頁面
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => NewsDetailPage(item: item)),
              );
            },
          );
        },
      ),
    );
  }
}

6.5 文本框操作

含文本框配置及數據操作

import 'package:flutter/material.dart';
import '../services/contact.dart';

class AboutContactPage extends StatefulWidget{
  @override
  AboutContactPageState createState() => AboutContactPageState();
}

class AboutContactPageState extends State<AboutContactPage>{

  //文本編輯控制器
  TextEditingController controller = TextEditingController();

  //提交數據
  void commit(){
    if(controller.text.length == 0){
      showDialog(context: context,builder: (context) => AlertDialog(title: Text('請輸入內容'),),);
    } else{
      var info = contactCompany(controller.text);
      print(info);
    }
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('給我留言'),
      ),
      body: Container(
        color: Colors.white,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Image.asset(
              'assets/images/company.jpg',
              fit: BoxFit.cover,
            ),
            SizedBox(
              height: 40.0,
            ),
            SizedBox(
              width: 380.0,
              child: TextField(
                controller: controller,
                decoration: InputDecoration(
                  hintText: '請留言',
                  labelText: '請留言',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.person),
                ),
              ),
            ),
            SizedBox(
              height: 40.0,
            ),
            SizedBox(
              width: 220.0,
              height: 48.0,
              child: RaisedButton(
                child: Text('給我們留言',style: TextStyle(fontSize: 16.0),),
                color: Theme.of(context).primaryColor,//Colors.redAccent,
                colorBrightness: Brightness.dark,
                textColor: Colors.white,
                padding: EdgeInsets.only(
                  left: 20.0,
                  right: 20.0,
                  top: 5.0,
                  bottom: 5.0,
                ),
                shape: RoundedRectangleBorder(
                  side: BorderSide(
                    width: 1.0,
                    color: Colors.white,
                    style: BorderStyle.solid,
                  ),
                  borderRadius: BorderRadius.only(
                    topRight: Radius.circular(4.0),
                    topLeft: Radius.circular(4.0),
                    bottomLeft: Radius.circular(4.0),
                    bottomRight: Radius.circular(4.0),
                  ),
                ),
                onPressed: (){
                  commit();
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

七、注意點

  • => 是 Dart 中單行函數的簡寫
  • StatelessWidget 代表只有一種狀態的組件,與之對應的是 StatefulWidget(表示可能有多種狀態)。
  • 在 Widget 組件中都是通過 build 方法來描述自己的內部結構。這裏的 build 表示構建 MyApp 中使用的是 MaterialApp 的系統組件。
  • home標籤的值:Scaffold 是 Material library 中提供的一個組件,我們可以在裏面設置導航欄、標題和包含主屏幕 widget 樹的 body 屬性。可以看到這裏是在頁面上添加了AppBar 和一個 Text。
  • Center 是一個可以把子組件放在中心的組件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章