跳至主要內容

小部件

Emilia Zhen大约 11 分钟flutter

StatefulWidget 和 StatelessWidget

  • StatefulWidget具有可变状态的窗口部件,也就是你在使用应用的时候就可以随时变化,比如我们常见的进度条
  • StatelessWidget不可变状态窗口部件,也就是你在使用时不可以改变,比如固定的文字

init 先初始父类 再初始子类
dispose 先销毁子类 再销毁父类

主题自定义配置

theme: ThemeData(
   primaryIconTheme: IconThemeData(
     color: Colors.white
   ),
   brightness: Brightness.light,
   primaryColor: Color.fromARGB(255, 30, 46, 69),
   accentColor: Colors.blue
),
  • primaryColor主要部分的背景颜色(工具栏,标签栏等)
  • primaryIconTheme与主要颜色形成颜色鲜明对比的图标主题
  • accentColor小部件的前景色(旋钮、文本、过渡滚动边缘效果等)

去掉 DeBug 图标

在 return 的 MaterialApp()里加入 debugShowCheckedModeBanner: false

body: Center(
  child:Container(
    child: Text(
      'Hello World!AAAAAA',
      textAlign: TextAlign.center,
      maxLines: 1,
      overflow:TextOverflow.ellipsis,
      style: TextStyle(
         fontSize: 25.0,
         color: Color.fromARGB(255, 255, 150, 150),
         decoration: TextDecoration.underline,
         decorationStyle: TextDecorationStyle.solid,
       )
   ),
   alignment: Alignment.center,
   width: 500.0,
   height: 400.0,
   // color: Colors.lightBlue,
   padding: const EdgeInsets.fromLTRB(50.0,10.0,50.0,10.0),
   margin: const EdgeInsets.all(10.0),
   decoration: BoxDecoration(
     gradient: const LinearGradient(
       colors:[Colors.lightBlue,Colors.greenAccent,Colors.purple]
     ),
     border: Border.all(width:2.0,color: Colors.red)
   ),
  )
),

Text Widget 文字控件

  • TextAligin属性,文本对齐方式,center/left/right/start/end
  • maxLines属性,设置最多显示的行数
  • overflow属性,设置文本溢出时的处理,clip直接切断,elipsis在后面显示省略号,fade溢出部分进行一个渐变消失的效果
  • style属性
Text.rich(
  TextSpan(
    text: 'AAAA',
    children: [
       TextSpan(text:'1'),
       TextSpan(text:'2'),
    ]
  )
)

Container 容器控件

  • alignment属性,对容器内child的对齐方式,bottomCenter/bottomLeft/bottomRight/center/centerLeft/centerRight/topLeft/topCenter/topRight
  • width属性,宽
  • height属性,高
  • color属性,容器颜色属性
  • padding属性,内边距
const EdgeInsets.all(10.0)
const EdgeInsets.fromLTRB(10.0,10.0,0,0)
const EdgeInsets.only(bottom:10.0)
  • margin属性,外边距
  • deciration属性,容器的修饰器,主要功能是设置背景和边框

Image 图片控件

Image.asset(
  'images/car_red.jpg',
  width: 600.0,
  height: 240.0,
  fit: BoxFit.cover,
  color: Colors.greenAccent,
  colorBlendMode: BlendMode.darken,
  repeat: ImageRepeat.repeat,
),
  • Image.asset加载资源图片,加载项目资源目录中的图片,加入图片后会增大打包的包体体积,用的相对路径
  • Image.file加载本地图片,加载本地文件中的图片,绝对路径,跟包体无关
  • Image.network网络资源图片,需要加入一段http://xxx的网络路径地址
  • Image.memory加载Uint8List资源图片 如果想配置项目资源文件,就需要使用pubspec.yaml文件,需要把资源文件在这里声明

fit属性

可以控制图片的拉伸和挤压,这些都是根据图片的父级容器来的

  • BoxFit.fill全图显示,图片会被拉伸,并充满父容器
  • BoxFit.contain全图显示,显示原比例,可能会有空隙
  • BoxFit.fitWidth宽度充满(横向充满),显示可能拉伸,可能裁切
  • BoxFit.fitHeight高度充满(竖向充满),显示可能拉伸,可能裁切
  • BoxFit.scaleDown效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大

colorBlendMode

图片混合模式,和color属性配合使用,能让图片改变颜色

repeat图片重复

  • imageRepeat.repeat横向和纵向都进行重复
  • imageRepeat.repeatX横向重复
  • imageRepeat.repeatY纵向重复

ListView列表控件

class MyList extends StatelessWidget{
  @override
  Widget build(BuildContext context){
  return new ListView(
    children: <Widget>[
    Image.network('https://xxx.png'),
    new ListTile(
      leading: new Icon(Icons.accessible_forward),
      title: new Text('accessible_forward')
    ),
   new ListTile(
     leading: new Icon(Icons.adb),
     title: new Text('adb')
    ),
   ],
  );
 }
}

使用ListView,在他内部的children中,使用了widget数组,因为是一个列表,所以他接受一个数组,使用ListTile控件,在控件中放置图标和文字

  • scrollDirection:Axis.vertical滚动方向
  • reverse是否反向显示数据
  • addAutomaticKeepAlives自动保存视图缓存
  • addRepaintBoundaries添加重绘边界
  • listView.builder()

横向列表

Center(
child: Container(
height: 200.0,
child: new ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
new Container(
width: 180.0,
color: Colors.redAccent,
),
new Container(
width: 180.0,
color: Colors.orangeAccent,
),
new Container(
width: 180.0,
color: Colors.yellow,
),
new Container(
width: 180.0,
color: Colors.greenAccent,
),
],
)
),
),

ListView组件的scrollDirection属性

  • Axis.horizontal水平方向滚动
  • Axis.vertical垂直方向滚动

动态列表

ListDart的集合类型之一

var myList = List() // 非固定长度的声明
var myList = List(2) // 固定长度的声明
var myList = List<String>() // 固定类型的声明
var myList = [1,2,3] // 对List直接赋值

List中的generate方法进行生产List里的元素

import 'package:flutter/material.dart';
void main () => runApp(MyApp(
   items: new List<String>.generate(1000, (i)=> "Item $i")
));
class MyApp extends StatelessWidget{
  final List<String> items;
  MyApp({Key key, @required this.items}):super(key:key);
  @override
  Widget build(BuildContext context ){
    return MaterialApp(
       title:'ListView widget',
       home:Scaffold(
         body:new ListView.builder(
           itemCount:items.length,
           itemBuilder:(context,index){
              return new ListTile(
                 title:new Text('${items[index]}'),
              );
           }
         )
      ),
   );
  }
}

main函数的runApp中调用了myApp类,再使用类的使用传递了一个items参数,并使用generate生成器对items复制,generate方法传递两个参数,第一个参数是生成的个数,第二个是方法 已经传递了参数,那myApp这个类是需要接收的,在构造函数里除了Key,我们增加了一个必传参数,这里的@required意思就是必传,:super如果父类没有无名无参数的默认构造函数,则子类必须手动调用一个父类构造函数
ListView.builder() 调用动态列表进行生成

GridView 网格列表控件

body: GridView.count(
   physics: NeverScrollableScrollPhysics(),
  padding: const EdgeInsets.all(20.0),
  crossAxisSpacing: 10.0,
  crossAxisCount: 3,
  childAspectRatio: 0.7,
  mainAxisSpacing: 2.0,
  children: <Widget>[
    new Image.network('https://xxx',fit: BoxFit.cover),
    new Image.network('https://xxx',fit: BoxFit.cover),
  ],
),
  • padding 表示内边距
  • crossAxisSpacing 网格间的间距
  • crossAxisCount 网格的列数
  • childAspectRatio 宽高比
  • mainAxisSpacing 主轴间距

水平布局 Row

Row 空间可以分为灵活排列和非灵活排列两种

body: new Row(
  children: <Widget>[
   new RaisedButton(
    onPressed: (){},
    color: Colors.orangeAccent,
    child: new Text('button')
  ),
  Expanded(
    child: new RaisedButton(
       onPressed: (){},
       color: Colors.redAccent,
       child: new Text('buy')
     ),
  ),
  new RaisedButton(
     onPressed: (){},
     color: Colors.orangeAccent,
     child: new Text('button')
  )
 ],
),

当都使用非灵活布局时,Row子元素不足会有空隙,子元素超出会有警告。如果想实现充满一行的效果就得使用到灵活水平布局,给包上Expanded就可以了

垂直布局 Column

body: Center(
  child:Column(
      crossAxisAlignment: CrossAxisAlignment.end,
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
         new Text('aaa'),
         Expanded(child: new Text('bbbbbb')),
         new Text('aaa'),
     ],
  ),
)
  • main轴,用column垂直则是主轴,用row水平则是主轴
  • cross轴,副轴

层叠布局 Stack

new Stack(
  alignment: const FractionalOffset(0.5, 0.8),
  children: <Widget>[
    new CircleAvatar(
      backgroundImage: new   NetworkImage('https://xx'),
      radius: 100.0,
    ),
    new Container(
      decoration: new BoxDecoration(
         color: Colors.blueAccent
       ),
      padding: const EdgeInsets.all(5.0),
      child: new Text('电影TOP100',),
    )
  ],
);
  • alignment属性是控制层叠位置的,建议在两个内容进行层叠时使用,它有两个值X轴距离和Y轴距离,值是从01的,都是从上层容器的左上角开始算起
  • circleAvatar组件经常作头像的,组件里radius的值可以设置图片的弧度
body: Center(
  child: new Stack(
     children: <Widget>[
        new CircleAvatar(
           backgroundImage: NetworkImage('https://xxx'),
           radius: 100.0,
        ),
        new Positioned(
           top:10.0,
           left: 10.0,
           child: new Text('AAA'),
        ),
        new Positioned(
           bottom: 10.0,
           right: 10.0,
           child: new Text('BBB'),
        )
     ],
  ),
)

Positioned 组件

  • bottom/left/top/right距离层叠组件的距离
  • width层叠定位组件的宽度
  • height层叠定位组件的高度

Card 卡片组件

这种布局类似ViewList但是列表会以物理卡片的形态进行展示

new Card(
  child: Column(
  children: <Widget>[
    ListTile(
       title: new Text(
         'aaaaa',
          style: TextStyle(
             fontWeight: FontWeight.w400
          )
       ),
       subtitle: new Text('bbbbb'),
       leading: new Icon(Icons.access_alarm,color: Colors.blueAccent,),
    ),
    new Divider(), // 水平线
    ...
   ],
  )
);

流式布局

import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
  _MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  List<Widget> imgList;
  @override
  void initState() {
    imgList = List<Widget>()..add(buildAddButton());
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    final height = MediaQuery.of(context).size.height;
    return Scaffold(
       appBar: AppBar(
          title: Text('Wrap')
        ),
        body: Center(
           child:Opacity(
              opacity: 0.8,
              child: Container(
                 width: width,
                 height: height /2,
                 color: Colors.grey,
                 child: Wrap(
                   children: imgList,
                   spacing: 26.0,
                 ),
               ),
            )
        ),
    );
  }
  Widget buildAddButton(){
     return GestureDetector(
         onTap: (){
             if(imgList.length < 9){
                 setState(() {
                    imgList.insert(imgList.length-1, buildPhoto());
                 });
             }
          },
         child: Padding(
             padding: const EdgeInsets.all(8.0),
             child:Container(
                 width: 80.0,
                 height: 80.0,
                 color: Colors.black54,
                 child: Icon(Icons.add),
             )
         ),
     );
  }
   Widget buildPhoto(){
       return Padding(
           padding: const EdgeInsets.all(8.0),
           child: Container(
               width: 80.0,
               height: 80.0,
               color: Colors.amber,
               child: Center(
                   child: Text('Photo')
                ),
            ),
       );
    }
}

GestureDetector

手势操作,没有任何的显示功能,用来触发事件的。如PaddingContainerCenter这些组件是没有触发事件的,可在在它们外层增加一个GestureDetector,让它们有触发事件

ExpansionTile 控件

可以展开闭合的控件

  • title 闭合时显示的标题
  • leading 标题左侧图标
  • backgroundColor 展开时背景颜色
  • children 子元素
  • initiallyExpanded 初始状态是否为展开
ExpansionTile(
   title: Text('AAA Aa'),
   leading: Icon(Icons.ac_unit),
   backgroundColor: Colors.white12,
   children: <Widget>[
       ListTile(
          title: Text('list title'),
          subtitle: Text('subtitle'),
       )
   ],
),

展开闭合列表 ExpansionPanelList可以实现展开闭合的列表功能,这个列表必须放在可滑动组件中使用
expansionCallback点击和交互的回调事件,第一个参数是触发动作的索引,第二个是布尔类型的触发值

import 'package:flutter/material.dart';
class MyExpamsionPanelList extends StatefulWidget {
  _MyExpamsionPanelListState createState() => _MyExpamsionPanelListState();
}
class _MyExpamsionPanelListState extends State<MyExpamsionPanelList> {
  var currentPanelIndex = -1;
  List<int> mList;
  List<ExpansionState> expansionStateList;
  _MyExpamsionPanelListState(){
    mList = new List();
    expansionStateList = new List();
    for(int i=0;i<10;i++){
      mList.add(i);
      expansionStateList.add(ExpansionState(i,false));
    }
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('expanison panel list'),
      ),
      body: SingleChildScrollView(
        child: ExpansionPanelList(
        expansionCallback: (int index, isExpand){
           setState(() {
             expansionStateList.forEach((item){
               if(item.index == index){
                 item.isOpen = !isExpand;
               }
             });
           });
        },
        children: mList.map((index){
           return ExpansionPanel(
              headerBuilder: (context, isExpanded){
                return ListTile(
                  title: Text('This is No.$index'),
                );
              },
              body: ListTile(
                title: Text('expansion No.$index'),
              ),
              isExpanded: expansionStateList[index].isOpen
           );
        }).toList(),
        ),
      )
    );
  }
}
class ExpansionState{
  var isOpen;
  var index;
  ExpansionState(this.index,this.isOpen);
}

贝塞尔曲线切割

clipPath控件可以把其内部的子控件切割 clipper属性,切割的路径,要和CustomClipper对象配合使用

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
     return Scaffold(
        body: Column(
           children: <Widget>[
               ClipPath(
                   clipper: BottomCliperTest(),
                   child: Container(
                     color: Colors.deepPurpleAccent,
                     height: 200.0,
                  ),
                )
            ],
         ),
     );
  }
}
class BottomCliperTest extends CustomClipper<Path> {
    @override
    Path getClip(Size size){
        var path = Path();
        path.lineTo(0, 0);
        path.lineTo(0, size.height - 50);
        var firstControlPoint = Offset(size.width/2, size.height);
        var firstEndPoint = Offset(size.width,size.height-50);
        path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,   firstEndPoint.dx, firstEndPoint.dy);
        path.lineTo(size.width,0);
        return path;
    }
    @override
    bool shouldReclip(CustomClipper<Path> oldCliper){
        return false;
    }
}

波浪式贝塞尔曲线

  var path = Path();
  path.lineTo(0, size.height-40);
  var firstControlPoint = Offset(size.width/4, size.height);
  var firstEndPoint = Offset(size.width/2.25,size.height - 30);
  path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy, firstEndPoint.dx, firstEndPoint.dy);
  var secondControlPoint = Offset(size.width/4 * 3, size.height-80);
  var secondEndPoint = Offset(size.width,size.height - 40);
  path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy, secondEndPoint.dx, secondEndPoint.dy);
  path.lineTo(size.width, 0);
  return path;

inkWell

点击效果墨水池

InkWell(
  onTap:(){},
  child:
)

Offstage

  • offstagetrue时,当前控件不会被绘制在屏幕上,不会响应点击事件,不会占用空间
  • offstagefalse时,当前控件跟平常用的空间一样渲染绘制
Offstage(
  offstage: offstage,
  child: Container(color: Colors.blue, height: 100.0),
)

OverflowBox

允许child超出parent的范围显示,最大尺寸大于child的时候,child可以完整显示,小于child的时候以最大尺寸为基准。

OverflowBox(
  maxWidth: MediaQuery.of(context).size.width,
  maxHeight: MediaQuery.of(context).size.height,
  alignment: Alignment.topCenter,
  child: Container(
    width: 1200.0,
  ),
),

SizedBox

设置具体尺寸

SizedBox(
  width: 200.0,
  height: 200.0,
  child: Container(
    color: Colors.red,
    width: 100.0,
    height: 300.0,
),
),

LimitedBox

对最大宽高进行限制

LimitedBox(
  maxWidth: 150.0,
  child: Container(
    color: Colors.blue,
    width: 250.0,
  ),
),

长宽比控件

AspectRatio(
  aspectRatio: 4/3,
  child: Container(
  color: Colors.green,
 ),
),

Drawer 抽屉

Scaffold中加入drawer项可以使用抽屉

Drawer(
  child: ListView(
    children: <Widget>[
      UserAccountsDrawerHeader(
        accountName: null,
        accountEmail: null,
        currentAccountPicture: null,
        decoration: BoxDecoration(
          image: DecorationImage(
            fit: BoxFit.cover,
            image: AssetImage('images/bg_account_switcher.png')
          )
        ),
      ),
     ListTile(
       title: Text(
         '首页',
         style: TextStyle(
           color: Colors.blue[600],
           fontSize: ScreenUtil().setSp(32)
         ),
       ),
       leading: ImageIcon(
          AssetImage('images/quantum_ic_home_black_24.png'),
          color: Colors.blue[600],
        ),
        onTap: () {},
     ),
  ],
),

延时

Future.delayed(Duration.zero,()=>_futrue());
controller.animateTo(
controller.position.maxScrollExtent,
duration: new Duration(milliseconds: 150),
curve: Curves.easeIn,
);

TextField

TextEditingController controller = TextEditingController();
Widget buildTextField(TextEditingController controller) {
return TextField(
//控制正在编辑的文本。通过其可以拿到输入的文本值
//获取方式 String value=controller.text
controller: controller,
//给TextField设置装饰(形状等)
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
borderSide: BorderSide(color: Colors.transparent)
),
//输入内容距离上下左右的距离 ,可通过这个属性来控制 TextField的高度
contentPadding: EdgeInsets.all(10.0),
fillColor: Colors.white, filled: true,
// labelText: 'Hello',
// 以下属性可用来去除TextField的边框
disabledBorder: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
//键盘类型
keyboardType: TextInputType.text,
//控制键盘的功能键 指enter键,比如此处设置为next时,enter键
//显示的文字内容为 下一步
textInputAction: TextInputAction.next,
//键盘大小写的显示 Only supports text keyboards 但是好像不起作用?
//characters 默认为每个字符使用大写键盘
//sentence 默认为每个句子的第一个字母使用大写键盘
//word 默认为每个单词的第一个字母使用大写键盘。
//none 默认使用小写
textCapitalization: TextCapitalization.words,
//是否是密码
obscureText: false,
//文本对齐方式(即光标初始位置)
textAlign: TextAlign.center,
//输入文本的样式
style: TextStyle(fontSize: 30.0, color: Colors.blue),
//最大行数
maxLines: 1,
//最大长度,设置此项会让TextField右下角有一个输入数量的统计字符串
//这种情况一般是不符合我们设计的要求的,但是有需要限制其输入的位数
// 这时候我们可以使用下方的属性来限制
maxLength: 9,
// 跟最大长度限制对应,如果此属性设置为false,当输入超过最大长度
// 限制时,依然会显示输入的内容,为true不会(默认为 true)
maxLengthEnforced: false,
// 限制输入的 最大长度 TextField右下角没有输入数量的统计字符串
// inputFormatters: [LengthLimitingTextInputFormatter(11)],
//允许的输入格式 下方的模式指只允许输入数字
// inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
//控制此小部件是否具有键盘焦点。
focusNode: FocusNode(),
//是否自动更正
autocorrect:false,
//是否自动获得焦点
autofocus: false,
//是否禁用textfield:如果为false, textfield将被“禁用”
enabled: true,
//光标颜色
cursorColor: Colors.red,
//光标宽度
cursorWidth: 5.0,
//光标圆角弧度
cursorRadius: Radius.circular(5.0),
//内容改变的回调
onChanged: (text) {
print('change $text');
},
//内容提交(按回车)的回调
onSubmitted: (text) {
print('submit $text');
},
//按回车时调用
onEditingComplete: (){
print('onEditingComplete');
},
);
}