Flutter 入门项目 - 实战 + 分析

一、效果图

项目地址:https://github.com/sparkerandroid/wanandroid_flutter

项目使用的接口:玩安卓、Gank,开放的api。感谢。

二、项目概览

dao:通过http,负责接口的调用;

model:数据解析;

navigator:页面导航;

pages:页面;

util:工具类,比如webView的封装;

widget:自定义组件;

constants文件:定义了项目中使用到的常量,比如接口的请求地址;

main文件:入口文件;

三、项目分析

3.1、项目入口 - main.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: 'wanAndroid',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyNavigator(),
    );
  }
}

顶级函数main,作为Flutter项目的入口函数。该函数是必须的,如果没有该函数,则项目是无法运行的。

除此之外,使用了MyApp作为项目的根组件。在Flutter中,一个应用程序的结构就是一个树(具体可以说是wdiget或者element树),需要在入口设置树的根。

Flutter为我们提供了Material风格的各种组件,比如上面使用的MaterialApp,还有像Scaffold等。我们可以直接使用这样的组件快速高效的开发出Material风格的app。当然,你也可以自定义。建议使用这些Flutter提供的Material风格组件来编写自己的应用。为啥?

首先,这和Google一直提倡的Material风格吻合;

其次,Flutter提供的Material组件,比如Scaffold组件,该组件其实已经为我们搭建了一个Material app的原型了,包括如下属性:AppBar、body、drawer、bottomNavigationBar、floatingActionButton等等。可以快速搭建app,无需重复造轮子。

如上,根Widget是MyApp,而home代表了主页面。

3.2、主框架搭建 

Scaffold + BottomNavigationBar + PageView + PageController  + Drawer;

实现效果参照第一部分的图1。

Scaffold作为主页面的骨架,在其基础之上添加:

  • 底部导航栏(BottomNavigationBar);
  • 侧滑菜单(Drawer);
  • 底部导航对应的页面(PageView) - 比如点击公众号Tab,则切换显示公众号页面;
  • 底部导航和页面之间的联动(PageController) - 滑动页面,对应底部导航的切换   或者  点击底部Tab,切换页面;

实现很简单,不再详细分析了,一看就懂的。

class NavigatorState extends State<MyNavigator> {
  String appBarTitle = "首页";
  int _currentIndex = 0;

  PageController _pageController = PageController(initialPage: 0);

  @override
  Widget build(BuildContext context) {
    // AppBar - 顶部标题栏;PageView - 中间显示的Page页面;BottomNavigationBar - 底部导航栏

    return Scaffold(
      appBar: AppBar(
        title: Text(appBarTitle),
        leading: Builder(builder: (context) {
          return IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () {
              Scaffold.of(context).openDrawer();
            },
            tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
          );
        }),
      ),
      drawer: MyDrawer(),
      body: PageView(
        controller: _pageController,
        children: <Widget>[HomePage(), GankMzPage(), PublicSubscriptionPage()],
        onPageChanged: (index) {
          setState(() {
            this._currentIndex = index;
            _setAppBarTitle();
          });
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
          type: BottomNavigationBarType.fixed,
          onTap: (index) {
            // 点击跳转对应的PageView
            _pageController.jumpToPage(index);
            setState(() {
              this._currentIndex = index;
              _setAppBarTitle();
            });
          },
          currentIndex: _currentIndex,
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
                icon: Icon(Icons.home),
                title: Text("首页"),
                backgroundColor: Colors.white),
            BottomNavigationBarItem(
                icon: Icon(Icons.apps),
                title: Text("福利"),
                backgroundColor: Colors.white),
            BottomNavigationBarItem(
                icon: Icon(Icons.map),
                title: Text("公众号"),
                backgroundColor: Colors.white)
          ]),
    );
  }

各个组件的具体用法可参阅Flutter中文网

3.3、公众号页面详解

  • 页面搭建

这里涉及到了Scaffold组件的另一种搭建页面的方式 - TabBar + TabBarView + TabController(负责TabBar与TabBarView之间的联动)。实现效果参照第一部分的图三。

Scaffold(
        appBar: PreferredSize(
            child: AppBar(
              bottom: TabBar(
                tabs: _tabs,
                controller: _tabController,
                isScrollable: true,
              ),
            ),
            preferredSize: Size.fromHeight(50)),
        body: TabBarView(
          children: _tabData.map((item) {
            return SubscriptionPage(
              subscriptionId: item?.id,
            );
          }).toList(),
          controller: _tabController,
        ),
      );

其中,页面的顶部导航实现只需为AppBar设置bottom属性即可,属性的value即为TabBar。

点击每个Tab,对应的页面如何实现呢?

Flutter为我们提供了TabBarView。只需根据对应的Tab去显示对应的页面即可。这个我们封装了SubscriptionPage Widget,用于显示每个公众号下的历史数据。

正如上面所说,TabController是连接TabBar和TabBarView之间的桥梁。有了TabController,就很容易实现两者之间的联动了。用法如上代码所示,这里不再详述。

  • 数据获取

数据的获取,我们单独放在Dao层。该文件夹下的各个Dao类负责对应页面的数据网络获取工作。涉及到http、convert等库的基本使用。

以获取公账号的Tab数据为例:

class PublicSubscriptionDao {
  static Future<PublicSubscriptionModel> getSubscriptions() async {
    http.Response response = await http.get(Apis.PUBLIC_SUBSCRIPTION);
    if (response != null && response.statusCode == 200) {
      Utf8Decoder utf8decoder = new Utf8Decoder();
      return PublicSubscriptionModel.fromJson(
          json.decode(utf8decoder.convert(response.bodyBytes)));
    } else {
      return null;
    }
  }
}

 

import 'package:http/http.dart' as http; // 网络通信
import 'dart:convert';// 数据解析

 这里用到了两个库用于数据的获取及解析。

为了解决中文乱码问题,这里使用了Utf8Decoder。

  • 数据解析
class Data {
  int courseId;
  int id;
  String name;
  int order;
  int parentChapterId;
  bool userControlSetTop;
  int visible;

  Data(
      {this.courseId,
      this.id,
      this.name,
      this.order,
      this.parentChapterId,
      this.userControlSetTop,
      this.visible});

  Data.fromJson(Map<String, dynamic> json) {
    courseId = json['courseId'];
    id = json['id'];
    name = json['name'];
    order = json['order'];
    parentChapterId = json['parentChapterId'];
    userControlSetTop = json['userControlSetTop'];
    visible = json['visible'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['courseId'] = this.courseId;
    data['id'] = this.id;
    data['name'] = this.name;
    data['order'] = this.order;
    data['parentChapterId'] = this.parentChapterId;
    data['userControlSetTop'] = this.userControlSetTop;
    data['visible'] = this.visible;
    return data;
  }
}

 这里涉及到了Dart相关的知识点:命名参数、命名构造函数。

通过上一步骤的数据获取,这里我们拿到了一个Map<String, dynamic>类型的数据。我们只需解析出对应的字段即可了。

  • 数据填充
1)定义数据集合
List<Tab> _tabs = [];

2)获取数据
void _getSubscriptions() {
    _tabs?.clear();
    PublicSubscriptionDao.getSubscriptions().then((subscriptions) {
      _tabData = subscriptions?.data ?? [];
      setState(() {// 通知Flutter Framework 刷新
        subscriptions?.data?.forEach((item) {
          _tabs.add(new Tab(text: item.name));
        });
      });
    }).catchError((e) {});
  }

3)填充数据
 AppBar(
        bottom: TabBar(
        tabs: _tabs, // 刷新之后,加载tab数据
        controller: _tabController,
        isScrollable: true,
        ),
     )
......

3.4、自定义Widget

Flutter推荐按照组合而非继承的方式进行自定义Widget。

在3.3小节,第一部分对于TabBarView,我们自定义了一个SubscriptionPage。自定义的好处在于可以封装公用逻辑,页面看起来也整洁不少。

目前,自定义的Widget包括WebView、SubscriptionPage以及Drawer。详细可参阅相应的源码。

3.5、其他相关问题

按照目前搭建的App,还存在一个问题,就是每次底部Tab切换的时候都会重新加载当前页面。解决这个问题只需按如下操作:

class HomeState extends State<HomePage>
    with AutomaticKeepAliveClientMixin<HomePage>{

......

@override
  bool get wantKeepAlive => true;//重写该方法,返回值为true即可

}

四、总结

Flutter的思想很多和RN比较相似,但是总体的感觉是Flutter的编程体验好于RN,无论的写代码还是调试。Dart的Debug  - JIT 和 发布 - AOT模式,这个非常棒。

本项目为入门级项目,还有很多需要优化的地方。共同学习。

相关推荐
<p> <b><span style="background-color:#FFE500;">【超实用课程内容】</span></b> </p> <p> <br /> </p> <p> <br /> </p> <p> 本套视频目标从UI分类开始讲起,结合网易新闻功能点实例讲解每一大类组件布局的使用。最后以一个完整的仿网易新闻的UI实战讲解,教会大家如何合理选择UI组件,并且使用组件快速实现我们的需求,完成一个完整的Flutter项目。 </p> <p> <br /> </p> <p> <br /> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <b><span style="background-color:#FFE500;">【课程如何观看?】</span></b> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> PC端:<a href="https://edu.csdn.net/course/detail/26277"></a><a href="https://edu.csdn.net/course/detail/26150"></a><a href="https://edu.csdn.net/course/detail/26150"></a><a href="https://edu.csdn.net/course/detail/27286"></a><a href="https://edu.csdn.net/course/detail/26858">https://edu.csdn.net/course/detail/26858</a> </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> 移动端:CSDN 学院APP(注意不是CSDN APP哦) </p> <p style="font-family:Helvetica;color:#3A4151;font-size:14px;background-color:#FFFFFF;"> 本课程为录播课,课程永久有效观看时长,大家可以抓紧时间学习后一起讨论哦~ </p> <p class="ql-long-24357476" style="font-family:"color:#3A4151;font-size:14px;background-color:#FFFFFF;"> <br /> </p> <p> <br /> </p>
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页