閱讀建議
- 字?jǐn)?shù):2739
- 時(shí)間:看你個(gè)人而定
- 主要內(nèi)容:圖片、代碼都有
- 場(chǎng)景:上下班的路上、床上
目標(biāo)
我們接下來(lái)會(huì)完成這部分
那由于我們是請(qǐng)求的網(wǎng)絡(luò)圖片資源,會(huì)有一些請(qǐng)求時(shí)間,也是要優(yōu)化的
寫(xiě)在前面
在開(kāi)始這段Flutter之旅前,需要儲(chǔ)備一些常用的點(diǎn)
- 科學(xué)上網(wǎng):不要問(wèn)為什么,因?yàn)樽鳛殚_(kāi)發(fā)來(lái)講這一步尤為的重要
- 《Flutter 實(shí)戰(zhàn)》作者杜文(網(wǎng)名wendux) :這本書(shū)很適合新手初步了解Flutter的各個(gè)部件。這將不同于我們的HTML
- Flutter中文社區(qū):中文社區(qū):其中會(huì)有一些視頻資源、插件推薦
- Flutter 咸魚(yú)團(tuán)隊(duì)技術(shù)博客阿里巴巴咸魚(yú)團(tuán)隊(duì):眾所周知,閑魚(yú)等APP就是國(guó)內(nèi)應(yīng)用Flutter技術(shù)開(kāi)發(fā)的,他們對(duì)Flutter這個(gè)大家庭的貢獻(xiàn)也是尤為重要的。
本篇是這段旅程的第一段,因?yàn)楣P者也不知會(huì)開(kāi)發(fā)的什么進(jìn)度,但爭(zhēng)取每周更新一篇,讓我們共同學(xué)習(xí),lets_do_it
目標(biāo)
初始化項(xiàng)目 init
那既然我們要開(kāi)始一個(gè)新的項(xiàng)目,我們選擇初始化一個(gè)新的項(xiàng)目。在磁盤(pán)的方便找到的哪個(gè)位置都可以,那我就選擇這個(gè)
項(xiàng)目的目錄
項(xiàng)目創(chuàng)建好之后,依舊老套路,刪除無(wú)用的代碼,其中主要的代碼是main.dart
在這里我們可以設(shè)置虛擬機(jī)的層級(jí),方便我們調(diào)試
把這個(gè)總是在上邊打開(kāi)
目錄結(jié)構(gòu)
開(kāi)始創(chuàng)建一些見(jiàn)名知意的文件夾
- models 主要是放置項(xiàng)目的Model類(lèi),這里至于為什么,在項(xiàng)目中我們直接操作后臺(tái)返回的JSON是不太好的
- pages 主要是放置一些頁(yè)面文件,其中包括首頁(yè)、書(shū)單、喜歡
- provider 主要放置全局狀態(tài)管理
- utils 項(xiàng)目中公用的方法類(lèi)
- widgets 公用的部件
添加第三方包
我們可以嘗試收藏這兩個(gè)網(wǎng)址
- pub一些第三方的插件和包,在我們的項(xiàng)目中也會(huì)用到
- hub包括像Flutter-go 這樣優(yōu)秀的項(xiàng)目都在,聽(tīng)說(shuō)appid用戶(hù)可以官方渠道申請(qǐng)APP 端的使用
| 插件名稱(chēng) | 地址 | |
|---|---|---|
| flutter_screenutil | flutter_screenutil | 屏幕適配 |
| curved_navigation_bar | curved_navigation_bar | 底部導(dǎo)航欄 |
| provider | provider | 狀態(tài)管理 |
| shared_preferences | shared_preferences | 本地持久化 |
| dio | dio | 網(wǎng)絡(luò)請(qǐng)求 |
| fluro | fluro | 路由框架 |
| 。。。 | ||
main.dart
那上邊我們已經(jīng)初始化了項(xiàng)目,顯然一片黑色是有點(diǎn)丑陋的,不符合我們的審美,看一下MaterialApp
對(duì)外暴露的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 這個(gè)應(yīng)該就是主頁(yè)面了
- initialRoute 這個(gè)是不是初始化的路由,也許后邊我們寫(xiě)到路由的時(shí)候可以用到
- title 這個(gè)應(yīng)該就是標(biāo)題了
- color 顏色
- theme 莫非是主題
一個(gè)APP,在我們的印象中,都是 分為上中下三部分,就像是我們的人一樣頭部身體,腳部
那我們就開(kāi)始寫(xiě)一個(gè)我的首頁(yè)
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
// 這里我們用StatelessWidget,我是一個(gè)沒(méi)有狀態(tài)的"孩子"
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'),
),
);
}
}
顯然我們?nèi)绻及堰@些部件放在同一個(gè)文件夾是不太符合開(kāi)發(fā)規(guī)范的,也不利于后期的優(yōu)化與維護(hù),
那就寫(xiě)在pages 文件夾下
lib
├── pages
├────book_list_page.dart
├────home_page.dart
├────love_page.dart
每個(gè)頁(yè)面的初始代碼就是這個(gè)樣子的
- 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('我是書(shū)單'),
),
);
}
}
- 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('我是首頁(yè)'),
),
);
}
}
- 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('我是喜歡'),
),
);
}
}
底部導(dǎo)航 bottomNavigationBar
在這里我們使用 **curved_navigation_bar **這個(gè)輪子
首先,還是加入依賴(lài)
dependencies:
curved_navigation_bar: ^0.3.1 #latest version
在前面的時(shí)候,我們說(shuō)過(guò)一些公用的部件我們放在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';
其中的全部代碼 是
/// 在這里我們生命一個(gè)有狀態(tài)的部件,因?yàn)槠渲袝?huì)牽扯到index的改變
class BottomNavBarWidget extends StatefulWidget {
BottomNavBarWidget({Key key}) : super(key: key);
@override
_BottomNavBarWidgetState createState() => _BottomNavBarWidgetState();
}
class _BottomNavBarWidgetState extends State<BottomNavBarWidget>
with SingleTickerProviderStateMixin {
/// 這里聲明一個(gè)控制器,在flutter中好多用到控制器的地方,包括像最常見(jiàn)的表單
TabController tabController;
/// 這里把我們引入的三個(gè)頁(yè)面放進(jìn)List集合里,等候發(fā)落
List _pages = [HomePage(), BookListPage(), LovePage()];
/// 這個(gè)就是比較核心的索引了,默認(rèn)值就是我們的首頁(yè)
int currentIndex = 0;
@override
void initState() {
super.initState();
tabController = TabController(vsync: this, length: 3)
..addListener(() {
/// setState 這里有點(diǎn)像咱們 的React,更改數(shù)據(jù)的時(shí)候是要在setState()里
setState(() {
currentIndex = tabController.index;
});
});
}
// 這里是一個(gè)部件,返回的值類(lèi)型是個(gè)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,
),
],
/// 點(diǎn)擊不同的底部導(dǎo)航
onTap: (index) {
//Handle button tap
setState(() {
currentIndex = index;
});
tabController.animateTo(index,
duration: Duration(milliseconds: 300), curve: Curves.ease);
},
),
// 主體部分,就是文中我們所說(shuō)的人的身體一樣
body: TabBarView(
controller: tabController,
children: <Widget>[
Container(
child: _pages[0],
),
Container(
child: _pages[1],
),
Container(
child: _pages[2],
)
],
));
}
}
至于這個(gè)輪子怎么用是傳字符串,還是部件呢,那沒(méi)有比看源碼更好不過(guò)了
- 項(xiàng)目:小部件列表
- 索引:NavigationBar的索引,可用于更改當(dāng)前索引或設(shè)置初始索引
- 顏色:NavigationBar的顏色,默認(rèn)為Colors.white
- buttonBackgroundColor:浮動(dòng)按鈕的背景色,默認(rèn)與顏色屬性
- backgroundColor: NavigationBar的背景,默認(rèn)Colors.blueAccent
- onTap:函數(shù)處理對(duì)項(xiàng)目的點(diǎn)擊
- animationCurve:曲線插值按鈕更改動(dòng)畫(huà),默認(rèn)Curves.easeOutCubic
- animationDuration:按鈕更改動(dòng)畫(huà)的持續(xù)時(shí)間,默認(rèn)Duration(毫秒:600)
- height:NavigationBar的高度,最小值0.0,最高75.0
Flutter 本地圖片的引入 assets
那關(guān)于上文我們引入的圖片有必要一起學(xué)習(xí)下
Image.asset(
'images/bottom_nav/book_list@light.png',
width: 50,
height: 50,
),
也就是images/bottom_nav/book_list@light.png,
在工程根目錄下創(chuàng)建一個(gè)
images目錄,并將所需的圖片拷貝到該目錄-
在
pubspec.yaml中的flutter部分添加如下內(nèi)容: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, )
那截止目前呢我們已經(jīng)開(kāi)發(fā)了一部分了,也沒(méi)有遇到什么磕磕絆絆,那《孤島APP》現(xiàn)在她便是這個(gè)樣子
屏幕適配
點(diǎn)擊的底部導(dǎo)航的時(shí)候,能夠在三個(gè)頁(yè)面中進(jìn)行切換,那現(xiàn)在有個(gè)很重要的問(wèn)題需要考慮,讓我們把目光聚焦在頭部的字體,當(dāng)下在這種模擬器下是這個(gè)大小,那手機(jī)的型號(hào)是千千萬(wàn)萬(wàn)的。所以就需要適配不通的屏幕
這里我們使用flutter_ScreenUtil
flutter 屏幕適配方案,讓你的UI在不同尺寸的屏幕上都能顯示合理的布局!
- 包的地址 [flutter_ScreenUtil (https://github.com/OpenFlutter/flutter_screenutil/blob/master/README_CN.md)
- 星星 1.2K+
先說(shuō)下怎么使用
- 寬度 width ScreenUtil.getInstance().setWidth(540)
- 高度 height ScreenUtil.getInstance().setHeight(200)
- 字體大小 fontSize
//長(zhǎng)方形:
Container(
width: ScreenUtil.getInstance().setWidth(375),
height: ScreenUtil.getInstance().setHeight(200),
),
//如果你想顯示一個(gè)正方形:
Container(
width: ScreenUtil.getInstance().setWidth(300),
height: ScreenUtil.getInstance().setWidth(300),
),
//傳入字體大小,默認(rèn)不根據(jù)系統(tǒng)的“字體大小”輔助選項(xiàng)來(lái)進(jìn)行縮放(可在初始化ScreenUtil時(shí)設(shè)置allowFontScaling)
ScreenUtil.getInstance().setSp(28)
//傳入字體大小,根據(jù)系統(tǒng)的“字體大小”輔助選項(xiàng)來(lái)進(jìn)行縮放(如果某個(gè)地方不遵循全局的allowFontScaling設(shè)置)
ScreenUtil(allowFontScaling: true).setSp(28)
在需要適配的文件引入
import 'package:flutter_screenutil/flutter_screenutil.dart';
在這里需要注意一下,我們把適配尺寸的初始化寫(xiě)在了底部導(dǎo)航
接著我們對(duì)底部的三個(gè)圖片屏幕適配
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)),
],
那現(xiàn)在就需要我們處理一下頭部的字體了不是嗎?
- 引入 import 'package:flutter_screenutil/flutter_screenutil.dart';
- 具體適配
title: Text(
'我是首頁(yè)',
style: TextStyle(fontSize: ScreenUtil.getInstance().setSp(36)),
),
有內(nèi)味了是吧
右上角的DEBUG
在 MaterialApp 中,將 debugShowCheckdModeBanner 設(shè)成 false 就可以了
這里放上一個(gè)參考的鏈接 如何移掉 flutter app 中的 debug label
在這段旅途的最后,我們來(lái)完善一下,這款《孤島》
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
'首頁(yè)',
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,
),
),
);
寫(xiě)在最后
這一段路,我們就一塊走到這兒,筆者會(huì)持續(xù)更新,請(qǐng)多多關(guān)注,相關(guān)代碼也會(huì)同步更新到 筆者的倉(cāng)庫(kù), https://github.com/yayxs/flutter_lsolated_island_app
如果喜歡的話,不妨給個(gè)鼓勵(lì),好了就這young 加油~~
END
tips:一些思路有借鑒一些優(yōu)秀的博文,如有不當(dāng),也可到筆者site 留言感謝開(kāi)源,感謝大家











