Flutter

Flutter实战 从头撸一个「孤岛」APP(No.1、项目初

2019-11-23  本文已影响0人  洋小洋同学

阅读建议

目标

我们接下来会完成这部分

0102.gif

那由于我们是请求的网络图片资源,会有一些请求时间,也是要优化的

写在前面

在开始这段Flutter之旅前,需要储备一些常用的点

本篇是这段旅程的第一段,因为笔者也不知会开发的什么进度,但争取每周更新一篇,让我们共同学习,lets_do_it

目标

初始化项目 init

那既然我们要开始一个新的项目,我们选择初始化一个新的项目。在磁盘的方便找到的哪个位置都可以,那我就选择这个

0101.png

项目的目录

0102.png

项目创建好之后,依旧老套路,删除无用的代码,其中主要的代码是main.dart

TIM截图20191116190242.png

在这里我们可以设置虚拟机的层级,方便我们调试

TIM截图20191116190432.png

把这个总是在上边打开

目录结构

开始创建一些见名知意的文件夹

TIM截图20191116190802.png

添加第三方包

我们可以尝试收藏这两个网址

插件名称 地址
flutter_screenutil flutter_screenutil 屏幕适配
curved_navigation_bar curved_navigation_bar 底部导航栏
provider provider 状态管理
shared_preferences shared_preferences 本地持久化
dio dio 网络请求
fluro fluro 路由框架
。。。

main.dart

那上边我们已经初始化了项目,显然一片黑色是有点丑陋的,不符合我们的审美,看一下MaterialApp

对外暴露的API

  const MaterialApp({
    Key key,
    this.navigatorKey,
    this.home, 
    this.routes = const <String, WidgetBuilder>{},
    this.initialRoute,
    this.onGenerateRoute,
    this.onUnknownRoute,
    this.navigatorObservers = const <NavigatorObserver>[],
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.darkTheme,
    this.themeMode = ThemeMode.system,
    this.locale,
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,

一个APP,在我们的印象中,都是 分为上中下三部分,就像是我们的人一样头部身体,脚部

那我们就开始写一个我的首页

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

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

// 这里我们用StatelessWidget,我是一个没有状态的"孩子"
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '孤岛',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHome(),
    );
  }
}

class MyHome extends StatefulWidget {
  MyHome({Key key}) : super(key: key);

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

class _MyHomeState extends State<MyHome> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('孤岛APP'),
      ),
    );
  }
}

显然我们如果都把这些部件放在同一个文件夹是不太符合开发规范的,也不利于后期的优化与维护,

那就写在pages 文件夹下

lib
├── pages
├────book_list_page.dart
├────home_page.dart
├────love_page.dart

TIM截图20191116195314.png

每个页面的初始代码就是这个样子的

import 'package:flutter/material.dart';

class BookListPage extends StatefulWidget {
  BookListPage({Key key}) : super(key: key);

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

class _BookListPageState extends State<BookListPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('我是书单'),
      ),
    );
  }
}

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('我是首页'),
      ),
    );
  }
}

import 'package:flutter/material.dart';

class LovePage extends StatefulWidget {
  LovePage({Key key}) : super(key: key);

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

class _LovePageState extends State<LovePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('我是喜欢'),
      ),
    );
  }
}

底部导航 bottomNavigationBar

在这里我们使用 **curved_navigation_bar **这个轮子

首先,还是加入依赖

dependencies:
  curved_navigation_bar: ^0.3.1 #latest version

TIM截图20191116200144.png

在前面的时候,我们说过一些公用的部件我们放在widgets文件下,那我们打算放在公用的部件文件夹下,并命名为widget_bottom_navigation_bar.dart

在文件的头部引入

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

import '../pages/home_page.dart';
import '../pages/book_list_page.dart';
import '../pages/love_page.dart';

其中的全部代码 是

/// 在这里我们生命一个有状态的部件,因为其中会牵扯到index的改变
class BottomNavBarWidget extends StatefulWidget {
  BottomNavBarWidget({Key key}) : super(key: key);

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

class _BottomNavBarWidgetState extends State<BottomNavBarWidget>
    with SingleTickerProviderStateMixin {
  /// 这里声明一个控制器,在flutter中好多用到控制器的地方,包括像最常见的表单
  TabController tabController;

  /// 这里把我们引入的三个页面放进List集合里,等候发落
  List _pages = [HomePage(), BookListPage(), LovePage()];

  /// 这个就是比较核心的索引了,默认值就是我们的首页
  int currentIndex = 0;

  @override
  void initState() {
    super.initState();
    tabController = TabController(vsync: this, length: 3)
      ..addListener(() {
        /// setState 这里有点像咱们 的React,更改数据的时候是要在setState()里
        setState(() {
          currentIndex = tabController.index;
        });
      });
  }

  // 这里是一个部件,返回的值类型是个Widget是用Scaffold包着的,里边也是界面的核心
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        bottomNavigationBar: CurvedNavigationBar(
          // backgroundColor: _pages[currentIndex],
          index: currentIndex,
          // 底部按钮
          items: <Widget>[
            Image.asset(
              'images/bottom_nav/home@light.png',
              width: 50,
              height: 50,
            ),
            Image.asset(
              'images/bottom_nav/book_list@light.png',
              width: 50,
              height: 50,
            ),
            Image.asset(
              'images/bottom_nav/love@light.png',
              width: 50,
              height: 50,
            ),
          ],

          /// 点击不同的底部导航
          onTap: (index) {
            //Handle button tap
            setState(() {
              currentIndex = index;
            });
            tabController.animateTo(index,
                duration: Duration(milliseconds: 300), curve: Curves.ease);
          },
        ),
        // 主体部分,就是文中我们所说的人的身体一样
        body: TabBarView(
          controller: tabController,
          children: <Widget>[
            Container(
              child: _pages[0],
            ),
            Container(
              child: _pages[1],
            ),
            Container(
              child: _pages[2],
            )
          ],
        ));
  }
}


至于这个轮子怎么用是传字符串,还是部件呢,那没有比看源码更好不过了

TIM截图20191116201616.png

Flutter 本地图片的引入 assets

那关于上文我们引入的图片有必要一起学习下

 Image.asset(
              'images/bottom_nav/book_list@light.png',
              width: 50,
              height: 50,
            ),

也就是images/bottom_nav/book_list@light.png,

那截止目前呢我们已经开发了一部分了,也没有遇到什么磕磕绊绊,那《孤岛APP》现在她便是这个样子

0101.gif

屏幕适配

点击的底部导航的时候,能够在三个页面中进行切换,那现在有个很重要的问题需要考虑,让我们把目光聚焦在头部的字体,当下在这种模拟器下是这个大小,那手机的型号是千千万万的。所以就需要适配不通的屏幕

这里我们使用flutter_ScreenUtil

flutter 屏幕适配方案,让你的UI在不同尺寸的屏幕上都能显示合理的布局!

先说下怎么使用

//长方形:
Container(
           width: ScreenUtil.getInstance().setWidth(375),
           height: ScreenUtil.getInstance().setHeight(200),
            ),
            
//如果你想显示一个正方形:
Container(
           width: ScreenUtil.getInstance().setWidth(300),
           height: ScreenUtil.getInstance().setWidth(300),
            ),

//传入字体大小,默认不根据系统的“字体大小”辅助选项来进行缩放(可在初始化ScreenUtil时设置allowFontScaling)
ScreenUtil.getInstance().setSp(28)         
 
//传入字体大小,根据系统的“字体大小”辅助选项来进行缩放(如果某个地方不遵循全局的allowFontScaling设置)     
ScreenUtil(allowFontScaling: true).setSp(28)   

在需要适配的文件引入

import 'package:flutter_screenutil/flutter_screenutil.dart';

在这里需要注意一下,我们把适配尺寸的初始化写在了底部导航

TIM截图20191116222116.png

接着我们对底部的三个图片屏幕适配

    items: <Widget>[
            Image.asset(
              'images/bottom_nav/home@light.png',
              width: ScreenUtil.getInstance().setWidth(100),
              height: ScreenUtil.getInstance().setHeight(100),
            ),
            Image.asset('images/bottom_nav/book_list@light.png',
                width: ScreenUtil.getInstance().setWidth(100),
                height: ScreenUtil.getInstance().setHeight(100)),
            Image.asset('images/bottom_nav/love@light.png',
                width: ScreenUtil.getInstance().setWidth(100),
                height: ScreenUtil.getInstance().setHeight(100)),
          ],

那现在就需要我们处理一下头部的字体了不是吗?

 title: Text(
        '我是首页',
        style: TextStyle(fontSize: ScreenUtil.getInstance().setSp(36)),
      ),

TIM截图20191116223249.png

有内味了是吧

右上角的DEBUG

在 MaterialApp 中,將 debugShowCheckdModeBanner 設成 false 就可以了

这里放上一个参考的链接 如何移掉 flutter app 中的 debug label

在这段旅途的最后,我们来完善一下,这款《孤岛》

    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text(
          '首页',
          style: TextStyle(fontSize: ScreenUtil.getInstance().setSp(36)),
        ),
      ),
      body: Container(
        height: ScreenUtil.getInstance().setHeight(1334),
        width: ScreenUtil.getInstance().setWidth(750),
        child: Image.network(
          'https://i.demo-1s.com/2019/11/16/yjhPSQWjuqPmosIL.jpg',
          fit: BoxFit.cover,
        ),
      ),
    );

写在最后

这一段路,我们就一块走到这儿,笔者会持续更新,请多多关注,相关代码也会同步更新到 笔者的仓库https://github.com/yayxs/flutter_lsolated_island_app

如果喜欢的话,不妨给个鼓励,好了就这young 加油~~

END

tips:一些思路有借鉴一些优秀的博文,如有不当,也可到笔者site 留言感谢开源,感谢大家

上一篇下一篇

猜你喜欢

热点阅读