第三章: 基礎(chǔ)組件 3.5 輸入框及表單

跟隨《Flutter實戰(zhàn)·第二版》學(xué)習(xí)殷费,建議直接看原書

Material 組件庫中提供了輸入框組件TextField和表單組件Form

TextField

TextField用于文本輸入蔼囊,它提供了很多屬性,我們先簡單介紹一下主要屬性的作用嘱腥,然后通過幾個示例來演示一下關(guān)鍵屬性的用法。

const TextField({
  ...
  TextEditingController controller, 
  FocusNode focusNode,
  InputDecoration decoration = const InputDecoration(),
  TextInputType keyboardType,
  TextInputAction textInputAction,
  TextStyle style,
  TextAlign textAlign = TextAlign.start,
  bool autofocus = false,
  bool obscureText = false,
  int maxLines = 1,
  int maxLength,
  this.maxLengthEnforcement,
  ToolbarOptions? toolbarOptions,
  ValueChanged<String> onChanged,
  VoidCallback onEditingComplete,
  ValueChanged<String> onSubmitted,
  List<TextInputFormatter> inputFormatters,
  bool enabled,
  this.cursorWidth = 2.0,
  this.cursorRadius,
  this.cursorColor,
  this.onTap,
  ...
})
  • controller:編輯框的控制器拘悦,通過它可以設(shè)置/獲取編輯框的內(nèi)容齿兔、選擇編輯內(nèi)容、監(jiān)聽編輯文本改變事件础米。大多數(shù)情況下我們都需要顯式提供一個controller來與文本框交互分苇。如果沒有提供controller,則TextField內(nèi)部會自動創(chuàng)建一個
  • focusNode:用于控制TextField是否占有當前鍵盤的輸入焦點屁桑。它是我們和鍵盤交互的一個句柄(handle)
  • InputDecoration:用于控制TextField的外觀顯示,如提示文本蘑斧、背景顏色靖秩、邊框等
  • keyboardType:用于設(shè)置該輸入框默認的鍵盤輸入類型,取值如下:


    image.png
  • textInputAction:鍵盤動作按鈕圖標(即回車鍵位圖標)竖瘾,它是一個枚舉值沟突,有多個可選值,全部的取值列表讀者可以查看API文檔准浴,下面是當值為TextInputAction.search時事扭,原生Android系統(tǒng)下鍵盤樣式所示:


    image.png
  • style:正在編輯的文本樣式。
  • textAlign: 輸入框內(nèi)編輯文本在水平方向的對齊方式乐横。
  • autofocus: 是否自動獲取焦點求橄。
  • obscureText:是否隱藏正在編輯的文本今野,如用于輸入密碼的場景等,文本內(nèi)容會用“?”替換
  • maxLines:輸入框的最大行數(shù)罐农,默認為1条霜;如果為null,則無行數(shù)限制涵亏。
  • maxLength和maxLengthEnforcement :maxLength代表輸入框文本的最大長度宰睡,設(shè)置后輸入框右下角會顯示輸入的文本計數(shù)。maxLengthEnforcement決定當輸入文本長度超過maxLength時如何處理气筋,如截斷队贱、超出等流强。
  • toolbarOptions:長按或鼠標右擊時出現(xiàn)的菜單,包括 copy、cut索守、paste 以及 selectAll捏题。
  • onChange:輸入框內(nèi)容改變時的回調(diào)函數(shù)官觅;注:內(nèi)容改變事件也可以通過controller來監(jiān)聽摇展。
  • onEditingComplete和onSubmitted:這兩個回調(diào)都是在輸入框輸入完成時觸發(fā),比如按了鍵盤的完成鍵(對號圖標)或搜索鍵(??圖標)瓤球。不同的是兩個回調(diào)簽名不同融欧,onSubmitted回調(diào)是ValueChanged<String>類型,它接收當前輸入內(nèi)容做為參數(shù)卦羡,而onEditingComplete不接收參數(shù)噪馏。
  • inputFormatters:用于指定輸入格式;當用戶輸入內(nèi)容改變時虹茶,會根據(jù)指定的格式來校驗逝薪。
  • enable:如果為false,則輸入框會被禁用蝴罪,禁用狀態(tài)不接收輸入和事件董济,同時顯示禁用態(tài)樣式(在其decoration中定義)。
  • cursorWidth要门、cursorRadius和cursorColor:這三個屬性是用于自定義輸入框光標寬度虏肾、圓角和顏色的
示例:登錄輸入框

布局

Column(
      children: [
        TextField(
          autofocus: true,
          decoration: InputDecoration(
//            labelText: "用戶名",
            hintText: "用戶名或郵箱",
            prefixIcon: Icon(Icons.person),
          ),
        ),
        TextField(
          decoration: InputDecoration(
            labelText: "密碼",
            hintText: "您的登錄密碼",
            prefixIcon: Icon(Icons.lock),
          ),
          textInputAction: TextInputAction.search,
          obscureText: true,
        )
      ],
    )
截屏.png
獲取輸入內(nèi)容

獲取輸入內(nèi)容有兩種方式:

  1. 定義兩個變量,用于保存用戶名和密碼欢搜,然后在onChange觸發(fā)時封豪,各自保存一下輸入內(nèi)容
  2. 通過controller直接獲取。

第一種方式比較簡單炒瘟,不在舉例吹埠,我們來重點看一下第二種方式,我們以用戶名輸入框舉例:
定義一個controller:

//定義一個controller
TextEditingController _unameController = TextEditingController();

然后設(shè)置輸入框controller:

TextField(
    autofocus: true,
    controller: _unameController, //設(shè)置controller
    ...
)

通過controller獲取輸入框內(nèi)容

print(_unameController.text)
監(jiān)聽文本變化
  1. 設(shè)置onChange回調(diào),如:
TextField(
    autofocus: true,
    onChanged: (v) {
      print("onChange: $v");
    }
)
  1. 通過controller監(jiān)聽缘琅,如:
@override
void initState() {
  //監(jiān)聽輸入改變  
  _unameController.addListener((){
    print(_unameController.text);
  });
}

兩種方式相比粘都,onChanged是專門用于監(jiān)聽文本變化,而controller的功能卻多一些刷袍,除了能監(jiān)聽文本變化外翩隧,它還可以設(shè)置默認值、選擇文本呻纹,下面我們看一個例子:
創(chuàng)建一個controller:

TextEditingController _selectionController =  TextEditingController();

設(shè)置默認值堆生,并從第三個字符開始選中后面的字符

_selectionController.text="hello world!";
_selectionController.selection=TextSelection(
    baseOffset: 2,
    extentOffset: _selectionController.text.length
);

設(shè)置controller:

TextField(
  controller: _selectionController,
)

效果


截屏.png
控制焦點

焦點可以通過FocusNode和FocusScopeNode來控制,默認情況下雷酪,焦點由FocusScope來管理淑仆,它代表焦點控制范圍,可以在這個范圍內(nèi)可以通過FocusScopeNode在輸入框之間移動焦點哥力、設(shè)置默認焦點等糯景。我們可以通過FocusScope.of(context) 來獲取Widget樹中默認的FocusScopeNode。下面看一個示例省骂,在此示例中創(chuàng)建兩個TextField,第一個自動獲取焦點最住,然后創(chuàng)建兩個按鈕:

  • 點擊第一個按鈕可以將焦點從第一個TextField挪到第二個TextField钞澳。
  • 點擊第二個按鈕可以關(guān)閉鍵盤
Simulator Screen Shot - iPhone 13.png

代碼如下:

Navigator.push(context, MaterialPageRoute(builder: (context){
          return FocusTestRoute();
        }));
class FocusTestRoute extends StatefulWidget {
  @override
  _FocusTestRouteState createState() => _FocusTestRouteState();
}

class _FocusTestRouteState extends State<FocusTestRoute> {
  FocusNode node1 = FocusNode();
  FocusNode node2 = FocusNode();
  FocusScopeNode? scopeNode;

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("hhh"),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            TextField(
              autofocus: true,
              focusNode: node1, // 關(guān)聯(lián)node1
              decoration: InputDecoration(
                  labelText: "input1"
              ),
            ),
            TextField(
              autofocus: true,
              focusNode: node2, // 關(guān)聯(lián)node2
              decoration: InputDecoration(
                  labelText: "input2"
              ),
            ),
            Builder(builder: (ctx) {
              return Column(
                children: [
                  ElevatedButton(
                    child: Text("移動焦點"),
                    onPressed: () {
                      //將焦點從第一個TextField移到第二個TextField
                      // 這是一種寫法 FocusScope.of(context).requestFocus(focusNode2);
                      // 這是第二種寫法
                      if(null == scopeNode){
                        scopeNode = FocusScope.of(context);
                      }
                      scopeNode!.requestFocus(node2);
                    },
                  ),
                  ElevatedButton(
                    child: Text("隱藏鍵盤"),
                    onPressed: () {
                      // 當所有編輯框都失去焦點時鍵盤就會收起
                      node1.unfocus();
                      node2.unfocus();
                    },
                  ),
                ],
              );
            }),
          ],
        ),
      ),
    );
  }
}
監(jiān)聽焦點狀態(tài)改變事件

FocusNode繼承自ChangeNotifier,通過FocusNode可以監(jiān)聽焦點的改變事件涨缚,如:

// 創(chuàng)建focusNode
    FocusNode focusNode = FocusNode();

TextField(focusNode: focusNode), // focusNode綁定輸入框

// 監(jiān)聽焦點變化
    focusNode.addListener(() {
      print(focusNode.hasFocus);
    });

獲得焦點時focusNode.hasFocus值為true轧粟,失去焦點時為false

自定義樣式

雖然我們可以通過decoration屬性來定義輸入框樣式,下面以自定義輸入框下劃線顏色為例來介紹一下:

TextField(
            decoration: InputDecoration(
              labelText: "請輸入用戶名",
              prefixIcon: Icon(Icons.person),
              // 未獲得焦點下劃線設(shè)為灰色
              enabledBorder: UnderlineInputBorder(
                borderSide: BorderSide(color: Colors.grey),
              ),
              // 獲得焦點下劃線設(shè)為藍色
              focusedBorder: UnderlineInputBorder(
                borderSide: BorderSide(color: Colors.blue),
              ),
            ),
          ),
截屏.png
截屏.png

上面代碼我們直接通過InputDecoration的enabledBorder和focusedBorder來分別設(shè)置了輸入框在未獲取焦點和獲得焦點后的下劃線顏色脓魏。
另外兰吟,我們也可以通過主題來自定義輸入框的樣式,下面我們探索一下如何在不使用enabledBorder和focusedBorder的情況下來自定義下滑線顏色

由于TextField在繪制下劃線時使用的顏色是主題色里面的hintColor茂翔,但提示文本顏色也是用的hintColor混蔼, 如果我們直接修改hintColor,那么下劃線和提示文本的顏色都會變珊燎。值得高興的是decoration中可以設(shè)置hintStyle惭嚣,它可以覆蓋hintColor,并且主題中可以通過inputDecorationTheme來設(shè)置輸入框默認的decoration悔政。所以我們可以通過主題來自定義晚吞,代碼如下:

Theme(
  data: Theme.of(context).copyWith(
      hintColor: Colors.grey[200], //定義下劃線顏色
      inputDecorationTheme: InputDecorationTheme(
          labelStyle: TextStyle(color: Colors.grey),//定義label字體樣式
          hintStyle: TextStyle(color: Colors.grey, fontSize: 14.0)//定義提示文本樣式
      )
  ),
  child: Column(
    children: <Widget>[
      TextField(
        decoration: InputDecoration(
            labelText: "用戶名",
            hintText: "用戶名或郵箱",
            prefixIcon: Icon(Icons.person)
        ),
      ),
      TextField(
        decoration: InputDecoration(
            prefixIcon: Icon(Icons.lock),
            labelText: "密碼",
            hintText: "您的登錄密碼",
            hintStyle: TextStyle(color: Colors.grey, fontSize: 13.0)
        ),
        obscureText: true,
      )
    ],
  )
)

效果:


截屏.png

我們成功的自定義了下劃線顏色和提問文字樣式,細心的讀者可能已經(jīng)發(fā)現(xiàn)谋国,通過這種方式自定義后槽地,輸入框在獲取焦點時,labelText不會高亮顯示了,正如上圖中的"用戶名"本應(yīng)該顯示藍色捌蚊,但現(xiàn)在卻顯示為灰色集畅,并且我們還是無法定義下劃線寬度。
另一種靈活的方式是直接隱藏掉TextField本身的下劃線逢勾,然后通過Container去嵌套定義樣式牡整,如:

Container(
            child: TextField(
              decoration: InputDecoration(
                labelText: "Email",
                hintText: "電子郵件地址",
                prefixIcon: Icon(Icons.email),
                border: InputBorder.none,
              ),
            ),
            decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey, width: 1.0)),
            ),
          ),
截屏.png

通過這種組件組合的方式,也可以定義背景圓角等溺拱。一般來說逃贝,優(yōu)先通過decoration來自定義樣式,如果decoration實現(xiàn)不了迫摔,再用widget組合的方式沐扳。


表單Form

實際業(yè)務(wù)中,在正式向服務(wù)器提交數(shù)據(jù)前句占,都會對各個輸入框數(shù)據(jù)進行合法性校驗沪摄,但是對每一個TextField都分別進行校驗將會是一件很麻煩的事。還有纱烘,如果用戶想清除一組TextField的內(nèi)容杨拐,除了一個一個清除有沒有什么更好的辦法呢?為此擂啥,F(xiàn)lutter提供了一個Form 組件哄陶,它可以對輸入框進行分組,然后進行一些統(tǒng)一操作哺壶,如輸入內(nèi)容校驗屋吨、輸入框重置以及輸入內(nèi)容保存。

Form

Form繼承自StatefulWidget對象山宾,它對應(yīng)的狀態(tài)類為FormState至扰。我們先看看Form類的定義:

Form({
  required Widget child,
  bool autovalidate = false,
  WillPopCallback onWillPop,
  VoidCallback onChanged,
})
  • autovalidate:是否自動校驗輸入內(nèi)容;當為true時资锰,每一個子 FormField 內(nèi)容發(fā)生變化時都會自動校驗合法性敢课,并直接顯示錯誤信息。否則绷杜,需要通過調(diào)用FormState.validate()來手動校驗翎猛。
  • onWillPop:決定Form所在的路由是否可以直接返回(如點擊返回按鈕),該回調(diào)返回一個Future對象接剩,如果 Future 的最終結(jié)果是false切厘,則當前路由不會返回;如果為true懊缺,則會返回到上一個路由疫稿。此屬性通常用于攔截返回按鈕培他。
  • onChanged:Form的任意一個子FormField內(nèi)容發(fā)生變化時會觸發(fā)此回調(diào)。
FormField

Form的子孫元素必須是FormField類型遗座,F(xiàn)ormField是一個抽象類舀凛,定義幾個屬性,F(xiàn)ormState內(nèi)部通過它們來完成操作途蒋,F(xiàn)ormField部分定義如下:

const FormField({
  ...
  FormFieldSetter<T> onSaved, //保存回調(diào)
  FormFieldValidator<T>  validator, //驗證回調(diào)
  T initialValue, //初始值
  bool autovalidate = false, //是否自動校驗猛遍。
})

為了方便使用,F(xiàn)lutter 提供了一個TextFormField組件号坡,它繼承自FormField類懊烤,也是TextField的一個包裝類,所以除了FormField定義的屬性之外宽堆,它還包括TextField的屬性腌紧。

FormState

FormState為Form的State類,可以通過Form.of()或GlobalKey獲得畜隶。我們可以通過它來對Form的子孫FormField進行統(tǒng)一操作壁肋。
我們看看其常用的三個方法:

  • FormState.validate():調(diào)用此方法后,會調(diào)用Form子孫FormField的validate回調(diào)籽慢,如果有一個校驗失敗,則返回false箱亿,所有校驗失敗項都會返回用戶返回的錯誤提示乙帮。
  • FormState.save():調(diào)用此方法后,會調(diào)用Form子孫FormField的save回調(diào)极景,用于保存表單內(nèi)容
  • FormState.reset():調(diào)用此方法后,會將子孫FormField的內(nèi)容清空
示例

我們修改一下上面用戶登錄的示例驾茴,在提交之前校驗:

  1. 用戶名不能為空盼樟,如果為空則提示“用戶名不能為空”。
  2. 密碼不能小于 6 位锈至,如果小于 6 為則提示“密碼不能少于 6 位”晨缴。
class FormTestRoute extends StatefulWidget {
  @override
  _FormTestRouteState createState() => _FormTestRouteState();
}

class _FormTestRouteState extends State<FormTestRoute> {
  TextEditingController _unameController = TextEditingController();
  TextEditingController _pwdController = TextEditingController();
  GlobalKey _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text("Form Test"),
      ),
      body: Form(
        key: _formKey, // 設(shè)置globalkey 用于后面獲取FormState
        autovalidateMode: AutovalidateMode.onUserInteraction,
        child: Column(
          children: <Widget>[
            TextFormField(
              autofocus: true,
              controller: _unameController,
              decoration: InputDecoration(
                labelText: "用戶名",
                hintText: "用戶名或郵箱",
                icon: Icon(Icons.person),
              ),
              // 校驗用戶名
              validator: (v) {
                return v!.trim().length > 0 ? null : "用戶名不能為空";
              },
            ),
            TextFormField(
              autofocus: true,
              controller: _pwdController,
              decoration: InputDecoration(
                labelText: "密碼",
                hintText: "您的登錄密碼",
                icon: Icon(Icons.lock),
              ),
              obscureText: true,
              // 校驗密碼
              validator: (v) {
                return v!.trim().length > 5 ? null : "密碼不能少于6位";
              },
            ),
            // 登錄按鈕
            Padding(
              padding: const EdgeInsets.only(top: 28),
              child: Row(
                children: <Widget>[
                  Expanded(
                    child: ElevatedButton(
                      child: Padding(
                        padding: EdgeInsets.all(16),
                        child: Text("登錄"),
                      ),
                      // 通過_formKey.currentState 獲取FormState后,
                      // 調(diào)用validate()方法校驗用戶名密碼是否合法峡捡,校驗
                      // 通過后再提交數(shù)據(jù)击碗。
                      onPressed: (){
                        if ((_formKey.currentState as FormState).validate()) {
                          print("驗證通過提交數(shù)據(jù)");
                        }
                      },
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Simulator Screen Shot - iPhone 13 - 2022-01-05 at 23.28.47.png

注意,登錄按鈕的onPressed方法中不能通過Form.of(context)來獲取们拙,原因是稍途,此處的context為FormTestRoute的context,而Form.of(context)是根據(jù)所指定context向根去查找砚婆,而FormState是在FormTestRoute的子樹中械拍,所以不行。正確的做法是通過Builder來構(gòu)建登錄按鈕,Builder會將widget節(jié)點的context作為回調(diào)參數(shù):

Expanded(
 // 通過Builder來獲取ElevatedButton所在widget樹的真正context(Element) 
  child:Builder(builder: (context){
    return ElevatedButton(
      ...
      onPressed: () {
        //由于本widget也是Form的子代widget坷虑,所以可以通過下面方式獲取FormState  
        if(Form.of(context).validate()){
          //驗證通過提交數(shù)據(jù)
        }
      },
    );
  })
)

其實context正是操作Widget所對應(yīng)的Element的一個接口甲馋,由于Widget樹對應(yīng)的Element都是不同的,所以context也都是不同的

Flutter中有很多“of(context)”這種方法迄损,讀者在使用時一定要注意context是否正確定躏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芹敌,隨后出現(xiàn)的幾起案子痊远,更是在濱河造成了極大的恐慌,老刑警劉巖党窜,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拗引,死亡現(xiàn)場離奇詭異,居然都是意外死亡幌衣,警方通過查閱死者的電腦和手機矾削,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豁护,“玉大人哼凯,你說我怎么就攤上這事〕铮” “怎么了断部?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長班缎。 經(jīng)常有香客問我蝴光,道長,這世上最難降的妖魔是什么达址? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任蔑祟,我火速辦了婚禮,結(jié)果婚禮上沉唠,老公的妹妹穿的比我還像新娘疆虚。我一直安慰自己,他們只是感情好满葛,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布径簿。 她就那樣靜靜地躺著,像睡著了一般嘀韧。 火紅的嫁衣襯著肌膚如雪篇亭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天锄贷,我揣著相機與錄音暗赶,去河邊找鬼鄙币。 笑死,一個胖子當著我的面吹牛蹂随,可吹牛的內(nèi)容都是我干的十嘿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼岳锁,長吁一口氣:“原來是場噩夢啊……” “哼绩衷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起激率,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤咳燕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乒躺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體招盲,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年嘉冒,在試婚紗的時候發(fā)現(xiàn)自己被綠了曹货。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡讳推,死狀恐怖顶籽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情银觅,我是刑警寧澤礼饱,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站究驴,受9級特大地震影響镊绪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洒忧,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一蝴韭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧跑慕,春花似錦、人聲如沸摧找。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹬耘。三九已至芝雪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間综苔,已是汗流浹背惩系。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工位岔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堡牡。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓抒抬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晤柄。 傳聞我的和親對象是個殘疾皇子擦剑,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容