Flutter实战 从头撸一个「孤岛」APP(No.1、项目初始化、屏幕适配)

阅读建议

  • 字数:2739
  • 时间:看你个人而定
  • 主要内容:图片、代码都有
  • 场景:上下班的路上、床上

目标

我们接下来会完成这部分

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

写在前面

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

  • 科学上网:不要问为什么,因为作为开发来讲这一步尤为的重要
  • 《Flutter 实战》作者杜文(网名wendux) :这本书很适合新手初步了解Flutter的各个部件。这将不同于我们的HTML
  • Flutter中文社区:中文社区:其中会有一些视频资源、插件推荐
  • Flutter 咸鱼团队技术博客阿里巴巴咸鱼团队:众所周知,闲鱼等APP就是国内应用Flutter技术开发的,他们对Flutter这个大家庭的贡献也是尤为重要的。

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

目标

初始化项目 init

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

项目的目录

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

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

把这个总是在上边打开

目录结构

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

  • models 主要是放置项目的Model类,这里至于为什么,在项目中我们直接操作后台返回的JSON是不太好的
  • pages 主要是放置一些页面文件,其中包括首页、书单、喜欢
  • provider 主要放置全局状态管理
  • utils 项目中公用的方法类
  • widgets 公用的部件

添加第三方包

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

  • pub一些第三方的插件和包,在我们的项目中也会用到
  • hub包括像Flutter-go 这样优秀的项目都在,听说appid用户可以官方渠道申请APP 端的使用
插件名称 地址
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,

  • home 这个应该就是主页面了
  • initialRoute 这个是不是初始化的路由,也许后边我们写到路由的时候可以用到
  • title 这个应该就是标题了
  • color 颜色
  • theme 莫非是主题

一个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

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

  • book_list_page.dart
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('我是书单'),
      ),
    );
  }
}

  • home_page.dart
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('我是首页'),
      ),
    );
  }
}

  • love_page.dart
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

在前面的时候,我们说过一些公用的部件我们放在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],
            )
          ],
        ));
  }
}


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

  • 项目:小部件列表
  • 索引:NavigationBar的索引,可用于更改当前索引或设置初始索引
  • 颜色:NavigationBar的颜色,默认为Colors.white
  • buttonBackgroundColor:浮动按钮的背景色,默认与颜色属性
  • backgroundColor: NavigationBar的背景,默认Colors.blueAccent
  • onTap:函数处理对项目的点击
  • animationCurve:曲线插值按钮更改动画,默认Curves.easeOutCubic
  • animationDuration:按钮更改动画的持续时间,默认Duration(毫秒:600)
  • height:NavigationBar的高度,最小值0.0,最高75.0

Flutter 本地图片的引入 assets

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

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

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

  • 在工程根目录下创建一个images目录,并将所需的图片拷贝到该目录

  • pubspec.yaml中的flutter部分添加如下内容:

      assets:
        - images/bottom_nav/home@light.png
        - images/bottom_nav/book_list@light.png
        - images/bottom_nav/love@light.png
    
    
  • 加载该图片

    • Image(
        image: AssetImage("images/avatar.png"),
        width: 100.0
      );
      
      
    • Image.asset("images/avatar.png",
        width: 100.0,
      )
      
      

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

屏幕适配

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

这里我们使用flutter_ScreenUtil

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

先说下怎么使用

  • 宽度 width ScreenUtil.getInstance().setWidth(540)
  • 高度 height ScreenUtil.getInstance().setHeight(200)
  • 字体大小 fontSize
//长方形:
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';

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

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

    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)),
          ],

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

  • 引入 import 'package:flutter_screenutil/flutter_screenutil.dart';
  • 具体适配
 title: Text(
        '我是首页',
        style: TextStyle(fontSize: ScreenUtil.getInstance().setSp(36)),
      ),

有内味了是吧

右上角的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 留言感谢开源,感谢大家

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容