Flutter小白教程系列

Flutter常用Widget介绍

2020-01-16  本文已影响0人  程序员指北

转载请注明出处: https://learnandfish.com/

Text文本组件 --最基础的组件

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(
            // 文本控件
            "常用Widget介绍", // 文本内容
            style: TextStyle(
                // 文本样式
                color: Colors.white, // 文字颜色
                fontSize: 20 // 文字大小
                ),
          ),
        ),
        body: Text(
          "Widget界面" * 5, // 字符串重复5次 
          textAlign: TextAlign.center, // 文字居中显示
        ),
      ),
    );
  }
}
var richText = Text.rich( // 富文本
                         TextSpan( // 通过TextSpan组合文本内容
                           children: [ // 有多个TextSpan, 每个TextSpan定义一部分内容
                             TextSpan(text: "个人博客"),
                             TextSpan(
                               text: "https://learnandfish.com",
                               style: TextStyle(color: Colors.lightBlueAccent),
                             ),
                           ],
                         ),
                       );

Button 按钮

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      // 默认竖直方向排列的容器widget, 类似于Android的LinearLayout。
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        // RaisedButton 默认带有阴影和灰色背景。按下后阴影会变大。
        RaisedButton(
          child: Text("不可点击状态"), // button中的文字显示
          onPressed: null, // 点击事件,如果赋值为null, 则控件状态为不可用,显示灰色不可点击
        ),
        RaisedButton(
          child: Text("正常状态"), // button中的文字显示
          onPressed: () {
            // 具体逻辑
          }, // 点击事件,正常状态
        ),
        RaisedButton(
          child: Text("打印我是可点击的RaisedButton"), // button中的文字显示
          onPressed: () {
            // 具体逻辑
            print("我是可点击的RaisedButton");
          }, // 点击事件,正常状态
        ),
        RaisedButton(
          child: Text("打印我是可点击的RaisedButton"), // button中的文字显示
          onPressed: () => print("我是可点击的RaisedButton"), // 点击事件,正常状态
        ),

        /// FlatButton 扁平化的按钮, 默认背景透明且不带阴影, 按下出现背景但没有阴影。
        /// 点击事件和结构和RaisedButton一致, 只是样式有些不同而已。
        FlatButton(
          child: Text("FlatButton"),
          onPressed: () => print("我是可点击的FlatButton"),
        ),

        /// OutlineButton 默认有一个边框, 不带阴影且背景透明。按下后边框颜色会变亮、同时出现背景和阴影。
        /// 点击事件和结构和RaisedButton一致, 只是样式有些不同而已。
        OutlineButton(
          borderSide: BorderSide(
              // 默认情况下的边框样式
              color: Colors.amber),
          highlightedBorderColor: Colors.red, // 按下时边框的颜色
          child: Text("OutlineButton"),
          onPressed: () => print("我是可点击的OutlineButton"),
        ),

        /// IconButton是一个可点击的Icon,不包括文字,默认没有背景,点击后会出现背景。
        IconButton(
          icon: Icon(Icons.add),
          onPressed: () => print("Add"),
        ),

        /// 带图标的按钮, RaisedButton、FlatButton、OutlineButton都有一个icon 构造函数
        /// 通过该方法可以轻松的创建带图标的按钮。
        RaisedButton.icon(
          onPressed: () => print("带图标的RaiseButton"),
          icon: Icon(Icons.adb),
          label: Text("Adb"),
        ),
        FlatButton.icon(
          onPressed: () => print("带图标的FlatButton"),
          icon: Icon(Icons.access_alarms),
          label: Text("Alarm"),
        ),
        OutlineButton.icon(
          onPressed: () => print("带图标的OutlineButton"),
          icon: Icon(Icons.print),
          label: Text("Printer"),
        ),

        /// 圆角按钮, 圆角是通过shape属性定义的, RaisedButton、FlatButton、OutlineButton都有该属性。
        /// 我们以RaiseButton为例定义。
        RaisedButton(
          color: Colors.white,
          shape: RoundedRectangleBorder( //
            borderRadius: BorderRadius.circular(5)
          ),
          child: Text("圆角按钮"),
          onPressed: () => print("圆角按钮"),
        ),
        
        /// ButtonBar 定义一组控件, 如果一行的宽度足够, 则按照子控件都在一行排列
        /// 如果子控件太多一行控件不够排列, 则按照竖直方向排列。
      ],
    );
  }
}

FloatingActionButton 悬浮按钮

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(
            // 文本控件
            "常用Widget介绍", // 文本内容
            style: TextStyle(
                // 文本样式
                color: Colors.white, // 文字颜色
                fontSize: 20 // 文字大小
                ),
          ),
        ),
        body: HomePage(),
        /// FloatingActionButtonLocation.centerDocked 配合BottomNavigationBar
        /// 能够实现闲鱼App底部效果。悬浮按钮位于底部导航栏的中间并覆盖底部导航栏。
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, // 悬浮按钮的位置
        floatingActionButton: FloatingActionButton( // 悬浮按钮, 必须放在Scaffold控件下方
          child: Icon(Icons.arrow_upward),
          onPressed: () => print("点击返回顶部"),
        ),
      ),
    );
  }
}

图片控件

/// 在工程目录添加images文件夹, 并将图片拷贝进去。
/// 此时, 如果需要使用images中的文件, 需要在项目的pubspec.yaml文件中声明该文件才能正常使用。
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Image( // 获取Assets中的图片
          image: AssetImage("images/ic_flutter.png"),
          width: 50,
          height: 50,
        ),
        Image( // 获取网络图片
          image: NetworkImage("https://i.loli.net/2019/12/25/lXYknLjtTKOpaiJ.png"),
          width: 250,
          height: 250,
        ),
      ],
    );
  }
}
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Image.asset(
          "images/ic_flutter.png",
          width: 50,
          height: 50,
        ),
        Image.network(
          "https://i.loli.net/2019/12/25/lXYknLjtTKOpaiJ.png",
          width: 250,
          height: 250,
        )
      ],
    );
  }
}

单选开关Switch和复选框Checkbox

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _switchState = true; // 默认关闭状态
  bool _checkState = true; // 默认开启状态

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Switch(
          value: _switchState,
          // Dart单行函数
          onChanged: (value) => setState(() => _switchState = value),
        ),
        Checkbox(
          value: _checkState,
          onChanged: (value) {
            setState(() { // 通过该函数重新赋值并刷新UI
              _checkState = value;
            });
          },
        ),
      ],
    );
  }
}

输入框TextField和表单Form

class _HomePageState extends State<HomePage> {

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          autofocus: true, // 自动获取焦点
          decoration: InputDecoration( // 输入框样式设置
            labelText: "用户名", // 无焦点时默认显示内容
            hintText: "请输入用户名", // 提示文本
            prefixIcon: Icon(Icons.person), // 左侧图标
          ),
          onChanged: (value) { // 内容改变时会触发回调, 每次输入或者删除都会回调。
            print("onChanged----$value");
          },
        ),
        TextField(
          decoration: InputDecoration(
            labelText: "密码",
            hintText: "请输入密码",
            prefixIcon: Icon(Icons.lock),
          ),
          obscureText: true, // 密码隐式显示
        ),
        TextField(
          decoration: InputDecoration(
            labelText: "手机号",
            hintText: "请输入手机号",
            prefixIcon: Icon(Icons.phone),
          ),
          keyboardType: TextInputType.phone, // 键盘输入类型, 此时为数字加*和#
        ),
      ],
    );
  }
}
class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    // 该方法只会调用一次, 适合做一些初始化操作。
    _controller.text = "初始值";
    _controller.addListener((){ // 监听状态变化
        print(_controller.text);
    });
  }

  @override
  void dispose() {
    super.dispose();
    // 该方法销毁组件时调用, 进行一些对象的回收工作, 减少内存消耗。
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          controller: _controller,
          autofocus: true, // 自动获取焦点
        ),
        RaisedButton(
          child: Text("输入框改变内容"),
          onPressed: (){
            _controller.text = "输入框改变内容";
          },
        )
      ],
    );
  }
}
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FormTestRoute(),
    );
  }
}

class FormTestRoute extends StatefulWidget {
  @override
  _FormTestRouteState createState() => new _FormTestRouteState();
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:Text("Form Test"),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
        child: Form(
          key: _formKey, //设置globalKey,用于后面获取FormState
          autovalidate: true, //开启自动校验
          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(
                  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.0),
                child: Row(
                  children: <Widget>[
                    Expanded(
                      child: RaisedButton(
                        padding: EdgeInsets.all(15.0),
                        child: Text("登录"),
                        color: Theme
                            .of(context)
                            .primaryColor,
                        textColor: Colors.white,
                        onPressed: () {
                          // 通过_formKey.currentState 获取FormState后,
                          // 调用validate()方法校验用户名密码是否合法,校验
                          // 通过后再提交数据。
                          if((_formKey.currentState as FormState).validate()){
                            //验证通过提交数据
                            print("登录成功");
                          }
                        },
                      ),
                    ),
                  ],
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

进度指示器

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        SizedBox(
          height: 20,
        ),
        LinearProgressIndicator(
          backgroundColor: Colors.blue[200], // 背景颜色,200为深度, 取值50-900之间。
          valueColor: AlwaysStoppedAnimation(Colors.blue), // 动画
        ),
        SizedBox(
          height: 20,
        ),
        CircularProgressIndicator(
          backgroundColor: Colors.blue[200], // 背景颜色,200为深度, 取值50-900之间。
          valueColor: AlwaysStoppedAnimation(Colors.blue),
        ),
      ],
    );
  }
}

常用布局类容器

/// Wrap实例
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Wrap(
      children: <Widget>[
        Container(
          width: 100,
          height: 100,
          color: Colors.blue,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.white,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.amber,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.red,
        ),
        Container(
          width: 100,
          height: 100,
          color: Colors.lightBlueAccent,
        ),
      ],
    );
  }
}
/// Stack和Positioned实例后续再给实例, 等学习了ConstrainedBox再给实例。

常用容器类控件

/// ConstrainedBox + Stack + Positioned
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ConstrainedBox(
      constraints: BoxConstraints.expand(), // 约定充满全屏
      child: Stack( 
        alignment: Alignment.center, //指定未定位或部分定位widget的对齐方式
        children: <Widget>[
          Container(
            child: Text("Hello world", style: TextStyle(color: Colors.white)),
            color: Colors.red,
          ),
          Positioned(
            left: 18.0,
            child: Text("I am Jack"),
          ),
          Positioned(
            top: 18.0,
            child: Text("Your friend"),
          )
        ],
      ),
    );
  }
}

/// 定义状态栏右侧进度条的宽高, 期望情况下是20*20, 但实际效果是一个椭圆进度条, 思考一下为什么?
/// 这就是我们说的被父类控件的ConstrainedBox约束了大小, 以后如果我们明确定义了控件的大小, 但是
/// 实际效果却不是我们想要的, 很大一部分情况就是父类控件约束了大小, 我们需要通过UnconstrainedBox
/// 控件进行去除父类控件的约束, 展示自定义的大小。
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(
            // 文本控件
            "常用Widget介绍", // 文本内容
          ),
          actions: <Widget>[
            SizedBox( // 定义状态栏右侧进度条的宽高, 期望情况下是20*20
              width: 20,
              height: 20,
              child: CircularProgressIndicator(
                valueColor: AlwaysStoppedAnimation(Colors.white),
              ),
            )
          ],
        ),
        body: HomePage(),
      ),
    );
  }
}
/// 修改之后如下, 在SizeBox外层添加了UnconstrainedBox
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text(
            // 文本控件
            "常用Widget介绍", // 文本内容
          ),
          actions: <Widget>[
            UnconstrainedBox(
              child: SizedBox( // 定义状态栏右侧进度条的宽高, 期望情况下是20*20
                width: 20,
                height: 20,
                child: CircularProgressIndicator(
                  valueColor: AlwaysStoppedAnimation(Colors.white),
                ),
              ),
            )
          ],
        ),
        body: HomePage(),
      ),
    );
  }
}

可滚动组件

/// SingleChildScrollView
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return Scrollbar( // 显示进度条
      child: SingleChildScrollView(
        padding: EdgeInsets.all(16.0), // 缩进
        child: Center(
          child: Column( //动态创建一个List<Widget>
            children: str
                .split("") //每一个字母都用一个Text显示,字体为原来的两倍
                .map((c) => Text(
                      c,
                      textScaleFactor: 3.0, // 缩放大小
                    ))
                .toList(),
          ),
        ),
      ),
    );
  }
}
/// ListView 通常使用ListView.builder和ListView.separated构造, 后者可以很方便的添加分隔线。
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scrollbar( // 显示进度条
      child: ListView.builder(
          itemCount: 20,
          itemExtent: 50.0, //强制高度为50.0
          itemBuilder: (BuildContext context, int index) {
            return ListTile(title: Text("$index"));
          }
      ),
    );
  }
}
/// GridView与ListView类似, 不思考难进步, 所以GridView作为思考题大家自行学习。
/// CustomScrollView
class CustomScrollViewTestRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //因为本路由没有使用Scaffold,为了让子级Widget(如Text)使用
    //Material Design 默认的样式风格,我们使用Material作为本路由的根。
    return Material(
      child: CustomScrollView(
        slivers: <Widget>[
          //AppBar,包含一个导航栏
          SliverAppBar( // 类似于Android的可收缩toolbar
            pinned: false, // 收起时不显示导航栏
            expandedHeight: 250.0,
            flexibleSpace: FlexibleSpaceBar(
              background: Image.network(
                "https://i.loli.net/2019/12/25/lXYknLjtTKOpaiJ.png",
                fit: BoxFit.cover,
              ),
            ),
          ),

          SliverPadding(
            padding: const EdgeInsets.all(8.0),
            sliver: new SliverGrid(
              //Grid
              gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2, //Grid按两列显示
                mainAxisSpacing: 10.0,
                crossAxisSpacing: 10.0,
                childAspectRatio: 4.0,
              ),
              delegate: new SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  //创建子widget
                  return new Container(
                    alignment: Alignment.center,
                    color: Colors.cyan[100 * (index % 9)],
                    child: new Text('grid item $index'),
                  );
                },
                childCount: 20,
              ),
            ),
          ),
          //List
          new SliverFixedExtentList(
            itemExtent: 50.0,
            delegate: new SliverChildBuilderDelegate(
                (BuildContext context, int index) {
              //创建列表项
              return new Container(
                alignment: Alignment.center,
                color: Colors.lightBlue[100 * (index % 9)],
                child: new Text('list item $index'),
              );
            }, childCount: 50 //50个列表项
                ),
          ),
        ],
      ),
    );
  }
}
/// ScrollController作为思考题大家自行学习。

结尾

还有一些功能性的控件放到后续讲完页面路由之后再进行讲解会比较好理解。

愿景

欢乐的时间总是那么短暂, 如果你觉得本篇博客对你有用,可以点个赞,评个论,加个关注或者打个赏给博主增加动力哦。

上一篇 下一篇

猜你喜欢

热点阅读