Flutter 完整示例

作者: 小小小小怪兽_666 | 来源:发表于2019-09-16 15:36 被阅读0次

    经过这一段对 Flutter 的了解和接触,掌握如何完整使用 Flutter 开发一个项目。实际上,在 Flutter 中,一切皆 widget,我们的界面都是由各种 widget 堆叠出来的。

    一个 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 (需要翻墙)上找到许多开源软件包

    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 获取刚添加的包。

    这样就可以使用这个库了。

    四、路由

    路由主要用于界面的切换及跳转,分为 静态路由 和 动态路由

    • 静态路由:界面的跳转不带附加参数
    • 动态路由:界面的跳转可携带附加参数

    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 是一个可以把子组件放在中心的组件

    相关文章

      网友评论

        本文标题:Flutter 完整示例

        本文链接:https://www.haomeiwen.com/subject/jiygyctx.html