Flutter入門篇(二)

在上一篇文章中以簡單的方式對Flutter自己提供的演示進行了一個簡單的分析,當然那是遠遠不夠。本來打算為大家帶來官網上的無限下拉刷新的案例,但是發現這里的有些東西實在是太超前了,作為Flutter入門篇,當然不能這么隨意,以為了讓大家都能夠學有所得,所以今天給大家帶來了自己手擼的一個登錄。

登錄演示

簡單分析布局

我們都知道,一個簡單的登錄需要至少需要3步:

  • 輸入賬號
  • 輸入密碼
  • 點擊登錄

那么我們的布局也就至少需要3個widget,為什么說至少呢?因為往往布局使用的widget都是大于操作步驟的。這里跟大家分享我的布局大概有這么幾個:

  • 整個外層框框,就是那個淡紅色的漸變底色,是一個容器widget,可以包裹里面的所有內容。
  • 在這里面是一個縱向的布局widget,讓所有的內容成縱向排列。
  • 里面輸入手機號和輸入密碼那里都是=容器,可以包裹輸入框。為什么要使用這個容器呢,直接使用輸入widget不好嗎?這里容許我先買個關子~~
  • 接下來就是一個按鈕
  • 最后就是顯示文字的布局

Scaffold

為什么要講解這個呢?這是因為它是實現了Mataril Design的一種簡單的“腳手架”,有些也叫“支架”,通過這個翻譯也就知道了,其實它就是向我們提供了簡單的框架,我們直接使用它就行了。那么問題來了,我們可不可以不使用它呢?當然是可以的,但是不建議這樣做,因為我們后面需要使用的很多widget(比如TextField)必須要在它的支持下才能運行,不然就會報錯了。

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的布局:

AppBar

一般就用于Scaffold.appBar,是一個置于屏幕頂部的橫向布局,為什么是橫向呢?可以如下中看出:

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。有興趣的可以去試試~~

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

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是一個浮動按鈕,也就是上面那個帶“+”的按鈕,這個可以用來添加,分享,或者是導航??梢耘cScaffold中兩個屬性配合使用

  • 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實戰

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容

  • Flutter是Google開發的一套全新的跨平臺、開源UI框架(本質上就是sdk)。 支持iOS、Android...
    HarveyLegend閱讀 8,236評論 1 43
  • 國慶后面兩天在家學習整理了一波flutter,基本把能擼過能看到的代碼都過了一遍,此文篇幅較長,建議保存(star...
    Nealyang閱讀 4,358評論 1 17
  • 本文主要介紹了Flutter布局相關的內容,對相關知識點進行了梳理,并從實際例子觸發,進一步講解該如何去進行布局。...
    Q吹個大氣球Q閱讀 9,836評論 6 51
  • 轉眼,時間來到2018年的4月2號。 整整3月份都在辦一件大事,終于完成一個目標。 4月開始朝著下一個目標奮斗! ...
    蕭小月閱讀 444評論 0 2
  • 題目:輸入數字n,按順序打印出從1最大的n位十進制數,比如輸入3,則打印從1,2,3一直到最大的3位數即999
    Felicia1993閱讀 130評論 0 0