您好,登錄后才能下訂單哦!
在上一篇文章中以簡單的方式對Flutter自己提供的演示進(jìn)行了一個(gè)簡單的分析,當(dāng)然那是遠(yuǎn)遠(yuǎn)不夠。本來打算為大家?guī)砉倬W(wǎng)上的無限下拉刷新的案例,但是發(fā)現(xiàn)這里的有些東西實(shí)在是太超前了,作為Flutter入門篇,當(dāng)然不能這么隨意,以為了讓大家都能夠?qū)W有所得,所以今天給大家?guī)砹俗约菏謹(jǐn)]的一個(gè)登錄。
cdn.xitu.io/2019/3/9/16961a925592e4a5?w=243&h=500&f=jpeg&s=4502">
我們都知道,一個(gè)簡單的登錄需要至少需要3步:
那么我們的布局也就至少需要3個(gè)widget
,為什么說至少呢?因?yàn)橥季质褂玫?code>widget都是大于操作步驟的。這里跟大家分享我的布局大概有這么幾個(gè):
widget
,可以包裹里面的所有內(nèi)容。widget
,讓所有的內(nèi)容成縱向排列。widget
不好嗎?這里容許我先買個(gè)關(guān)子~~為什么要講解這個(gè)呢?這是因?yàn)樗菍?shí)現(xiàn)了Mataril Design
的一種簡單的“腳手架”,有些也叫“支架”,通過這個(gè)翻譯也就知道了,其實(shí)它就是向我們提供了簡單的框架,我們直接使用它就行了。那么問題來了,我們可不可以不使用它呢?當(dāng)然是可以的,但是不建議這樣做,因?yàn)槲覀兒竺嫘枰褂玫暮芏?code>widget(比如TextField
)必須要在它的支持下才能運(yùn)行,不然就會報(bào)錯(cuò)了。
class Scaffold extends StatefulWidget {
/// Creates a visual scaffold for material design widgets.
const Scaffold({
Key key,
this.appBar, //橫向水平布局,通常顯示在頂部(*)
this.body, // 內(nèi)容(*)
this.floatingActionButton, //懸浮按鈕,就是上圖右下角按鈕(*)
this.floatingActionButtonLocation, //懸浮按鈕位置
//懸浮按鈕在[floatingActionButtonLocation]出現(xiàn)/消失動畫
this.floatingActionButtonAnimator,
//在底部呈現(xiàn)一組button,顯示于[bottomNavigationBar]之上,[body]之下
this.persistentFooterButtons,
//一個(gè)垂直面板,顯示于左側(cè),初始處于隱藏狀態(tài)(*)
this.drawer,
this.endDrawer,
//出現(xiàn)于底部的一系列水平按鈕(*)
this.bottomNavigationBar,
//底部持久化提示框
this.bottomSheet,
//內(nèi)容背景顏色
this.backgroundColor,
//棄用,使用[resizeToAvoidBottomInset]
this.resizeToAvoidBottomPadding,
//重新計(jì)算布局空間大小
this.resizeToAvoidBottomInset,
//是否顯示到底部,默認(rèn)為true將顯示到頂部狀態(tài)欄
this.primary = true,
//
this.drawerDragStartBehavior = DragStartBehavior.down,
}) : assert(primary != null),
assert(drawerDragStartBehavior != null),
super(key: key);
從這里面,我們可以看出Scaffold
提供了很多的方式方法,去實(shí)現(xiàn)Mataril Design
的布局:
一般就用于Scaffold.appBar
,是一個(gè)置于屏幕頂部的橫向布局,為什么是橫向呢?可以如下中看出:
我在它其中的anctions
屬性中設(shè)置了多個(gè)widget
,然后就向這樣后面那三就一溜的按順序排好了。
AppBar(
title: Text('Sample Code'),
leading: IconButton(
icon: Icon(Icons.view_quilt),
tooltip: 'Air it',
onPressed: () {},
),
bottom: TabBar(tabs: tabs.map((e) => Tab(text: e)).toList(),controller: _tabController),
actions: <Widget>[
IconButton(
icon: Icon(Icons.playlist_play),
tooltip: 'Air it',
onPressed: () {},
),
IconButton(
icon: Icon(Icons.playlist_add),
tooltip: 'Restitch it',
onPressed: () {},
),
IconButton(
icon: Icon(Icons.playlist_add_check),
tooltip: 'Repair it',
onPressed: () {},
)
],
)
對于上述中leading
需要說明一下,一般我們用它來顯示一個(gè)按鈕去關(guān)閉當(dāng)前頁面或者打開一個(gè)drawer
。有興趣的可以去試試~~
在AppBar
眾多的屬性中,還有一個(gè)是我們比較常用的,那就是bottom
,這個(gè)顯示于工具欄的下方,注意不是屏幕底部哦!一般使用TabBar
來實(shí)現(xiàn)一個(gè)頁面包含中多個(gè)不同頁面的切換。
當(dāng)然還有其他一些方式方法,這里就不多占用篇幅了,就簡單聊聊:
title
就是標(biāo)題drawer
抽屜,一般左側(cè)打開,默認(rèn)初始隱藏centerTitle
是否標(biāo)題居中
如果想看完整的實(shí)現(xiàn)方式,就跟我來吧!
這個(gè)屬性也是相當(dāng)重要的,如果我們想要實(shí)現(xiàn)多個(gè),不同頁面的切換,就可以使用這個(gè)。咦?這個(gè)不是說過了么?
BottomNavigationBar與AppBar里面的TabBar是不同的,一個(gè)是用來顯示于頂部,一個(gè)用來顯示與底部
在我們國內(nèi)的應(yīng)用中很少向這樣出現(xiàn)可以浮動選擇項(xiàng),所以如果想讓你的App不進(jìn)行浮動的話,可以使用里面的一個(gè)type
屬性。
type: BottomNavigationBarType.fixed,
BottomNavigationBarType有兩值,就是fixed,還有一個(gè)就是shifting,默認(rèn)是shifting。這樣設(shè)置之后仍然存在一個(gè)問題:就是選中的按鈕的字體仍然會比未選中的大一點(diǎn),有興趣的可以自己去驗(yàn)證一下。
那么這個(gè)問題改怎么辦呢?很遺憾,在最新穩(wěn)定版(Flutter Stable 1.2.1)SDK中并沒有處理這個(gè)問題的方式方法。如果想要解決這個(gè)問題的話,更換Flutter SDK到最新的開發(fā)版本(Flutter Dev 1.3.8),就可以使用它的屬性去解決這個(gè)問題。
selectedItemColor: colorRegular, //選中顏色
unselectedItemColor: colorBlack,//未選擇顏色
selectedFontSize: 12,//選中字體大小
unselectedFontSize: 12,//未選中字體大小
個(gè)人覺得這個(gè)FloatingActionButton
還是需要說明一下的,畢竟用的時(shí)候還是比較多的。FloatingActionButton
是一個(gè)浮動按鈕,也就是上面那個(gè)帶“+”的按鈕,這個(gè)可以用來添加,分享,或者是導(dǎo)航??梢耘cScaffold
中兩個(gè)屬性配合使用
FloatingActionButtonLocation
屬性可以移動浮動按鈕的位置,有如下幾個(gè)位置可以移動:
FloatingActionButtonLocation.endDocked //右側(cè)bottomNagivationBar遮蓋
FloatingActionButtonLocation.centerDocked //居中bottomNagivationBar上遮蓋
FloatingActionButtonLocation.endFloat //bottomNagivationBar上方右側(cè)顯示
FloatingActionButtonLocation.centerFloat //bottomNagivationBar上方居中顯示
自己可以試一試,這里就不一一演示,只演示一下這個(gè)centerDocked
FloatingActionButtonAnimator
就是FloatingActionButton
在出現(xiàn)位置FloatingActionButtonLocation
的動畫效果~~
需要注意以下幾點(diǎn):
FloatingActionButtonLocation
,那么就需要讓每一個(gè)浮動按鈕都有自己且唯一的heroTag
。onPressed
返回了null,那么它將不會對你的觸摸進(jìn)行任何反應(yīng),不推薦這樣去展示一個(gè)無任何響應(yīng)的浮動按鈕。經(jīng)常在我們的應(yīng)用中會使用到信息提示,那么我們就可以使用showSnackBar的方式去顯示一個(gè)簡短的提示,默認(rèn)顯示4s。
class SnackTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
),
)
);
}
}
一般我們會向如上方式處理,但是可能會拋出一個(gè)Scaffold.of() called with a context that does not contain a Scaffold.
的異常,也不會顯示出snackBar
。
這是因?yàn)椋?code>Scaffold.of()所需的context是Scaffold的,并不是Scaffold上方的build(BuildContext context)中的,這兩個(gè)并不是一個(gè)。
正確的方式是,創(chuàng)建自己的context:
class SnackTest extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text('Hello!'),
));
},
),
);
},
),
);
}
}
當(dāng)然還可以使用GlobalKey
的方式:
class ScaffoldTestState extends State<ScaffoldTest> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
void showSnackBar() {
_scaffoldKey.currentState
.showSnackBar(new SnackBar(content: Text("SnackBar is Showing!")));
}
return new Scaffold(
key: _scaffoldKey,
body: Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
showSnackBar(),
));
},
),
)
}
}
還有另一種也可以作為提示,就是bottomSheet:
這個(gè)與snackBar
的區(qū)別就是,雖然彈出了提示,但是不會自動消失,需要手動下拉才會消失。
class SnackTest extends StatelessWidget{
void showBottomSheet(BuildContext context) {
Scaffold.of(context).showBottomSheet((BuildContext context) {
return new Container(
constraints: BoxConstraints.expand(height: 100),
color: Color(0xFFFF786E),
alignment: Alignment.center,
child: new Text(
"BottomSheet is Showing!",
style: TextStyle(color: Color(0xFFFFFFFF)),
),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) {
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
showBottomSheet(context);
},
),
);
},
),
);
}
}
前面講了那么多都是為我們接下來的演示做準(zhǔn)備的,那先來看看登錄代碼:
class LoginPageState extends State<LoginPage> {
Color colorRegular = Color(0xFFFF786E);
Color colorLight = Color(0xFFFF978F);
Color colorInput = Color(0x40FFFFFF);
Color colorWhite = Colors.white;
TextStyle defaultTextStyle =
TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16);
BorderRadius radius = BorderRadius.all(Radius.circular(21));
void login() {
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: Container(
constraints: BoxConstraints.expand(),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [colorLight, colorRegular],
begin: Alignment.topCenter,
end: Alignment.bottomCenter)),
child: Column(
children: <Widget>[
Container (
margin: EdgeInsets.only(top: 110, bottom: 39, left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(21)), color: colorInput),
child: TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15,vertical: 9),
border: InputBorder.none,
hintText: "輸入手機(jī)號",
hintStyle: TextStyle(color: Colors.white, fontSize: 16),
labelStyle: TextStyle(color: Colors.black, fontSize: 16)),
maxLines: 1,
cursorColor: colorRegular,
keyboardType: TextInputType.phone,
),
),
Container(
margin: EdgeInsets.only(bottom: 58, left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: radius,
color: colorInput),
child: TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15,vertical: 9),
border: InputBorder.none,
hintText: "輸入密碼",
hintStyle: TextStyle(color: Colors.white, fontSize: 16),
labelStyle: TextStyle(color: Colors.black, fontSize: 16)),
maxLines: 1,
cursorColor: colorRegular,
keyboardType: TextInputType.number,
obscureText: true,
),
),
Container(
height: 42, width: 312,
margin: EdgeInsets.only(left: 24, right: 24),
decoration: BoxDecoration (
borderRadius: radius,
color: colorWhite),
child: RaisedButton(onPressed: login,
elevation: 1,
highlightElevation: 1,
textColor: colorRegular,
shape: RoundedRectangleBorder(
borderRadius: radius
),
child: new Text("立即登錄", style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold),
)),
),
Padding(
padding: EdgeInsets.only(top: 10),
child: Text(
"登錄/注冊即代表您已同意《會員協(xié)議》",
style: TextStyle(color: Colors.white, fontSize: 13),
),
),
],
),
),
);
}
}
在上一章就講過,如果在整個(gè)生命周期中,狀態(tài)如果改變,那么我們就是用StatefulWidget
來呈現(xiàn),并且StatefulWidget
的實(shí)現(xiàn)需要兩步:一個(gè)是需要?jiǎng)?chuàng)建繼承StatefulWidget
的類;另一個(gè)就是創(chuàng)建繼承State
的類,一般在State
中控制整個(gè)狀態(tài)。所以此處就是如此:
class LoginPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => LoginPageState();
}
class LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: Container(
//省略代碼
...
)
);
}
}
并且當(dāng)前登錄界面是沒有工具欄的,所以去掉了AppBar
。將所有內(nèi)容直接寫在了body
中??梢钥吹秸麄€(gè)登錄界面的背景是一個(gè)漸變,上面淺一點(diǎn),下面深一點(diǎn),所以就需要一個(gè)容器去包裹整個(gè)內(nèi)容,并且這個(gè)容器可以實(shí)現(xiàn)背景顏色的漸變的,所以我選用了Container
,因?yàn)樗撬腥萜鞑季种袑傩宰钊娴摹?/p>
Container({
Key key,
this.alignment,//子布局的排列方式
this.padding,//內(nèi)部填充
Color color,//背景顏色
Decoration decoration, //用于裝飾容器
this.foregroundDecoration,//前景裝飾
double width, //容器寬
double height, //容器高
BoxConstraints constraints, //約束
this.margin, //外部填充
this.transform, //對容器進(jìn)行變換
this.child,
})
提示:如果處于body
下的container
不論是否設(shè)置寬高,它將都會撲滿全屏。
那么最外層的漸變我們就是使用BoxDecoration
:
const BoxDecoration({
this.color,
this.image, 圖片
this.border, //邊框
this.borderRadius, //圓角
this.boxShadow, //陰影
this.gradient, //漸變
this.backgroundBlendMode, //背景模式,默認(rèn)BlendMode.srcOver
this.shape = BoxShape.rectangle, //形狀
}) : assert(shape != null),
assert(
backgroundBlendMode == null || color != null || gradient != null,
'backgroundBlendMode applies to BoxDecoration\'s background color or '
'gradient, but no color or gradient was provided.'
);
提示:在對形狀的處理中,以下是可以互換的:
所以從上可以完成我們的漸變:
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [colorLight, colorRegular],
begin: Alignment.topCenter,
end: Alignment.bottomCenter)
)
實(shí)現(xiàn)了漸變的過程,那么就是輸入框,可以從設(shè)計(jì)上來說,這些內(nèi)容都是縱向排列的,所以內(nèi)容使用了布局Column
,用于縱向布局,當(dāng)然相對的橫向布局Row
。
Column({
Key key,
//主軸排列方式,這里的主軸就是縱向,實(shí)際就是縱向的布局方式
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
//Column在主軸(縱向)占有的控件,默認(rèn)盡可能大
MainAxisSize mainAxisSize = MainAxisSize.max,
//交叉軸排列方式,那么就是橫向
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
//橫向子widget的布局順序
TextDirection textDirection,
//交叉軸的布局對齊方向
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
在Column
中包含了三個(gè)Container
,前兩個(gè)中是輸入布局TextField
,最后一個(gè)是RaisedButton
。這里回答在文章開始開始的時(shí)候提出的問題:為什么要用Container
去包裹TextField
?
所有需要使用Container
去完成這樣的樣式裝飾。
TextField
應(yīng)該是我們比較常用的widget了:
TextField(
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(horizontal: 15,vertical: 9),
border: InputBorder.none,
hintText: "輸入手機(jī)號",
hintStyle: TextStyle(color: Colors.white, fontSize: 16),
labelStyle: TextStyle(color: Colors.black, fontSize: 16)
),
maxLines: 1,
cursorColor: colorRegular,
keyboardType: TextInputType.phone,
),
這里只是使用可decoration
,對TextField
裝飾,比如其中的contentPadding
,對內(nèi)容留白填補(bǔ)。cursorColor
光標(biāo)顏色,輸入類型keyboardType
,這里是手機(jī)號類型。此外還有很多的屬性,這里就不一一贅述,可以自行到官網(wǎng)去查看。
最后被container
包裹是的RaisedButton
:
RaisedButton(
onPressed: login,
elevation: 1,
highlightElevation: 1,
textColor: colorRegular,
shape: RoundedRectangleBorder(
borderRadius: radius
),
child: new Text("立即登錄", style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold),
))
到處登錄界面的布局就算完成了,然后運(yùn)行之后就會出現(xiàn)在文章開頭的登錄界面,但是當(dāng)我們點(diǎn)擊TextField
進(jìn)行輸入的時(shí)候,會發(fā)現(xiàn)整個(gè)布局會被頂上去了,這是為什么呢?。
答:這是因?yàn)?code>Scaffold會填充整個(gè)可用空間,當(dāng)軟鍵盤從Scaffold
布局中出現(xiàn),那么在這種情況下,可用空間變少Scaffold
就會重新計(jì)算大小,這也就是為什么Scaffold
會將我們的布局全部上移的根本原因,為了避免這種情況,可以使用resizeToAvoidBottomInset
并將其
置為false
就可以了。
debug
標(biāo)簽?答:將MaterialApp中的debugShowCheckedModeBanner
置為false
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Color(0xFFFF786E),
primaryColorLight: Color(0xFFFF978F),
accentColor: Color(0xFFFFFFFF)
),
home: LoginPage(),
debugShowCheckedModeBanner: false,
);
}
}
runApp(new MyApp());
if (Platform.isAndroid) {
// 以下兩行 設(shè)置android狀態(tài)欄為透明的沉浸。
//寫在組件渲染之后,是為了在渲染后進(jìn)行set賦值,
//覆蓋狀態(tài)欄,寫在渲染之前MaterialApp組件會覆蓋掉這個(gè)值。
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
最后給大家推薦一本Flutter書,詳細(xì)介紹了Flutter的使用方式方法,都提供了演示案例:Flutter實(shí)戰(zhàn):
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。