Flutter入門篇(二)

在上一篇文章中以簡單的方式對Flutter本身提供的演示進行了一個簡單的分析,固然那是遠遠不夠。原本打算爲你們帶來官網上的無限下拉刷新的案例,可是發現這裏的有些東西實在是太超前了,做爲Flutter入門篇,固然不能這麼隨意,覺得了讓你們都可以學有所得,因此今天給你們帶來了本身手擼的一個登陸。java

登陸演示

簡單分析佈局

咱們都知道,一個簡單的登陸須要至少須要3步:android

  • 輸入帳號
  • 輸入密碼
  • 點擊登陸

那麼咱們的佈局也就至少須要3個widget,爲何說至少呢?由於每每佈局使用的widget都是大於操做步驟的。這裏跟你們分享個人佈局大概有這麼幾個:git

  • 整個外層框框,就是那個淡紅色的漸變底色,是一個容器widget,能夠包裹裏面的全部內容。
  • 在這裏面是一個縱向的佈局widget,讓全部的內容成縱向排列。
  • 裏面輸入手機號和輸入密碼那裏都是=容器,能夠包裹輸入框。爲何要使用這個容器呢,直接使用輸入widget很差嗎?這裏允許我先買個關子~~
  • 接下來就是一個按鈕
  • 最後就是顯示文字的佈局

Scaffold

爲何要講解這個呢?這是由於它是實現了Mataril Design的一種簡單的「腳手架」,有些也叫「支架」,經過這個翻譯也就知道了,其實它就是向咱們提供了簡單的框架,咱們直接使用它就好了。那麼問題來了,咱們可不能夠不使用它呢?固然是能夠的,可是不建議這樣作,由於咱們後面須要使用的不少widget(好比TextField)必需要在它的支持下才能運行,否則就會報錯了。github

class Scaffold extends StatefulWidget {
  /// Creates a visual scaffold for material design widgets.
  const Scaffold({ Key key, this.appBar, //橫向水平佈局,一般顯示在頂部(*) this.body, // 內容(*) this.floatingActionButton, //懸浮按鈕,就是上圖右下角按鈕(*) this.floatingActionButtonLocation, //懸浮按鈕位置 //懸浮按鈕在[floatingActionButtonLocation]出現/消失動畫 this.floatingActionButtonAnimator, //在底部呈現一組button,顯示於[bottomNavigationBar]之上,[body]之下 this.persistentFooterButtons, //一個垂直面板,顯示於左側,初始處於隱藏狀態(*) this.drawer, this.endDrawer, //出現於底部的一系列水平按鈕(*) this.bottomNavigationBar, //底部持久化提示框 this.bottomSheet, //內容背景顏色 this.backgroundColor, //棄用,使用[resizeToAvoidBottomInset] this.resizeToAvoidBottomPadding, //從新計算佈局空間大小 this.resizeToAvoidBottomInset, //是否顯示到底部,默認爲true將顯示到頂部狀態欄 this.primary = true, // this.drawerDragStartBehavior = DragStartBehavior.down, }) : assert(primary != null), assert(drawerDragStartBehavior != null), super(key: key);
複製代碼

從這裏面,咱們能夠看出Scaffold提供了不少的方式方法,去實現Mataril Design的佈局:bash

AppBar

通常就用於Scaffold.appBar,是一個置於屏幕頂部的橫向佈局,爲何是橫向呢?能夠以下中看出:app

AppBar-橫向佈局

我在它其中的anctions屬性中設置了多個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須要說明一下,通常咱們用它來顯示一個按鈕去關閉當前頁面或者打開一個drawer。有興趣的能夠去試試~~less

AppBar衆多的屬性中,還有一個是咱們比較經常使用的,那就是bottom,這個顯示於工具欄的下方,注意不是屏幕底部哦!通常使用TabBar來實現一個頁面包含中多個不一樣頁面的切換。 ide

AppBar-使用tabBar

固然還有其餘一些方式方法,這裏就很少佔用篇幅了,就簡單聊聊:工具

  • title就是標題
  • drawer抽屜,通常左側打開,默認初始隱藏
  • centerTitle 是否標題居中

若是想看完整的實現方式,就跟我來吧

BottomNavigationBar

這個屬性也是至關重要的,若是咱們想要實現多個,不一樣頁面的切換,就可使用這個。咦?這個不是說過了麼?


BottomNavigationBar與AppBar裏面的TabBar是不一樣的,一個是用來顯示於頂部,一個用來顯示與底部


BottomNavigationBar

在咱們國內的應用中不多向這樣出現能夠浮動選擇項,因此若是想讓你的App不進行浮動的話,可使用裏面的一個type屬性。

type: BottomNavigationBarType.fixed,
複製代碼

BottomNavigationBarType有兩值,就是fixed,還有一個就是shifting,默認是shifting。這樣設置以後仍然存在一個問題:就是選中的按鈕的字體仍然會比未選中的大一點,有興趣的能夠本身去驗證一下。

BottomNavigationBar-選中
那麼這個問題改怎麼辦呢?很遺憾,在最新穩定版(Flutter Stable 1.2.1)SDK中並無處理這個問題的方式方法。若是想要解決這個問題的話,更換Flutter SDK到最新的開發版本(Flutter Dev 1.3.8),就可使用它的屬性去解決這個問題。

selectedItemColor: colorRegular, //選中顏色
unselectedItemColor: colorBlack,//未選擇顏色
selectedFontSize: 12,//選中字體大小
unselectedFontSize: 12,//未選中字體大小
複製代碼

FloatingActionButton

我的以爲這個FloatingActionButton仍是須要說明一下的,畢竟用的時候仍是比較多的。FloatingActionButton是一個浮動按鈕,也就是上面那個帶「+」的按鈕,這個能夠用來添加,分享,或者是導航。能夠與Scaffold中兩個屬性配合使用

  • FloatingActionButtonLocation
  • FloatingActionButtonAnimator

FloatingActionButtonLocation屬性能夠移動浮動按鈕的位置,有以下幾個位置能夠移動:

FloatingActionButtonLocation.endDocked //右側bottomNagivationBar遮蓋
FloatingActionButtonLocation.centerDocked //居中bottomNagivationBar上遮蓋
FloatingActionButtonLocation.endFloat //bottomNagivationBar上方右側顯示
FloatingActionButtonLocation.centerFloat //bottomNagivationBar上方居中顯示
複製代碼

本身能夠試一試,這裏就不一一演示,只演示一下這個centerDocked

浮動居中

FloatingActionButtonAnimator就是FloatingActionButton在出現位置FloatingActionButtonLocation的動畫效果~~

須要注意如下幾點:

  • 若是一個頁面有多個FloatingActionButtonLocation,那麼就須要讓每個浮動按鈕都有本身且惟一的heroTag
  • 若是onPressed返回了null,那麼它將不會對你的觸摸進行任何反應,不推薦這樣去展現一個無任何響應的浮動按鈕。

SnackBar

常常在咱們的應用中會使用到信息提示,那麼咱們就可使用showSnackBar的方式去顯示一個簡短的提示,默認顯示4s。

SnackBar

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!'),
            ));
          },
        ),
      )
    );
  }
}
複製代碼

通常咱們會向如上方式處理,可是可能會拋出一個Scaffold.of() called with a context that does not contain a Scaffold.的異常,也不會顯示出snackBar
這是由於,Scaffold.of()所需的context是Scaffold的,並非Scaffold上方的build(BuildContext context)中的,這兩個並非一個。

正確的方式是,建立本身的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!'),
                ));
              },
            ),
          );
        },
      ),
    );
  }
}
複製代碼

固然還可使用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:

BottomSheet
這個與 snackBar的區別就是,雖然彈出了提示,可是不會自動消失,須要手動下拉纔會消失。

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);
              },
            ),
          );
        },
      ),
    );
  }
}
複製代碼

實現登陸

前面講了那麼多都是爲咱們接下來的演示作準備的,那先來看看登陸代碼:

登陸演示

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: "輸入手機號",
                    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(
                "登陸/註冊即表明您已贊成《會員協議》",
                style: TextStyle(color: Colors.white, fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
複製代碼

在上一章就講過,若是在整個生命週期中,狀態若是改變,那麼咱們就是用StatefulWidget來呈現,而且StatefulWidget的實現須要兩步:一個是須要建立繼承StatefulWidget的類;另外一個就是建立繼承State的類,通常在State中控制整個狀態。因此此處就是如此:

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(
      //省略代碼
      ...
      )
    );
  }
}
複製代碼

而且當前登陸界面是沒有工具欄的,因此去掉了AppBar。將全部內容直接寫在了body中。能夠看到整個登陸界面的背景是一個漸變,上面淺一點,下面深一點,因此就須要一個容器去包裹整個內容,而且這個容器能夠實現背景顏色的漸變的,因此我選用了Container,由於它是全部容器佈局中屬性最全面的。

Container({
    Key key,
    this.alignment,//子佈局的排列方式
    this.padding,//內部填充
    Color color,//背景顏色
    Decoration decoration,  //用於裝飾容器
    this.foregroundDecoration,//前景裝飾
    double width, //容器寬
    double height, //容器高
    BoxConstraints constraints, //約束
    this.margin, //外部填充
    this.transform, //對容器進行變換
    this.child,
  })
複製代碼

提示:若是處於body下的container不管是否設置寬高,它將都會撲滿全屏。

那麼最外層的漸變咱們就是使用BoxDecoration

const BoxDecoration({ this.color, this.image, 圖片 this.border, //邊框 this.borderRadius, //圓角 this.boxShadow, //陰影 this.gradient, //漸變 this.backgroundBlendMode, //背景模式,默認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.' );
複製代碼

提示:在對形狀的處理中,如下是能夠互換的:

  • CircleBorder === BoxShape.circle
  • RoundedRectangleBorder == BoxShape.rectangle

因此從上能夠完成咱們的漸變:

decoration: BoxDecoration(
            gradient: LinearGradient(
                colors: [colorLight, colorRegular],
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter)
        )
複製代碼

實現了漸變的過程,那麼就是輸入框,能夠從設計上來講,這些內容都是縱向排列的,因此內容使用了佈局Column,用於縱向佈局,固然相對的橫向佈局Row

Column({
    Key key,
    //主軸排列方式,這裏的主軸就是縱向,實際就是縱向的佈局方式
    MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, 
    //Column在主軸(縱向)佔有的控件,默認儘量大
    MainAxisSize mainAxisSize = MainAxisSize.max,
    //交叉軸排列方式,那麼就是橫向
    CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
    //橫向子widget的佈局順序
    TextDirection textDirection,
    //交叉軸的佈局對齊方向
    VerticalDirection verticalDirection = VerticalDirection.down,
    TextBaseline textBaseline,
    List<Widget> children = const <Widget>[],
  })
複製代碼

Column中包含了三個Container,前兩個中是輸入佈局TextField,最後一個是RaisedButton。這裏回答在文章開始開始的時候提出的問題:爲何要用Container去包裹TextField

  1. 須要實現圓角 (decoration)
  2. 要實現間距 (marin 和 padding)

全部須要使用Container去完成這樣的樣式裝飾。

TextField應該是咱們比較經常使用的widget了:

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.phone,
),
複製代碼

這裏只是使用可decoration,對TextField裝飾,好比其中的contentPadding,對內容留白填補。 cursorColor光標顏色,輸入類型keyboardType,這裏是手機號類型。此外還有不少的屬性,這裏就不一一贅述,能夠自行到官網去查看。

最後被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),
))
複製代碼

我也修飾一下

  • 處處登陸界面的佈局就算完成了,而後運行以後就會出如今文章開頭的登陸界面,可是當咱們點擊TextField進行輸入的時候,會發現整個佈局會被頂上去了,這是爲何呢?。

答:這是由於Scaffold會填充整個可用空間,當軟鍵盤從Scaffold佈局中出現,那麼在這種狀況下,可用空間變少Scaffold就會從新計算大小,這也就是爲何Scaffold會將咱們的佈局所有上移的根本緣由,爲了不這種狀況,可使用resizeToAvoidBottomInset並將其 置爲false就能夠了。


  • 怎麼去掉屏幕右上角的debug標籤?

答:將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) {
    // 如下兩行 設置android狀態欄爲透明的沉浸。
    //寫在組件渲染以後,是爲了在渲染後進行set賦值,
    //覆蓋狀態欄,寫在渲染以前MaterialApp組件會覆蓋掉這個值。
    SystemUiOverlayStyle systemUiOverlayStyle = 
    SystemUiOverlayStyle(statusBarColor: Colors.transparent);
    SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
  }
複製代碼

最後給你們推薦一本Flutter書,詳細介紹了Flutter的使用方式方法,都提供了演示案例:Flutter實戰:

Flutter實戰
相關文章
相關標籤/搜索