大家好,我是“追梦蜗牛”,大家可以在公众号后台回复 “Java资料”获得技能提升的资料,绝对是干货。
上篇文章为大家讲述了flutter的主要框架组件;本篇文章接着上篇内容继续为大家介绍flutter的组件使用,并且通过组件实现登录界面,本文针对功能点做特殊实例讲解,特别详细的整体使用我们会在其它的文章中来展开说明。每篇文章只要有代码说明 就会有demo提供。
前言
一、Widget介绍
二、布局控制 Row、Column控件 布局行为以及使用场景
三、Text组件使用和继承关系
四、Image组件使用和分辨率的适配
五、TextField 输入框的使用和数据控制器
六、 符合组件Container的使用实现(点击按钮)和类引用
七、提示信息的添加
八、总结
一、Widget介绍
Flutter Widget采用现代响应式框架构建,这是从 React 中获得的灵感,中心思想是用widget构建你的UI。Widget描述了他们的视图在给定其当前配置和状态时应该看起来像什么。当widget的状态发生变化时,widget会重新构建UI,Flutter会对比前后变化的不同, 以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。
例如:就以官网给出的示例,一个最简单的Flutter应用程序,只需一个widget即可!如下面示例:将一个widget传给runApp函数即可:
import 'package:flutter/material.dart';
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
该runApp函数接受给定的Widget并使其成为widget树的根。在此示例中,widget树由两个widget:Center(及其子widget)和Text组成。框架强制根widget覆盖整个屏幕,这意味着文本“Hello, world”会居中显示在屏幕上。文本显示的方向需要在Text实例中指定,当使用MaterialApp时,文本的方向将自动设定。
示例如下:
class LoginPage extends StatefulWidget{
final String title;
LoginPage({Key key,this.title});
@override
createState() => new LoginPageState(title: this.title);//传递数据使用
}
class LoginPageState extends State<LoginPage>{
String title;
LoginPageState({Key key,this.title});
@override
Widget build(BuildContext context){
//界面布局
return Scaffold(
appBar: AppBar(//设置标题
title: Text("登录",
style: TextStyle(color: Colors.white)),
iconTheme: IconThemeData(color: Colors.white)
),
body: Form(//页面内容
child: ListView(
key: _formKey,
padding: const EdgeInsets.all(10.0),
children: <Widget>[
SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度
pageTitle(),//登录标题,外部引用函数显示
],
)
)
);
}
}
该实例中 重写Widget函数实现界面显示
@override
Widget build(BuildContext context){
}
二、布局控制 Row、Column控件 布局行为以及使用场景
上面的实例中显示布局用到了多种布局控件,用来控制布局的显示
1. 实例中 用到了表单 组件 Form,Flutter中的Form组件和html中的<form></form>的作用类似,都是起到了一个容器的作用
2. ListView 是一个线性布局的widgets 列表,ListView是最常用的滑动组件。它在滚动方向上一个接一个地显示它的孩子。在交叉轴中,需要孩子填充ListView。
3. 布局的方向除了From和Listview还有其他方式设定,通过 布局分为 上下布局 和 左右布局,Flutter通过这两种布局给出了 Row、Column控件
Row
在Flutter中非常常见的一个多子节点控件,将children排列成一行。估计是借鉴了Web中Flex布局,如android的线性布局设置了属性:android:orientation 值为horizontal,所以很多属性和表现,都跟其相似。但是注意一点,自身不带滚动属性,如果超出了一行,在debug下面则会显示溢出的提示。
Row的布局有六个步骤,这种布局表现来自Flex(Row和Column的父类):
1.首先按照不受限制的主轴(main axis)约束条件,对flex为null或者为0的child进行布局,然后按照交叉轴( cross axis)的约束,对child进行调整;
2.按照不为空的flex值,将主轴方向上剩余的空间分成相应的几等分;
3.对上述步骤flex值不为空的child,在交叉轴方向进行调整,在主轴方向使用最大约束条件,让其占满步骤2所分得的空间;
4.Flex交叉轴的范围取自子节点的最大交叉轴;
5.主轴Flex的值是由mainAxisSize属性决定的,其中MainAxisSize可以取max、min以及具体的value值;
6.每一个child的位置是由mainAxisAlignment以及crossAxisAlignment所决定。
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > RenderObjectWidget > MultiChildRenderObjectWidget > Flex > Row
Row(
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
padding: EdgeInsets.all(5.0),
),
flex: 1,
),
Expanded(
child: Container(
color: Colors.blue,
padding: EdgeInsets.all(5.0),
),
flex: 1,
),
],
)
例子说明,使用Expanded控件,将一行的宽度分成2个等分,每个child占1/2区域,由flex属性控制。
构造函数
Row({
Key key,
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline textBaseline,
List<Widget> children = const <Widget>[],
})
Column
Row和Column都是Flex的子类,只是direction参数不同。Column各方面同Row。
三、Text组件使用和继承关系
Text显示文本添加在Widget中,作为一个函数引用,看起来模块化一些,不容易混乱
@override
Widget build(BuildContext context){
//界面布局
return Scaffold(
appBar: AppBar(//设置标题
title: Text("登录",
style: TextStyle(color: Colors.white)),
iconTheme: IconThemeData(color: Colors.white)
),
body: Form(//页面内容
child: ListView(
key: _formKey,
padding: const EdgeInsets.all(10.0),
children: <Widget>[
SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度
pageTitle(),//登录标题,外部引用函数显示
],
)
)
);
Text添加通过 Padding来实现;
添加内边距 padding设置边距有两种 一种通过 all 设置全部边距,一种通过only设置每一个的边距
padding: EdgeInsets.only(left: 5.0, top: 15.0,bottom: 5.0,right: 5.0),// 1. EdgeInsets.all(8.0) 所有内边距 2. EdgeInsets.only(left: 12.0, top: 4.0) 设置内边距
设置文本的位置,居中,居左,居右等
textAlign: TextAlign.center,
设置文本的样式
style: TextStyle(inherit: false,fontSize: 22.0,color: Color(0xFF000000),/*fontStyle: FontStyle.italic,*/letterSpacing: 2.0,/*decoration: TextDecoration.overline,
decorationStyle: TextDecorationStyle.wavy,*/fontWeight: FontWeight.w700),
//字体是否隐藏 inherit: true,
//字体样式 大小,颜色 color: Color(0xFFffffff) ,color: Color.fromARGB(255, 150, 150, 150),
//字体斜体 fontStyle: FontStyle.italic
//字体间距 letterSpacing: 10.0,
//上划线 decoration: TextDecoration.overline,(lineThrough 删除线)(underline 下划线)
//波浪线 decorationStyle: TextDecorationStyle.wavy (dashed)(dotted)
//字重 fontWeight: FontWeight.w700
具体的效果不在一一演示,大伙可以根据代码验证效果。
//顶部登录标题(文本介绍)
Padding pageTitle() {
return Padding(
padding: EdgeInsets.only(left: 5.0, top: 15.0,bottom: 5.0,right: 5.0),// 1. EdgeInsets.all(8.0) 所有内边距 2. EdgeInsets.only(left: 12.0, top: 4.0) 设置内边距
child: Text(
'欢迎进入登录界面',
textAlign: TextAlign.center,
//字体是否隐藏 inherit: true,
//字体样式 大小,颜色 color: Color(0xFFffffff) ,color: Color.fromARGB(255, 150, 150, 150),
//字体斜体 fontStyle: FontStyle.italic
//字体间距 letterSpacing: 10.0,
//上划线 decoration: TextDecoration.overline,(lineThrough 删除线)(underline 下划线)
//波浪线 decorationStyle: TextDecorationStyle.wavy (dashed)(dotted)
//字重 fontWeight: FontWeight.w700
style: TextStyle(inherit: false,fontSize: 22.0,color: Color(0xFF000000),/*fontStyle: FontStyle.italic,*/letterSpacing: 2.0,/*decoration: TextDecoration.overline,
decorationStyle: TextDecorationStyle.wavy,*/fontWeight: FontWeight.w700),
),
);
}
继承关系
Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget > Text
四、Image组件使用和分辨率的适配
在 Flutter 中也有多种图片显示方式,用来加载不同形式的图片:
Image:通过ImageProvider来加载图片
Image.asset:用来加载本地资源图片
Image.file:用来加载本地(File文件)图片
Image.network:用来加载网络图片
Image.memory:用来加载Uint8List资源(byte数组)图片
实例:通过 new Image.asset("images/user_photo.png",
alignment: Alignment.topCenter,height: 100,width: 100,), //设置图片logo
设置读取本地图片资源显示,并且设置图片的位置和大小。
@override
Widget build(BuildContext context){
//界面布局
return Scaffold(
appBar: AppBar(//设置标题
title: Text("登录",
style: TextStyle(color: Colors.white)),
iconTheme: IconThemeData(color: Colors.white)
),
body: Form(//页面内容
child: ListView(
key: _formKey,
padding: const EdgeInsets.all(10.0),
children: <Widget>[
SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度
new Image.asset("images/user_photo.png",
alignment: Alignment.topCenter,height: 100,width: 100,), //设置图片logo
],
)
)
);
在本地图片加载显示的时候 为了区分分辨率的大小,需要有多种分辨率
Image.asset
加载一个本地资源图片,和 Android 一样,有多种分辨率的图片可供选择,但是沿袭的是 iOS 的图片风格,分为 1x,2x,3x,具体做法是在项目的根目录下创建两个文件夹,如下图所示:
具体生命图片的操作 请查看之前章节的文字说明。
五、TextField 输入框的使用和数据控制器
文本输入框 TextField,类似于iOS中的UITextField和Android中的EditText和Web中的TextInput。主要是为用户提供输入文本提供方便
构造方法
const TextField({
Key key,
this.controller, //控制器,控制TextField文字
this.focusNode,
this.decoration: const InputDecoration(), //输入器装饰
TextInputType keyboardType: TextInputType.text, //输入的类型
this.style,
this.textAlign: TextAlign.start,
this.autofocus: false,
this.obscureText: false, //是否隐藏输入
this.autocorrect: true,
this.maxLines: 1,
this.maxLength,
this.maxLengthEnforced: true,
this.onChanged, //文字改变触发
this.onSubmitted, //文字提交触发(键盘按键)
this.onEditingComplete, //当用户提交可编辑内容时调用
this.inputFormatters,
this.enabled,
this.cursorWidth = 2.0,
this.cursorRadius,
this.cursorColor,
this.keyboardAppearance,
})
只是知道构造函数还是不够的,具体使用是怎么样的呢,需要实现了看效果,下面我们通过一个实例来了解一下 输入框的显示和控制器获取数据
@override
Widget build(BuildContext context){
//界面布局
return Scaffold(
body: Form(//页面内容
child: ListView(
key: _formKey,
padding: const EdgeInsets.all(10.0),
children: <Widget>[
SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度
//----用户名
Row(//水平布局控件
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("用户名:"),
// Image.asset("act_login_et_username.PNG"),
Expanded(child: TextField(
controller: usernameCtrl,
decoration: InputDecoration(
prefixIcon: Icon(Icons.person),//可以设置用户名图标在输入框中
hintText: "注册邮箱",//可以设置用户名在左边
hintStyle: TextStyle( color: const Color(0xFF000000)
),
border: OutlineInputBorder(//如果要边框,设置该属性,否则默认是底部下划线
borderRadius: const BorderRadius.all(const Radius.circular(6.0)),
// borderSide: BorderSide(color: kYellow),//边框颜色变化
borderSide: BorderSide(color: Color(0x00000000)),
),
contentPadding: const EdgeInsets.all(10.0)
),
))
],
),
],
)
)
);
}
输入框放置在一个 Row水平布局组件中,因为输入框需要显示主题,输入框,主题图标,所以需要放置在 children: <Widget>[中实现,
Image.asset("act_login_et_username.PNG"), 显示一个图标放置在左边
Expanded(child: TextField 添加输入框
添加控制器,用来控制数据 controller: usernameCtrl,
添加 decoration属性
decoration: InputDecoration(
prefixIcon: Icon(Icons.person),//可以设置用户名图标在输入框中
hintText: "注册邮箱",//可以设置用户名在左边
hintStyle: TextStyle( color: const Color(0xFF000000)),//设置输入框默认信息的样式
border: OutlineInputBorder(//如果要边框,设置该属性,否则默认是底部下划线
borderRadius: const BorderRadius.all(const Radius.circular(6.0)),
// borderSide: BorderSide(color: kYellow),//边框颜色变化
borderSide: BorderSide(color: Color(0x00000000)),
),
contentPadding: const EdgeInsets.all(10.0)
),
数据控制器添加
在TextField 中添加 属性controller
Expanded(child: TextField(
controller: usernameCtrl,
decoration: InputDecoration(
prefixIcon: Icon(Icons.person),//可以设置用户名图标在输入框中
hintText: "注册邮箱",//可以设置用户名在左边
hintStyle: TextStyle( color: const Color(0xFF000000)
),
border: OutlineInputBorder(//如果要边框,设置该属性,否则默认是底部下划线
borderRadius: const BorderRadius.all(const Radius.circular(6.0)),
// borderSide: BorderSide(color: kYellow),//边框颜色变化
borderSide: BorderSide(color: Color(0x00000000)),
),
contentPadding: const EdgeInsets.all(10.0)
),
))
控制器获取数据
var loginBtn = Builder(builder: (ctx) {
return CommonButton(text: "登录", onTap: () {
// 拿到用户输入的账号密码
String username = usernameCtrl.text.trim();
if (username.isEmpty) {
Scaffold.of(ctx).showSnackBar(SnackBar(
content: Text("账号和密码不能为空!"),
));
return;
}
// 关闭键盘
FocusScope.of(context).requestFocus(FocusNode());
// 发送给webview,让webview登录后再取回token
loginPush(username, password);
});
});
六、 符合组件Container的使用实现(点击按钮)和类引用
提交登录或者提交某一个操作的时候,需要一个按钮效果并支持点击后操作
如下通过引用外部的封装的类实现 点击效果
@override
Widget build(BuildContext context){
//登录
var loginBtn = Builder(builder: (ctx) {
return CommonButton(text: "登录", onTap: () {
// 拿到用户输入的账号密码
String username = usernameCtrl.text.trim();
String password = passwordCtrl.text.trim();
if (username.isEmpty || password.isEmpty) {
Scaffold.of(ctx).showSnackBar(SnackBar(
content: Text("账号和密码不能为空!"),
));
return;
}
// 关闭键盘
FocusScope.of(context).requestFocus(FocusNode());
// 发送给webview,让webview登录后再取回token
loginPush(username, password);
});
});
//界面布局
return Scaffold(
body: Form(//页面内容
child: ListView(
key: _formKey,
padding: const EdgeInsets.all(10.0),
children: <Widget>[
SizedBox(height: 0/*kToolbarHeight*/),//设置一个工具bar高度
//----登录按钮
loginBtn,
],
)
)
);
}
引用类中通过 Container实现点击效果,
Container在Flutter中非常常见。官方给出的简介,是一个结合了绘制(painting)、定位(positioning)以及尺寸(sizing)widget的widget。
它是一个组合的widget,内部有绘制widget、定位widget、尺寸widget。后续看到的不少widget,都是通过一些更基础的widget组合而成。
Container会遵循如下顺序去尝试布局:
对齐(alignment);
调节自身尺寸适合子节点;
采用width、height以及constraints布局;
扩展自身去适应父节点;
调节自身到足够小。
class CommonButton extends StatefulWidget {
final String text;
final GestureTapCallback onTap;
CommonButton({@required this.text, @required this.onTap});
@override
State<StatefulWidget> createState() => CommonButtonState();
}
class CommonButtonState extends State<CommonButton> {
Color color = ThemeColorUtils.currentColorTheme;
TextStyle textStyle = TextStyle(color: Colors.white, fontSize: 17);
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
this.widget.onTap();
},
child: Container(
height: 45,
decoration: BoxDecoration(
color: color,
border: Border.all(color: const Color(0xffcccccc)),
borderRadius: BorderRadius.all(Radius.circular(30))
),
child: Center(
child: Text(this.widget.text, style: textStyle,),
),
),
);
}
}
上面的简单实例,就是以 复合组件 Container 类设置 高度,圆角,颜色,居中的文字的个组合成了一个按钮效果 。
七、提示信息的添加
提示框是很常用的一种效果,比如:添加操作成功提示,获取数据成功结果提示,等等
这种使用率分成高的,效果需要单独分离出来,实现自定义效果,需要用到 OverlayState组件,
什么是OverlayState?
Flutter事实上有一个Overlay的Widge,它是一个StatefullWidget,它的createState方法获取的就是OverlayState对象。
Overlay可以认为是一个UI上面的蒙版/浮空层,使用起来类似Stack,如何使用:
通过Overlay.of获得OverlayState对象,调用OverlayState.insert添加OverlayEntry,当不需要的时候,通过OverlayEntry.remove移除OverlayEntry。
import 'package:flutter/material.dart';
class Toast {
static OverlayEntry _overlayEntry; //toast靠它加到屏幕上
static bool _showing = false; //toast是否正在showing
static DateTime _startedTime; //开启一个新toast的当前时间,用于对比是否已经展示了足够时间
static String _msg;
static void show(
BuildContext context,
String msg,
) async {
assert(msg != null);
_msg = msg;
_startedTime = DateTime.now();
//获取OverlayState
OverlayState overlayState = Overlay.of(context);
_showing = true;
if (_overlayEntry == null) {
_overlayEntry = OverlayEntry(
builder: (BuildContext context) => Positioned(
//top值,可以改变这个值来改变toast在屏幕中的位置
top: MediaQuery.of(context).size.height * 2 / 3,
child: Container(
alignment: Alignment.center,
width: MediaQuery.of(context).size.width,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 80.0),
child: AnimatedOpacity(
opacity: _showing ? 1.0 : 0.0, //目标透明度
duration: _showing
? Duration(milliseconds: 100)
: Duration(milliseconds: 400),
child: _buildToastWidget(),
),
)),
));
overlayState.insert(_overlayEntry);
} else {
//重新绘制UI,类似setState
_overlayEntry.markNeedsBuild();
}
await Future.delayed(Duration(milliseconds: 2000)); //等待两秒
//2秒后 到底消失不消失
if (DateTime.now().difference(_startedTime).inMilliseconds >= 2000) {
_showing = false;
_overlayEntry.markNeedsBuild();
}
}
//toast绘制
static _buildToastWidget() {
return Center(
child: Card(
color: Colors.black87,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 10.0),
child: Text(
_msg,
style: TextStyle(
fontSize: 16.0,
color: Colors.white,
),
),
),
),
);
}
}
八、总结
本章主要对组件的使用做详细介绍和使用说明
需要掌握几种知识点:
1. From 组件
2.ListView组件
3.Row 和Clounm 水平和竖直组件
4.符合组件的使用 Container
5.图片分辨率的设置
6.输入框 ,控制器的使用
7.自定义提示框的添加
完整代码地址:https://github.com/chenjianpeng/flutter/tree/master/flutter_demo002
- End -
长按二维码关注
期待您的加入
▽