Flutter(八)之Flutter的基礎Widget

coderwhy發表於2019-09-27

Flutter的基礎Widget

前言一:接下來一段時間我會陸續更新一些列Flutter文字教程

更新進度: 每週至少兩篇;

更新地點: 首發於公眾號,第二天更新於掘金、思否、開發者頭條等地方;

更多交流: 可以新增我的微信 372623326,關注我的微博:coderwhy

希望大家可以 幫忙轉發,點選在看,給我更多的創作動力。

前言二:這個章節本來打算講解Flutter的渲染原理,但是學習初期過多的講解原理性的內容,並不利於大家快速入門和上手,做出一些帶效果的內容;

所以,我打算換一種思路,先講解一些元件的用法,讓大家習慣Flutter的開發過程和模式,再回頭去鞏固原理性的知識;

另外,在講解這些Widget的時候,我並不打算將所有的屬性一一列出,因為沒有意義,也記不住;

我後面打算有一個專題是關於Flutter佈局的,會選出一些好看的佈局介面帶著大家一起來完成:美團頁面、京東頁面、B站頁面等等,某些我目前沒有講到的屬性,後面應用的會再進行講解;

1. 文字Widget

在Android中,我們使用TextView,iOS中我們使用UILabel來顯示文字;

Flutter中,我們使用Text元件控制文字如何展示;

1.1. 普通文字展示

在Flutter中,我們可以將文字的控制顯示分成兩類:

  • 控制文字佈局的引數: 如文字對齊方式 textAlign、文字排版方向 textDirection,文字顯示最大行數 maxLines、文字截斷規則 overflow 等等,這些都是建構函式中的引數;
  • 控制文字樣式的引數: 如字型名稱 fontFamily、字型大小 fontSize、文字顏色 color、文字陰影 shadows 等等,這些引數被統一封裝到了建構函式中的引數 style 中;

下面我們來看一下其中一些屬性的使用:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      "《定風波》 蘇軾 \n莫聽穿林打葉聲,何妨吟嘯且徐行。\n竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。",
      style: TextStyle(
        fontSize: 20,
        color: Colors.purple
      ),
    );
  }
}
複製程式碼

展示效果如下:

image-20190902101522966

我們可以通過一些屬性來改變Text的佈局:

  • textAlign:文字對齊方式,比如TextAlign.center
  • maxLines:最大顯示行數,比如1
  • overflow:超出部分顯示方式,比如TextOverflow.ellipsis
  • textScaleFactor:控制文字縮放,比如1.24

程式碼如下:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(
      "《定風波》 蘇軾 \n莫聽穿林打葉聲,何妨吟嘯且徐行。\n竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。",
      textAlign: TextAlign.center, // 所有內容都居中對齊
      maxLines: 3, // 顯然 "生。" 被刪除了
      overflow: TextOverflow.ellipsis, // 超出部分顯示...
//      textScaleFactor: 1.25,
      style: TextStyle(
        fontSize: 20,
        color: Colors.purple
      ),
    );
  }
}
複製程式碼

image-20190902102035307

1.2. 富文字展示

前面展示的文字,我們都應用了相同的樣式,如果我們希望給他們不同的樣式呢?

  • 比如《定風波》我希望字型更大一點,並且是黑色字型,並且有加粗效果;
  • 比如 蘇軾 我希望是紅色字型;

如果希望展示這種混合樣式,那麼我們可以利用分片來進行操作(在Android中,我們可以使用SpannableString,在iOS中,我們可以使用NSAttributedString完成,瞭解即可)

程式碼如下:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text.rich(
      TextSpan(
        children: [
          TextSpan(text: "《定風波》", style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold, color: Colors.black)),
          TextSpan(text: "蘇軾", style: TextStyle(fontSize: 18, color: Colors.redAccent)),
          TextSpan(text: "\n莫聽穿林打葉聲,何妨吟嘯且徐行。\n竹杖芒鞋輕勝馬,誰怕?一蓑煙雨任平生。")
        ],
      ),
      style: TextStyle(fontSize: 20, color: Colors.purple),
      textAlign: TextAlign.center,
    );
  }
}
複製程式碼

image-20190902103333353

二. 按鈕Widget

2.1. 按鈕的基礎

Material widget庫中提供了多種按鈕Widget如FloatingActionButton、RaisedButton、FlatButton、OutlineButton等

我們直接來對他們進行一個展示:

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        FloatingActionButton(
          child: Text("FloatingActionButton"),
          onPressed: () {
            print("FloatingActionButton Click");
          },
        ),
        RaisedButton(
          child: Text("RaisedButton"),
          onPressed: () {
            print("RaisedButton Click");
          },
        ),
        FlatButton(
          child: Text("FlatButton"),
          onPressed: () {
            print("FlatButton Click");
          },
        ),
        OutlineButton(
          child: Text("OutlineButton"),
          onPressed: () {
            print("OutlineButton Click");
          },
        )
      ],
    );
  }
}
複製程式碼

image-20190902105017343

2.2. 自定義樣式

前面的按鈕我們使用的都是預設樣式,我們可以通過一些屬性來改變按鈕的樣式

RaisedButton(
  child: Text("同意協議", style: TextStyle(color: Colors.white)),
  color: Colors.orange, // 按鈕的顏色
  highlightColor: Colors.orange[700], // 按下去高亮顏色
  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), // 圓角的實現
  onPressed: () {
    print("同意協議");
  },
)
複製程式碼

image-20190902110011756

事實上這裡還有一個比較常見的屬性:elevation,用於控制陰影的大小,很多地方都會有這個屬性,大家可以自行演示一下

三. 圖片Widget

圖片可以讓我們的應用更加豐富多彩,Flutter中使用Image元件

Image元件有很多的建構函式,我們這裡主要學習兩個:

  • Image.assets:載入本地資源圖片;
  • Image.network:載入網路中的圖片;

3.1. 載入網路圖片

相對來講,Flutter中載入網路圖片會更加簡單,直接傳入URL並不需要什麼配置,所以我們先來看一下Flutter中如何載入網路圖片。

我們先來看看Image有哪些屬性可以設定:

const Image({
  ...
  this.width, //圖片的寬
  this.height, //圖片高度
  this.color, //圖片的混合色值
  this.colorBlendMode, //混合模式
  this.fit,//縮放模式
  this.alignment = Alignment.center, //對齊方式
  this.repeat = ImageRepeat.noRepeat, //重複方式
  ...
})
複製程式碼
  • widthheight:用於設定圖片的寬、高,當不指定寬高時,圖片會根據當前父容器的限制,儘可能的顯示其原始大小,如果只設定widthheight的其中一個,那麼另一個屬性預設會按比例縮放,但可以通過下面介紹的fit屬性來指定適應規則。
  • fit:該屬性用於在圖片的顯示空間和圖片本身大小不同時指定圖片的適應模式。適應模式是在BoxFit中定義,它是一個列舉型別,有如下值:
    • fill:會拉伸填充滿顯示空間,圖片本身長寬比會發生變化,圖片會變形。
    • cover:會按圖片的長寬比放大後居中填滿顯示空間,圖片不會變形,超出顯示空間部分會被剪裁。
    • contain:這是圖片的預設適應規則,圖片會在保證圖片本身長寬比不變的情況下縮放以適應當前顯示空間,圖片不會變形。
    • fitWidth:圖片的寬度會縮放到顯示空間的寬度,高度會按比例縮放,然後居中顯示,圖片不會變形,超出顯示空間部分會被剪裁。
    • fitHeight:圖片的高度會縮放到顯示空間的高度,寬度會按比例縮放,然後居中顯示,圖片不會變形,超出顯示空間部分會被剪裁。
    • none:圖片沒有適應策略,會在顯示空間內顯示圖片,如果圖片比顯示空間大,則顯示空間只會顯示圖片中間部分。
  • colorcolorBlendMode:在圖片繪製時可以對每一個畫素進行顏色混合處理,color指定混合色,而colorBlendMode指定混合模式;
  • repeat:當圖片本身大小小於顯示空間時,指定圖片的重複規則。

我們對其中某些屬性做一個演練:

  • 注意,這裡我用了一個Container,大家可以把它理解成一個UIView或者View,就是一個容器;
  • 後面我會專門講到這個元件的使用;
class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Image.network(
          "http://img0.dili360.com/ga/M01/48/3C/wKgBy1kj49qAMVd7ADKmuZ9jug8377.tub.jpg",
          alignment: Alignment.topCenter,
          repeat: ImageRepeat.repeatY,
          color: Colors.red,
          colorBlendMode: BlendMode.colorDodge,
        ),
        width: 300,
        height: 300,
        color: Colors.yellow,
      ),
    );
  }
}
複製程式碼

image-20190902113310213

3.2. 載入本地圖片

載入本地圖片稍微麻煩一點,需要將圖片引入,並且進行配置

class MyHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        width: 300,
        height: 300,
        color: Colors.yellow,
        child: Image.asset("images/test.jpeg"),
      ),
    );
  }
}
複製程式碼

image-20190902114616699

3.3. 實現圓角影象

在Flutter中實現圓角效果也是使用一些Widget來實現的。

3.3.1. 實現圓角頭像

方式一:CircleAvatar

CircleAvatar可以實現圓角頭像,也可以新增一個子Widget:

const CircleAvatar({
  Key key,
  this.child, // 子Widget
  this.backgroundColor, // 背景顏色
  this.backgroundImage, // 背景影象
  this.foregroundColor, // 前景顏色
  this.radius, // 半徑
  this.minRadius, // 最小半徑
  this.maxRadius, // 最大半徑
}) 
複製程式碼

我們來實現一個圓形頭像:

  • 注意一:這裡我們使用的是NetworkImage,因為backgroundImage要求我們傳入一個ImageProvider;

    • ImageProvider是一個抽象類,事實上所有我們前面建立的Image物件都有包含image屬性,該屬性就是一個ImageProvider
  • 注意二:這裡我還在裡面新增了一個文字,但是我在文字外層包裹了一個Container;

    • 這裡Container的作用是為了可以控制文字在其中的位置調整;
class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: CircleAvatar(
        radius: 100,
        backgroundImage: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"),
        child: Container(
          alignment: Alignment(0, .5),
          width: 200,
          height: 200,
          child: Text("兵長利威爾")
        ),
      ),
    );
  }
}

複製程式碼

image-20190924081343639

方式二:ClipOval

ClipOval也可以實現圓角頭像,而且通常是在只有頭像時使用

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ClipOval(
        child: Image.network(
          "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
          width: 200,
          height: 200,
        ),
      ),
    );
  }
}

複製程式碼

image-20190924092127687

實現方式三:Container+BoxDecoration

這種方式我們放在講解Container時來講這種方式

3.3.2. 實現圓角圖片

方式一:ClipRRect

ClipRRect用於實現圓角效果,可以設定圓角的大小。

實現程式碼如下,非常簡單:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ClipRRect(
        borderRadius: BorderRadius.circular(10),
        child: Image.network(
          "https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg",
          width: 200,
          height: 200,
        ),
      ),
    );
  }
}
複製程式碼

image-20190924094516174

方式二:Container+BoxDecoration

這個也放到後面講解Container時講解

四. 表單Widget

和使用者互動的其中一種就是輸入框,比如註冊、登入、搜尋,我們收集使用者輸入的內容將其提交到伺服器。

4.1. TextField的使用

4.1.1. TextField的介紹

TextField用於接收使用者的文字輸入,它提供了非常多的屬性,我們來看一下原始碼:

  • 但是我們沒必要一個個去學習,很多時候用到某個功能時去檢視是否包含某個屬性即可
const TextField({
  Key key,
  this.controller,
  this.focusNode,
  this.decoration = const InputDecoration(),
  TextInputType keyboardType,
  this.textInputAction,
  this.textCapitalization = TextCapitalization.none,
  this.style,
  this.strutStyle,
  this.textAlign = TextAlign.start,
  this.textAlignVertical,
  this.textDirection,
  this.readOnly = false,
  ToolbarOptions toolbarOptions,
  this.showCursor,
  this.autofocus = false,
  this.obscureText = false,
  this.autocorrect = true,
  this.maxLines = 1,
  this.minLines,
  this.expands = false,
  this.maxLength,
  this.maxLengthEnforced = true,
  this.onChanged,
  this.onEditingComplete,
  this.onSubmitted,
  this.inputFormatters,
  this.enabled,
  this.cursorWidth = 2.0,
  this.cursorRadius,
  this.cursorColor,
  this.keyboardAppearance,
  this.scrollPadding = const EdgeInsets.all(20.0),
  this.dragStartBehavior = DragStartBehavior.start,
  this.enableInteractiveSelection = true,
  this.onTap,
  this.buildCounter,
  this.scrollController,
  this.scrollPhysics,
}) 

複製程式碼

我們來學習幾個比較常見的屬性:

  • 一些屬性比較簡單:keyboardType鍵盤的型別,style設定樣式,textAlign文字對齊方式,maxLength最大顯示行數等等;
  • decoration:用於設定輸入框相關的樣式
    • icon:設定左邊顯示的圖示
    • labelText:在輸入框上面顯示一個提示的文字
    • hintText:顯示提示的佔位文字
    • border:輸入框的邊框,預設底部有一個邊框,可以通過InputBorder.none刪除掉
    • filled:是否填充輸入框,預設為false
    • fillColor:輸入框填充的顏色
  • controller
  • onChanged:監聽輸入框內容的改變,傳入一個回撥函式
  • onSubmitted:點選鍵盤中右下角的down時,會回撥的一個函式

4.1.2. TextField的樣式以及監聽

我們來演示一下TextField的decoration屬性以及監聽:

class HomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFieldDemo()
        ],
      ),
    );
  }
}

class TextFieldDemo extends StatefulWidget {
  @override
  _TextFieldDemoState createState() => _TextFieldDemoState();
}

class _TextFieldDemoState extends State<TextFieldDemo> {
  @override
  Widget build(BuildContext context) {
    return TextField(
      decoration: InputDecoration(
        icon: Icon(Icons.people),
        labelText: "username",
        hintText: "請輸入使用者名稱",
        border: InputBorder.none,
        filled: true,
        fillColor: Colors.lightGreen
      ),
      onChanged: (value) {
        print("onChanged:$value");
      },
      onSubmitted: (value) {
        print("onSubmitted:$value");
      },
    );
  }
}
複製程式碼

image-20190923165526780

4.1.3. TextField的controller

我們可以給TextField新增一個控制器(Controller),可以使用它設定文字的初始值,也可以使用它來監聽文字的改變;

事實上,如果我們沒有為TextField提供一個Controller,那麼會Flutter會預設建立一個TextEditingController的,這個結論可以通過閱讀原始碼得到:

  @override
  void initState() {
    super.initState();
    // ...其他程式碼
    if (widget.controller == null)
      _controller = TextEditingController();
  }

複製程式碼

我們也可以自己來建立一個Controller控制一些內容:

class _TextFieldDemoState extends State<TextFieldDemo> {
  final textEditingController = TextEditingController();

  @override
  void initState() {
    super.initState();

    // 1.設定預設值
    textEditingController.text = "Hello World";

    // 2.監聽文字框
    textEditingController.addListener(() {
      print("textEditingController:${textEditingController.text}");
    });
  }
	
  // ...省略build方法
}

複製程式碼

image-20190923171132816

4.2. Form表單的使用

在我們開發註冊、登入頁面時,通常會有多個表單需要同時獲取內容或者進行一些驗證,如果對每一個TextField都分別進行驗證,是一件比較麻煩的事情。

做過前端的開發知道,我們可以將多個input標籤放在一個form裡面,Flutter也借鑑了這樣的思想:我們可以通過Form對輸入框進行分組,統一進行一些操作。

4.2.1. Form表單的基本使用

Form表單也是一個Widget,可以在裡面放入我們的輸入框。

但是Form表單中輸入框必須是FormField型別的

  • 我們檢視剛剛學過的TextField是繼承自StatefulWidget,並不是一個FormField型別;
  • 我們可以使用TextFormField,它的使用類似於TextField,並且是繼承自FormField的;

我們通過Form的包裹,來實現一個註冊的頁面:

class FormDemo extends StatefulWidget {
  @override
  _FormDemoState createState() => _FormDemoState();
}

class _FormDemoState extends State<FormDemo> {
  @override
  Widget build(BuildContext context) {
    return Form(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(
              icon: Icon(Icons.people),
              labelText: "使用者名稱或手機號"
            ),
          ),
          TextFormField(
            obscureText: true,
            decoration: InputDecoration(
              icon: Icon(Icons.lock),
              labelText: "密碼"
            ),
          ),
          SizedBox(height: 16,),
          Container(
            width: double.infinity,
            height: 44,
            child: RaisedButton(
              color: Colors.lightGreen,
              child: Text("注 冊", style: TextStyle(fontSize: 20, color: Colors.white),),
              onPressed: () {
                print("點選了註冊按鈕");
              },
            ),
          )
        ],
      ),
    );
  }
}
複製程式碼

image-20190923175224983

4.2.2. 儲存和獲取表單資料

有了表單後,我們需要在點選註冊時,可以同時獲取和儲存表單中的資料,怎麼可以做到呢?

  • 1、需要監聽註冊按鈕的點選,在之前我們已經監聽的onPressed傳入的回撥中來做即可。(當然,如果巢狀太多,我們待會兒可以將它抽取到一個單獨的方法中)
  • 2、監聽到按鈕點選時,同時獲取使用者名稱密碼的表單資訊。

如何同時獲取使用者名稱密碼的表單資訊?

  • 如果我們呼叫Form的State物件的save方法,就會呼叫Form中放入的TextFormField的onSave回撥:
TextFormField(
  decoration: InputDecoration(
    icon: Icon(Icons.people),
    labelText: "使用者名稱或手機號"
  ),
  onSaved: (value) {
    print("使用者名稱:$value");
  },
),
複製程式碼
  • 但是,我們有沒有辦法可以在點選按鈕時,拿到 Form物件 來呼叫它的save方法呢?

知識點:在Flutter如何可以獲取一個通過一個引用獲取一個StatefulWidget的State物件呢?

答案:通過繫結一個GlobalKey即可。

image-20190923202433788

案例程式碼演練:

class FormDemo extends StatefulWidget {
  @override
  _FormDemoState createState() => _FormDemoState();
}

class _FormDemoState extends State<FormDemo> {
  final registerFormKey = GlobalKey<FormState>();
  String username, password;

  void registerForm() {
    registerFormKey.currentState.save();

    print("username:$username password:$password");
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: registerFormKey,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(
              icon: Icon(Icons.people),
              labelText: "使用者名稱或手機號"
            ),
            onSaved: (value) {
              this.username = value;
            },
          ),
          TextFormField(
            obscureText: true,
            decoration: InputDecoration(
              icon: Icon(Icons.lock),
              labelText: "密碼"
            ),
            onSaved: (value) {
              this.password = value;
            },
          ),
          SizedBox(height: 16,),
          Container(
            width: double.infinity,
            height: 44,
            child: RaisedButton(
              color: Colors.lightGreen,
              child: Text("注 冊", style: TextStyle(fontSize: 20, color: Colors.white),),
              onPressed: registerForm,
            ),
          )
        ],
      ),
    );
  }
}
複製程式碼

image-20190923202832219

4.2.3. 驗證填寫的表單資料

在表單中,我們可以新增驗證器,如果不符合某些特定的規則,那麼給使用者一定的提示資訊

比如我們需要賬號和密碼有這樣的規則:賬號和密碼都不能為空。

按照如下步驟就可以完成整個驗證過程:

  • 1、為TextFormField新增validator的回撥函式;
  • 2、呼叫Form的State物件的validate方法,就會回撥validator傳入的函式;

image-20190923203843492

也可以為TextFormField新增一個屬性:autovalidate

  • 不需要呼叫validate方法,會自動驗證是否符合要求;

image-20190923204051768

備註:所有內容首發於公眾號,之後除了Flutter也會更新其他技術文章,TypeScript、React、Node、uniapp、mpvue、資料結構與演算法等等,也會更新一些自己的學習心得等,歡迎大家關注

公眾號

相關文章