一、环境配置
1、配置java环境
1、下载java JDK
Oracle官网下载jdk17:www.oracle.com/java/techno…
登录Oracle账号,若无账号则创建账号并且登录。
2、安装jdk
双击下载完成的jdk进行安装,记录安装路径。
- 配置环境变量 我的电脑->右键->属性->高级系统设置
点击 “环境变量”
修改以下系统变量(非用户变量),若不存在,则新建。
- 变量名:JAVA_HOME
- 变量值:电脑上JDK安装的绝对路径
- 变量名:CLASSPATH
- 变量值:.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
最前面的.不要漏掉!
- 变量名:Path
- 变量值:
- %JAVA_HOME%\bin
- %JAVA_HOME%\jre\bin
3、验证是否配置成功
打开命令行工具:win+R,输入cmd,点击确定。
输入 java -version ,若正常显示出版本号,则配置成功。
2、Android环境配置
1、下载Android Studio
进入android studio官网,下载Android Studio:developer.android.google.cn/studio/ 向下拉,选择绿色版,安装较为简单。
2、安装AS
- 下载完成后解压到非系统盘,进入目录:/bin,双击 studio64.exe
- 取消Android SDK自动安装
- 点击下一步
- 选择Custom点击Next
- 选择UI风格
- 设置Android SDK,若本地无SDK可自己设置一个目录(最好不要设置到系统盘),Android studio会自动下载Android SDK。
- 模拟器设置,直接点下一步
- 点击finish,安装Android SDK
3、配置环境变量
非必须步骤
- 变量名:ANDROID_HOME
- 变量值:Android SDK的安装路径
- 变量名:Path
- 变量值:
- %ANDROID_HOME%\tools
- %ANDROID_HOME%\platform-tools
3、Flutter环境配置
1、下载Flutter SDK
下载完成后解压到本地磁盘。(例如 D:\flutter\flutter)
请注意
- 请勿将 Flutter 有特殊字符或空格的路径下。
- 勿将 Flutter 安装在需要高权限的文件夹内,例如 C:\Program Files\。
2、设置环境变量
- 变量名:Path
- 变量值:flutter\bin 目录的完整路径
- 变量名:PUB_HOSTED_URL
- 变量值:pub.flutter-io.cn
- 变量名:FLUTTER_STORAGE_BASE_URL
- 变量值:storage.flutter-io.cn
3、运行flutter doctor
打开命令行窗口,输入 flutter doctor
上述命令会检查你的现有环境,并将检测结果以报告形式呈现出来。仔细阅读它显示的内容,检查是否有尚未安装的软件或是有其他的步骤需要完成(通常会以粗体呈现,Visual Studio与Android Studio安装一个即可)。
4、安装Flutter插件
以Android Studio为例:
1、安装dart插件
打开Android Studio,选择Plugins
搜索 dart,点击 Install 按钮进行安装。
2、安装flutter插件
搜索 flutter,点击 Install 按钮进行安装。
安装完成后重启Android studio生效
二、Dart语法
变量声明
int i = 1;
double d = 0.1;
bool b = true;
String str = "";
Object o = 1.1;
//可变数据类型
dynamic dy = 1.1;
//允许
dy = "hello";
//类型推导
var v = 1.1;
//不允许
v = "hello";
思考: Object、dynamic、var的区别?
私有变量
Java是通过private关键字声明私有变量或者私有函数的。而Dart没有private/public关键字,默认声明的函数或者变量都为public属性,如果函数或者变量以 _ 开头,则为private属性。不可见属性是相对于文件不可见。
常量
1、const:编译时常量
必须在编译阶段完成赋值,必须为编译常量。
//编译常量,在编译时赋值
const int age = 23;
//时间戳运行时赋值,此语法不允许
const DateTime timeStamp = DateTime.now();
const修饰包含成员变量或者方法的对象时,实例本身以及成员皆不可修改。
const arr = [1];
//此操作不允许,变量实例不允许修改
arr = [1, 2];
//此操作不允许,成员变量不允许修改
arr[0] = 4;
debugPrint(arr.toString());
2、final:运行时常量
允许运行时赋值,可在程序执行到此代码块时进行赋值。
//编译常量,允许在编译时赋值
final DateTime timeStamp = DateTime.now();
debugPrint(timeStamp.toString());
final修饰包含成员变量或者方法的对象时,实例本身不可修改,成员变量允许修改。
final arr = [1];
//此操作不允许,变量实例不允许修改
arr = [1, 2];
//此操作允许,成员变量可以修改
arr[0] = 4;
debugPrint(arr.toString());
枚举
enum Country{
cn,
us,
uk
}
与java不同的是,dart的枚举可以添加内部变量。比如我们需要通过枚举类型国家代码,获取国家名字。可以这么做:
void main() {
Country c = Country.us;
debugPrint('c: ${c.name}');
}
enum Country{
cn('中国'),
us('美国'),
uk('英国');
final String name;
const Country(this.name);
}
反过来,如果我们需要通过国家名字,获取国家的代码,可以这么做:
void main() {
String name = "美国";
Country country = Country.fromName(name);
debugPrint("country: $country");
}
enum Country {
cn('中国'),
us('美国'),
uk('英国');
final String name;
const Country(this.name);
static Country fromName(String name) {
return values.firstWhere(
(element) => element.name == name,
orElse: () => cn,
);
}
}
操作符 / 运算符
1、非空断言/空安全操作符
String? name;
//name?:空安全操作符
int? l1 = name?.length;
//name!:非空断言操作符
int l2 = name!.length;
2、除法,整除、取模
main() {
var i = 7;
debugPrint("${i / 3}"); //除法操作,结果2.3333333333333335
debugPrint("${i ~/ 3}"); //整除,结果2
debugPrint("${i % 3}"); //取模,结果1
}
3、??= 赋值操作
- 当变量为null时,使用后面的内容进行赋值。
- 当变量不为null时,使用自己原来的值。
就是如果该变量不为null,该运算符操作无效
String? userName = "Tom";
userName ??= "Jerry"; //变量本身有值,使用变量本身的值
debugPrint(userName); //结果为"Tom"
String? nickName;
nickName ??= "猫"; //变量本身为null,使用后面的值
debugPrint(nickName); //结果为猫
4、expr1??expr2条件运算符
- 如果expr1为null,返回expr2的结果。
- 如果expr1不为null,返回expr1的结果。
String str1 = "Tom" ?? "Jerry"; //结果为"Tom"
String str2 = null ?? "Jerry"; //结果为"Jerry"
5、级联运算符
..完成链式调用
class Person {
String? name;
void run() {
debugPrint("$name is running");
}
void eat() {
debugPrint("$name is eating");
}
}
main() {
Person p1 = Person();
p1.name = "Tom";
p1.run();
p1.eat();
Person p2 = Person()
..name = "Jerry"
..run()
..eat();
}
6、解构操作符
... 将数组解构为单个的item
List<int> list1 = [1, 2, 3];
List<int> list2 = [0, ...list1, 4];
debugPrint("list2: $list2"); //[0, 1, 2, 3, 4]
7、is 关键字
用于检查某个对象是否属于某个类型,相当于Java的instanceof。与Java不同的是is还有一个否行形式 is! 。
var i = 1;
//相当于Java的 i instanceof Integer
debugPrint("${i is int}"); //true
debugPrint("${i is! int}"); //false
函数
Dart不支持重载,但是支持可选参数。
1、位置可选参数
main() {
printInfo("Tom");
printInfo("Tom", null);
printInfo("Tom", null, 1.98);
printInfo("Tom", 15, 1.98);
}
/// 位置可选参数(可选参数使用[])
void printInfo(String name, [int? age, double height = 1.88]) {
debugPrint("$name, $age, $height");
}
2、命名可选参数
main() {
printMessage("Jerry");
printMessage("Jerry", age: 17);
printMessage("Jerry", height: 1.68);
printMessage("Jerry", age: 17, height: 1.68);
}
///命名可选参数(可选参数使用{})
void printMessage(String name, {int? age, double height = 1.88}) {
debugPrint("$name, $age, $height");
}
3、函数一等公民
1、函数作为参数:
void main() {
say(getMessage);
}
void say(String Function() func) {
String message = func();
debugPrint("say: $message");
}
String getMessage() {
return 'Hello World!';
}
2、函数作为返回值:
void main() {
Function fun = say();
debugPrint("say: ${fun()}");
}
String Function() say() {
return getMessage;
}
String getMessage() {
return 'Hello World!';
}
类
1、构造函数
class Rectangle {
int width = 0;
int height = 0;
//构造函数
// Rectangle(int width, int height) {
// this.width = width;
// this.height = height;
// }
//Dart支持的语法糖写法
Rectangle(this.width, this.height);
}
2、命名构造函数
void main() {
Rectangle r1 = Rectangle(10, 11);
Rectangle r2 = Rectangle.fromJson({'width': 100, 'height': 110});
}
class Rectangle {
int width = 0;
int height = 0;
Rectangle(this.width, this.height);
// 假设这个构造函数用于从一个JSON对象中创建Rectangle对象
Rectangle.fromJson(Map<String, int> json) {
width = json['width'] ?? 0;
height = json['height'] ?? 0;
}
}
3、初始化列表
void main() {
Rectangle r1 = Rectangle(10, 11);
}
class Rectangle {
int width = 0;
int height = 0;
int? area;
Rectangle(this.width, this.height) : area = width * height;
}
集合
List
List<int> list = [3, 4, 3];
//取值
int item = list[0];
//插入
list.add(5);
list.insert(0, 1);
//删除
list.remove(1);
list.removeAt(0);
list.removeRange(0, 1);
list.removeWhere((element) => element > 4);
//转换
List<String> result = list
.map((e) => e * 2)
.where((element) => element > 5)
.map((e) => '年龄为:$e')
.toList();
//遍历
for (var element in list) {
debugPrint("$element");
}
for (var i = 0; i < result.length; ++i) {
var o = result[i];
debugPrint(o);
}
Map
Map<String, dynamic> map = {
'age': 1,
};
//取值
int? item = map['age'];
//插入
map['height'] = 1.8;
//删除
map.remove('height');
map.removeWhere((key, value) => value is int);
//转换
Map<String, String> result = map.map((key, value) => MapEntry(key, value.toString()));
//遍历
map.forEach((key, value) {
debugPrint("key:$key,value:$value");
});
for (var key in map.keys) {
debugPrint("key:$key,value:${map[key]}");
}
Set
Set set = {'Tom', 'Jerry'};
//利用Set去重
List<String> list = ['Tom', 'Jerry', 'Jerry'];
list = list.toSet().toList();
debugPrint("list: $list");
接口
dart中无Interface,使用函数作为回调接口使用。
void main() {
MyButton button = MyButton(
'按钮',
() => debugPrint("点击了按钮"),
);
button.onPressed();
}
class MyButton {
String text;
Function() onPressed;
MyButton(this.text, this.onPressed);
}
混入
混入是Dart提供的一个多个类中复用相同代码的方式。与继承有所不同。继承只可以单继承,但是复用性强,父类所有变量都会被子类继承。混入可以多混入,但是复用性弱,混入类的变量不属于被混入类。
mixin Jump {
void jumping() {
debugPrint("跳跃");
}
}
mixin Run {
void running() {
debugPrint("奔跑");
}
}
class Person with Jump, Run {
}
扩展函数
使用 extension 关键字,必须设置类型。
void func(String? name) {
if (name.isNotBlank) {
debugPrint("name: $name");
}
}
extension CustomerExtension on String? {
bool get isNotBlank {
if (null != this && this!.isNotEmpty) {
return true;
}
return false;
}
}
三、StatelessWidget
AppBar
AppBar(
//标题内容
title: const Text('Title'),
//是否自动添加返回按钮
automaticallyImplyLeading: false,
//左侧按钮
leading: IconButton(
onPressed: () {},
icon: const Icon(Icons.arrow_back_ios),
),
//阴影
elevation: 4,
//背景颜色
backgroundColor: Colors.purple,
//右侧按钮
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.add)),
IconButton(onPressed: () {}, icon: const Icon(Icons.share)),
],
);
Scaffold
Scaffold(
//自动调整body的大小
resizeToAvoidBottomInset: false,
//标题栏
appBar: AppBar(title: const Text('标题')),
//页面内容
body: Container( ),
//左侧抽屉
drawer: Drawer(),
//浮动按钮
floatingActionButton: FloatingActionButton(onPressed: () { }, child: const Icon(Icons.add)),
)
Container
Container(
padding: EdgeInsets.symmetric(vertical: 1),
//内距离
alignment: Alignment.center,
//内部控件居中
width: 40,
//宽
height: 30,
//高
child: Icon(Icons.star),
//color: Colors.red, //背景色,与BoxDecoration互斥,设置了BoxDecoration就不能使用此属性
decoration: BoxDecoration(
color: Colors.red,
//设置形状
shape: BoxShape.circle,
//边框
border: Border.all(width: 1, color: Colors.red),
// border: Border(bottom: BorderSide(width: 1, color: Colors.red)),//下划线
//设置圆角
borderRadius: BorderRadius.all(Radius.circular(25.0)),
//设置图片
image: DecorationImage(image: NetworkImage("XXX.jpg"), fit: BoxFit.cover),
//设置阴影
boxShadow: [
BoxShadow(
blurRadius: 40, //阴影深度(数字越大阴影越淡)
offset: Offset(20, 20), //阴影X,Y轴偏移距离
spreadRadius: 10, //阴影与decoration分割线宽度,可用此属性配合borderRadius实现圆角边框效果
color: Colors.black26, //阴影颜色
)
],
//渐变颜色
//RadialGradient圆形渐变效果
gradient: LinearGradient(
colors: [Colors.white, Colors.red], //渐变起止色
begin: Alignment.bottomCenter, //渐变开始方向
end: Alignment.topCenter, //渐变结束方向
stops: [0.4, 0.6], //渐变变化范围,(只有比例尺0.4~0.6范围内渐变,其他范围为纯色)
),
),
);
Text
Text(
'Hello World',
style: TextStyle(
color: Colors.blue,
fontSize: 24,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.start,
)
除了正常显示的文本,还可以显示富文本。
const Text.rich(TextSpan(
children: const [
TextSpan(text: 'Hello', style: TextStyle(fontSize: 15, color: Colors.red),),
TextSpan(text: 'World', style: TextStyle(fontSize: 26, color: Colors.blue),),
],
));
如果是多个Text显示同一个样式,可以统一设置style。
DefaultTextStyle(
style: TextStyle(fontSize: 20, color: Colors.red),
child: Column(
children: [
Text('Hello'),
Text('World'),
],
),
);
Colum / Row
Row(
//MainAxisAlignment.start 从开始方向排列
//MainAxisAlignment.end 从结束方向排列
//MainAxisAlignment.center 居中
//MainAxisAlignment.spaceBetween 空白区域均分排列子元素,并且首尾没有空隙
//MainAxisAlignment.spaceAround 空白区域均分排列子元素,中间子控件距离相等,首位子控件距离左右边界距离为中间子控件距离的一半
//MainAxisAlignment.spaceEvenly 空白区域均分排列子元素,各个子控件距离相等
mainAxisAlignment: MainAxisAlignment.start,
//TextDirection.ltr 排列方向从左向右(默认)
//TextDirection.rtl 排列方向从右向左
textDirection: TextDirection.rtl,
//MainAxisSize.min相当于主轴方向wrap_content
//MainAxisSize.max相当于主轴方向match_parent
mainAxisSize: MainAxisSize.min,
children: [
Text("text1"),
Text("text2"),
],
);
Stack/Positioned/Align
SizedBox(
width: 300,
height: 300,
child: Stack(
fit: StackFit.loose,
clipBehavior: Clip.hardEdge,
children: [
Container(width: 300, height: 300, color: Colors.yellow),
Align(
alignment: Alignment.center,
child: Container(width: 100, height: 100, color: Colors.red),
),
Positioned(
bottom: 0,
right: 0,
child: Container(width: 100, height: 100, color: Colors.green),
)
],
),
)
FractionallySizeBox
可以按照比例设置占用父组件的尺寸
Container(
width: 300,
height: 300,
alignment: Alignment.center,
child: FractionallySizedBox(
widthFactor: 0.5, //宽占用父组件的一半
heightFactor: 0.5, //高占用父组件的一半
child: LayoutBuilder(
builder: (context, constraints) {
debugPrint("constraint:$constraints"); //约束为 (w=150.0, h=150.0)
return const Text("hello");
},
),
),
)
Expanded
//尺寸扩张为父组件剩余空间的最大值
Row(
children: [
Expanded(
flex: 2,
child: Container(height: 60, color: Colors.red),
),
Expanded(
flex: 1,
child: Container(height: 60, color: Colors.yellow),
),
],
)
ConstrainedBox
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 100,
maxHeight: 100,
),
child: Container( //ConstrainedBox限制了最大尺寸约束,所以Container的尺寸无效。
width: 200,
height: 200,
color: Colors.red,
),
)
FittedBox
FittedBox(
//BoxFit.fill:会拉伸或压缩子部件以填充 FittedBox,这可能会导致子部件的宽高比发生变化。
//BoxFit.contain:会尽可能大的展示子部件,同时保持子部件的宽高比,所以子部件的一部分可能会在 FittedBox 内部无法显示。
//BoxFit.cover:会尽可能小的展示子部件,同时保持子部件的宽高比,所以子部件的全部内容都可以在 FittedBox 内部显示,但可能无法完全填满 FittedBox。
//BoxFit.fitWidth:会拉伸或压缩子部件以填满 FittedBox 的宽度,同时保持子部件的宽高比。
//BoxFit.fitHeight:会拉伸或压缩子部件以填满 FittedBox 的高度,同时保持子部件的宽高比。
//BoxFit.none:不会对子部件进行缩放,子部件将会以其原始大小进行渲染。
//BoxFit.scaleDown:会尽可能小的展示子部件,同时保持子部件的宽高比,但子部件的大小不会超过其原始大小。
fit: BoxFit.scaleDown,
child: Text(
'一二三四一二三四',
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
)
Button
ElevatedButton
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
//设置背景色
backgroundColor: Colors.blue,
//设置边框
side: const BorderSide(width: 2, color: Colors.red),
//设置圆角
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
//设置按钮的大小
minimumSize: const Size(200, 100),
//设置按钮的padding
padding: const EdgeInsets.all(20),
//设置按钮的elevation效果
elevation: 10,
//设置按钮的阴影颜色
shadowColor: Colors.green,
),
child: const Text('ElevatedButton'),
)
TextButton
TextButton(
onPressed: () {},
child: const Text('Button'),
)
OutlinedButton
OutlinedButton(
onPressed: () {},
child: const Text('Button'),
)
Scroll
SingleChildScrollView
使超过屏幕高度的子组件可以上下滚动,防止越界。
与ListView有个不同是,不需要滚动时,用户感觉不到他的存在。
SingleChildScrollView(
child: SizedBox(
width: 800,
child: Text("Hello Flutter" * 800),
),
)
ListView
1、固定子项(与SingleChildScrollView类似,区别是即便不需要滚动时,拖动时依然可以感受到它的存在)
ListView(
children: [
FlutterLogo(),
FlutterLogo(),
FlutterLogo(),
],
)
2、带懒加载与子项复用
final _controller = ScrollController();
ListView.builder(
//ClampingScrollPhysics(), Android默认效果,拖动到边界后固定。
//BouncingScrollPhysics(), IOS默认效果,拖动到边界后反弹。
//NeverScrollableScrollPhysics(), 不可滚动,禁用ListView滚动功能。
//Android默认效果
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
itemCount: 100,
cacheExtent: 0,
//预加载区域大小。
itemExtent: 50,
//固定每个item主轴方向的尺寸,设置后item再设置大小无效。
shrinkWrap: true,
//真空包装,相当于Android的wrap_content
padding: const EdgeInsets.all(50),
//ListView内部的padding
scrollDirection: Axis.horizontal,
//设置ListView方向
itemBuilder: (context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Text("$index"),
);
},
)
带分割线
ListView.separated(
itemCount: 100,
itemBuilder: (context, index) {
return SizedBox(height: 50, child: Text("$index"));
},
separatorBuilder: (BuildContext context, int index) {
//添加分割线
return const Divider(thickness: 1);
},
)
GridView
GridView.builder(
itemCount: 100,
//固定交叉轴方向最大数量
// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount (
// crossAxisCount: 3,
// childAspectRatio: 16 / 9,
// crossAxisSpacing: 10,
// mainAxisSpacing: 10,
// ),
//固定交叉轴方向最大尺寸
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 120, //交叉轴方向尺寸
childAspectRatio: 16 / 9,
),
itemBuilder: (context, index) {
return Container(color: Colors.primaries[index % Colors.primaries.length]);
},
)
如果固定个数的item,不需要builder的话,可以使用语法糖写法,使用GildView.count或者GridView.extent
//交叉轴固定个数
GridView.count(
crossAxisCount: 4,
children: List.generate(
36,
(index) => Container(color: Colors.primaries[index % Colors.primaries.length]),
),
)
//交叉轴固定尺寸
GridView.extent(
maxCrossAxisExtent: 100,
children: List.generate(
36,
(index) => Container(color: Colors.primaries[index % Colors.primaries.length]),
),
)
四、StatefulWidget
组件渲染与刷新的原理:
Widget 、Element(State) 、RenderObject
TextField:
var controllerPhone = TextEditingController(text: "默认值");
TextField(
controller: controllerPhone,
style: const TextStyle(fontSize: 20),
obscureText: true, //密码输入样式
textAlign: TextAlign.start, //文字靠左显示
//输入框多行输入
maxLines: 1,
//键盘只能输入数字
keyboardType: TextInputType.number,
//TextCapitalization.words:每个单词首字母默认大写字母
//TextCapitalization.sentences:每句话字母默认大写字母
//TextCapitalization.characters:每个字符首字母默认大写字母
textCapitalization: TextCapitalization.none,
//输入格式
inputFormatters: [
FilteringTextInputFormatter.digitsOnly, //只允许输入整数
// LengthLimitingTextInputFormatter(11), //限制输入字数位11
// FilteringTextInputFormatter(RegExp("[0-9.]"), allow: true), //允许输入数字包括小数
// FilteringTextInputFormatter(RegExp("[a-zA-Z]"), allow: true), //允许输入字母
],
decoration: InputDecoration(
//前端外置icon
icon: Icon(Icons.person),
//后缀icon
suffixIcon: Icon(Icons.person),
//前端内置icon
prefixIcon: Icon(Icons.person),
//label提示
labelText: "用户名",
//hint
hintText: "请输入用户名",
//正常状态时的border
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(width: 1, color: Colors.orange)),
//存在焦点时的border
focusedBorder: OutlineInputBorder(borderSide: BorderSide(width: 1, color: Colors.black), borderRadius: BorderRadius.circular(30)),
//底下添加help
helperText: "我是help",
//count
counterText: textController.text.length.toString(),
//填充颜色
fillColor: Colors.blue,
filled: true,
//去掉输入框上下的间隔
isCollapsed: true,
//去边框
border: InputBorder.none,
),
//输入内容改变监听
onChanged: (text) {
debugPrint(text);
},
);
Key
Key的作用是什么?什么情况下应该使用什么Key?结合Flutter绘制原理与代码演示来讲。
1、LocalKey
只有本层组件树可见,效率较高。
- ValueKey():比较值,相当于java的 equals
- ObjectKey():比较对象内存指针是否相同,相当于java的 ==
- UniqueKey():自动生成一个随机的key
2、GlobalKey
所有组件树均可见,如非必要使用localKey。 组件设置GlobalKey后,可使用GlobalKey获取此组件的Widget、State、BuildContext。
final _globalKey = GlobalKey();
Container(
key: _globalKey,
)
//通过globalKey获取weidget
_globalKey.currentWidget as XXXWidget;
//通过globalKey获取state
_globalKey.currentState as XXXState;
//通过globalKey获取BuildContext
_globalKey.currentContext;
//通过BuildContext获取组件尺寸
RenderBox renderBox = _globalKey.currentContext?.findRenderObject() as RenderBox; //普通组件直接转换为renderBox
var height = renderBox.size.height; //获取组件高度
Form
class FormWidgetState extends State<FormWidget> {
String phone = '';
GlobalKey<FormState> formGlobalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Form(
key: formGlobalKey,
child: Column(
children: [
const SizedBox(height: 30),
TextFormField(
autovalidateMode: AutovalidateMode.disabled,
onSaved: (value) {
phone = value ?? "";
},
validator: (value) {
RegExp reg = RegExp(r'^\d{11}$');
return reg.hasMatch(value!) ? null : '请输入11位手机号码';
},
),
ElevatedButton(
onPressed: () {
var _currentForm = formGlobalKey.currentState;
if (_currentForm!.validate()) {
print("验证通过");
}
},
child: Text("按钮"),
),
],
),
);
}
}
五、路由
1、匿名路由
- 跳转新的路由
//跳转
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PageTow(),
));
//传参、接收回传
dynamic result = await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const PageTwo(name: 'Tom'),
));
//页面B接收参数、回传参数
class PageTwo extends StatelessWidget {
final String? name;
const PageTwo({super.key, this.name});
@override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
onPressed: () {
//关闭页面并回传
Navigator.of(context).pop('jerry');
},
child: const Text('返回'),
),
);
}
}
- 跳转新的路由,并关闭当前路由
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => const PageTow(),
));
- 关闭当前路由
Navigator.of(context).pop();
2、命名路由
- MaterialApp设置路由列表与初始化路由
MaterialApp(
title: 'Flutter Demo',
initialRoute: '/',
routes: {
'/': (context) => const PageOne(),
'/pageTwo': (context) => const PageTwo(),
},
);
- 跳转新的路由
//跳转新的路由
Navigator.of(context).pushNamed('/pageTwo');
//跳转并传值、接收回传
dynamic name = await Navigator.of(context).pushNamed('/pageTwo', arguments: 'Tom');
debugPrint("name: $name");
//页面2接收参数
dynamic name = ModalRoute.of(context)?.settings.arguments;
debugPrint("name: $name");
//页面2关闭页面并回传
Navigator.of(context).pop('jerry');
- 跳转新的路由,并关闭当前路由
//跳转
Navigator.of(context).pushReplacementNamed('/pageTwo');
- 关闭当前路由
Navigator.of(context).pop();
六、异步
Flutter异步原理:
EventLooper机制、EventQueue
Future
async/await语法糖
Future<String> futureFun() {
// return Future(() => "future");
return Future.error(Exception("出现错误了"));
}
ElevatedButton(
onPressed: () {
futureFun()
.then((value) => debugPrint(value)) //Futrue执行完成后调用
.catchError((exception) => debugPrint(exception.toString())) //Futrue异常后调用
.whenComplete(() => debugPrint("无论如何我都会执行"));
},
child: Text('test'),
);
为了防止回调地狱,一般使用 async 与 await 配合来使用。
Future<String> getUserId() async {
return Future.error("抛出异常");
// return Future(() => "123");
}
ElevatedButton(
onPressed: () async {
try {
String userId = await getUserId();
debugPrint(userId);
} catch (error) {
debugPrint(error.toString());
}
},
child: Text('test'),
);
FutrueBuilder
Center(
child: FutureBuilder(
future: Future.delayed(const Duration(seconds: 1), () => "content"),
// future: Future.delayed(const Duration(seconds: 1), () => throw("出错了")),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.done) {
// if (null != snapshot.data) {
// return Text(snapshot.data);
// } else {
// return Text("${snapshot.error!}");
// }
if (snapshot.hasError) {
return const Icon(Icons.error);
}
if (snapshot.hasData) {
return Text(snapshot.data);
}
}
throw "went error";
},
),
);
因为 snapshot.data 与 snapshot.error 中一定有一个不为空,可以简写一下。
Center(
child: FutureBuilder(
// future: Future.delayed(const Duration(seconds: 2), () => "content"),
future: Future.delayed(const Duration(seconds: 1), () => throw("出错了")),
//在Futrue没有完成之前,设置了data的值,这样在Futrue加载过程中,snapshot.hasData一定为ture
initialData: "initData",
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasError) {
return const Icon(Icons.error);
}
if(snapshot.hasData) {
return Text(snapshot.data);
}
return const CircularProgressIndicator();
},
),
)
Stream
final future = Future.delayed(const Duration(seconds: 1), () => 42);
final stream = Stream.periodic(const Duration(seconds: 1), (count) => 12);
@override
void initState() {
super.initState();
//Future使用then监听,延时1秒后打印出出42
future.then((value) => debugPrint("$value"));
//Stream使用Liten监听,每隔一秒打印一次12
stream.listen((event) => debugPrint("event:$event"));
//Stream执行4次以后停止
stream.take(4);
stream.takeWhile((element) => element < 4)
}
StreamBuilder
final stream = Stream.periodic(const Duration(seconds: 1), (count) => count);
StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text("未添加数据流");
case ConnectionState.waiting:
return const Text("等待数据流");
case ConnectionState.active:
if (snapshot.hasData) {
return Text("Stream content:${snapshot.data}");
} else {
return Text("Stream 出错:${snapshot.error}");
}
case ConnectionState.done:
return const Text("Stream 已经关闭");
}
},
)
Stream监听
- 默认一个Stream只能存在一个监听者。StreamBuilder与listen不能同时存在,两个Listen也不能同时存在。
- 若需要两个同时监听,可以使用.broacast方式创建,但是此方式创建,不会缓存数据。
//正常创建Stream的方式,所有点击按钮发送的事件,5秒延时过后都会被监听到。
final controller = StreamController();
//广播方式创建的Stream,点击事件不会被缓存,5秒延时过后,才开始监听。
final controller = StreamController.broadcast();
@override
void dispose() {
super.dispose();
controller.close();
}
@override
void initState() {
super.initState();
//延时5秒钟后监听Stream
Future.delayed(const Duration(seconds: 5), () {
controller.stream.listen(
(event) => debugPrint("$event"),
onError: (error) => debugPrint("$error"),
onDone: () => debugPrint("done"),
);
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: () => controller.sink.add(10), child: const Text("10")),
ElevatedButton(onPressed: () => controller.sink.addError("出错了"), child: const Text("报错了")),
ElevatedButton(onPressed: () => controller.sink.close(), child: const Text("关闭")),
]
);
}
七、动画
隐式动画
Animated...
AnimatedContainer
Container的属性变化时(比如尺寸,颜色,圆角等,仅限于自己的尺寸,对子控件无效)产生动画
AnimatedContainer(
duration: const Duration(milliseconds: 1000),
width: 300,
height: 300,
//动画曲线为完成回弹,默认为线性的Cureves.linear
curve: Curves.bounceOut,
decoration: BoxDecoration(
//渐变色
gradient: const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.white, Colors.red],
stops: [0.3, 0.4], //渐变区域
),
//加边框,
boxShadow: const [
BoxShadow(
spreadRadius: 20, //加边框
blurRadius: 20, //加阴影
)
],
//加圆角
borderRadius: BorderRadius.circular(150),
),
)
AnimatedSwitcher
两个组件之间动画切换效果,如果两个组件是相同类型的组件,一定要添加key。
AnimatedSwitcher(
duration: const Duration(milliseconds: 800),
child: isIcon ? Text("你好", key: UniqueKey()) : Text("Hello", key: UniqueKey()),
)
系统默认添加了FadeTransition,渐变动画
AnimatedSwitcher(
//系统默认添加了此builder
transitionBuilder: (child, anim) {
return FadeTransition(
opacity: anim,
child: child,
);
},
//同样可以设置为旋转动画
transitionBuilder: (child, anim) {
return RotationTransition(
turns: anim,
child: child,
);
},
//缩放动画
transitionBuilder: (child, anim) {
return ScaleTransition(
scale: anim,
child: child,
);
},
//同样支持嵌套,比如既想要旋转,又想要缩放
transitionBuilder: (child, anim) {
return RotationTransition(
turns: anim,
child: ScaleTransition(
scale: anim,
child: child,
),
);
},
duration: const Duration(milliseconds: 800),
child: isIcon ? Text("你好", key: UniqueKey(), style: const TextStyle(fontSize: 20)) : Text("Hello", key: UniqueKey(), style: const TextStyle(fontSize: 20)),
)
AnimatedOpacity
AnimatedOpacity(
duration: const Duration(milliseconds: 800),
opacity: 0.3,
child: Container(
width: 300,
height: 300,
color: Colors.blue,
),
)
AnimatedPadding
AnimatedPadding(
duration: const Duration(milliseconds: 800),
padding: EdgeInsets.all(60),
child: Container(
width: 300,
height: 300,
color: Colors.blue,
),
)
AnimatedPositioned
Stack(
children: [
AnimatedPositioned(
top: 0,
curve: Curves.linear,
duration: const Duration(milliseconds: 800),
child: Container(width: 300, height: 300, color: Colors.red),
),
],
)
TweenAnimationBuilder(自定义)
如果系统未提供默认的AnimatedXXX组件,可以使用TweenAnimation来实现,也可以实现内部组件的动画。
随意搭配其他组件
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: Tween(begin: 0.0, end: 1.0),
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Container(
width: 300,
height: 300,
color: Colors.red,
child: Text("Hello", style: TextStyle(fontSize: value * 50)),
),
);
},
)
与Transform.scale配合
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: Tween(begin: 0.5, end: 2.5),
builder: (context, value, child) {
return Transform.scale(
scale: value, //1.0为原始大小
child: const Center(child: Text("Hello", style: TextStyle(fontSize: 50))),
);
},
)
与Transform.rotate配合
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: Tween(begin: 0.0, end: 6.28),
builder: (context, value, child) {
return Transform.rotate(
angle: value, //0~6.28为一圈
child: const Center(child: Text("Hello", style: TextStyle(fontSize: 50))),
);
},
)
与Transform.translate配合
TweenAnimationBuilder(
duration: const Duration(seconds: 1),
tween: Tween(begin: 0.0, end: 1),
builder: (context, value, child) {
return Transform.translate(
offset: Offset(value * 200, value * 200),
child: const Text("Hello", style: TextStyle(fontSize: 50)),
);
},
)
Flutter出于性能考虑,只要移动出父组件的范围后,就不可接收点击事件。任何父组件(包括Positioned)。所以在TweenAnimationBuilder外单独嵌套父组件而不设置大小的话,一旦执行位移动作,很可能子组件接受不到点击事件。
显示动画
...Transition
RotationTransition
class _TestFulState extends State<TestFul> with SingleTickerProviderStateMixin {
late AnimationController controller;
@override
void initState() {
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
lowerBound: 3, //从第三圈
upperBound: 5, //转到第五圈
);
controller.addListener(() {
debugPrint("${controller.value}");
});
super.initState();
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("App Bar")),
body: RotationTransition(
turns: controller,
child: Center(child: Container(width: 300, height: 300, color: Colors.blue)),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward(); //运行一次
// controller.repeat(); //一直运行
// controller.repeat(reverse: true); //动画播放后反转播放
// controller.revise(); //动画倒序循环
// controller.reset(); //停止,回到初始状态
// controller.stop(); //停止,原地停止
},
child: const Icon(Icons.add),
),
);
}
}
FadeTransition
late AnimationController controller;
@override
void initState() {
controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
controller.addListener(() {
debugPrint("${controller.value}");
});
super.initState();
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("App Bar")),
body: FadeTransition(
opacity: controller,
child: Center(child: Container(width: 300, height: 300, color: Colors.blue)),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
controller.forward(from: 0.5);
},
child: const Icon(Icons.add),
),
);
}
ScaleTransition
ScaleTransition(
scale: controller,
child: Center(child: Container(width: 300, height: 300, color: Colors.blue)),
)
SlideTransition
SlideTransition(
//平移offset的x,y参数不是具体的坐标,是自身的倍数
position: controller.drive(Tween(begin: Offset(0, 0), end: Offset(0, 1))),
child: Container(width: 100, height: 100, color: Colors.blue),
)
chain
SlideTransition(
position: Tween(begin: const Offset(0, 0), end: const Offset(0, 1))
.chain(CurveTween(curve: Curves.bounceOut))//添加一个回弹效果的Tween
.chain(CurveTween(curve: const Interval(0.5, 1))) //等待时间过去一半后再执行动画
.animate(controller),
child: Container(width: 100, height: 100, color: Colors.blue),
)
AnimatedBuilder(自定义)
AnimatedBuilder(
animation: controller, //不可以自定义Tween,固定的0-1之间切换
builder: (BuildContext context, Widget? child) {
return Center(
child: Container(//动画改变Contariner大小
width: 300 + controller.value * 100,
height: 300,
color: Colors.blue,
child: const Center(child: Text("Hello", style: TextStyle(fontSize: 50))),//Text不需要任何动画
),
);
},
)
性能优化原理讲解:
AnimatedBuilder(
animation: controller,
builder: (BuildContext context, Widget? child) {
return Center(
child: Container(
width: 300 + controller.value * 100,
height: 300,
color: Colors.blue,
child: child,
),
);
},
child: const Center(child: Text("Hello", style: TextStyle(fontSize: 50))),
)
Painter(估计没时间讲Painter了吧?)
八、GetX
路由
1、GetX路由实现原理
Getx路由与原生路由的异同、GetX路由使用时需要注意的问题...
2、匿名路由
//原生跳转
Navigator.of(context).push(MaterialPageRoute(builder: (context){
return PageTwo();
}));
//GetX跳转
Get.to(PageTwo());
3、命名路由
static const MAIN = "/main";
static const PAGE_SECOND = "/page_second";
runApp(GetMaterialApp(
theme: ThemeData(
primarySwatch: Colors.red, //设置主题颜色
// colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), //不要设置,否则primarySwatch无效
// useMaterial3: true, //不要设置,否则primarySwatch无效
),
initialRoute: Routes.MAIN, //默认首页路由
getPages: [
GetPage(name: MAIN, page: ()=> MyApp()),
GetPage(name: PAGE_SECOND, page: ()=> PageSecond()),
], //路由列表
defaultTransition: Transition.native, //默认路由跳转动画
));
// 打开新的路由
Get.toNamed(Routes.NAME)
// 开新的路由并关闭当前路由
Get.offNamed(Routes.NAME)
// 开新的路由并取消其他所有路由
Get.offAllNamed(Routes.NAME)
状态管理
GetBuilder
1、使用
GetBuilder<ThirdController>(
init: _ctrl, //init可以不传,系统自动识别导入
builder: (ctrl) {
return Center(child: Text("你点击了${_ctrl.count}下按钮", style: const TextStyle(fontSize: 20)));
},
)
刷新
void increase() {
count++;
update();
}
2、多个GetBuilder
可通过设置id的方式,单独刷新。
Column(
children: [
GetBuilder<ThirdController>(
builder: (ctrl) {
return Text("你点击了${ctrl.count}下按钮");
},
),
GetBuilder<ThirdController>(
id: 2, //设置id单独刷新
builder: (ctrl) {
return Text("您点击了${ctrl.count}下按钮");
},
),
],
)
刷新
void increase() {
count++;
update([2]);//只刷新id为2的GetBuilder
}
3、实现原理
.......
obx
1、使用
1、定义Obs变量
///第一种方式
// var name = Rx<String>("");
// var count = Rx<int>(0);
// var height = Rx<double>(1.74);
// var itemList = Rx<List<String>>([]);
// var itemMap = Rx(Map<String, dynamic>)();
///第二种方式
// var name = RxString("");
// var count = RxInt(0);
// var height = RxDouble(1.74);
// var itemList = RxList<String>([]);
// var itemMap = RxMap<String, dynamic>();
// ///推荐方式(.obs扩展函数)
// var name = "".obs;
// var count = 0.obs;
// var height = 1.74.obs;
// var itemList = <String>[].obs;
// var itemMap = <String, dynamic>{}.obs;
// //自定义类
// var entity = XXXEntity().obs;
RxInt count = 0.obs;
var userEntity = UserEntity(123, "李雷").obs;
2、使用Obx监听变量的值。
Obx(() {
return Column(
children: [
Text("您点击了${count.value}下按钮", style: const TextStyle(fontSize: 23)),
Text("用户的名字是:${userEntity.value.name}", style: const TextStyle(fontSize: 23)),
],
);
})
3、改变变量的值。
//参考源码可以看到,RxInt重载了 + 和 - 运算符,自己取出了value进行了计算。
count.value++;
//更新对象中的值时,需要使用update。
userEntity.update((val) {
if(val?.name == "韩梅梅") {
val?.name = "李雷";
} else {
val?.name = "韩梅梅";
}
});
//或者使用refresh()
userEntity.name = "李雷";
userEntity.refresh
2、刷新原理
......
3、使用场景
//Useful for simple local states, like toggles, visibility, themes,
国际化
1、使用步骤
1、GetMaterialApp中配置国际化
GetMaterialApp(
title: 'Flutter Demo',
translations: Locales(), //国际化配置文件
locale: const Locale('zh', 'CN'), //设置默认语音,不设置为系统当前语言
fallbackLocale: const Locale('zh', 'CN'), //配置错误的情况下使用的语言
);
2、定义语言配置文件,以英语汉语为例。
final Map<String, String> enUS = {
"home.set_language": "Set Language",
"home.chinese": "Chinese",
"home.english": "English",
};
final Map<String, String> zhCn = {
"home.set_language": "设置语言",
"home.chinese": "中文",
"home.english": "英文",
};
class Locales extends Translations {
@override
Map<String, Map<String, String>> get keys => {
"zh_cn": zhCn,
"en_US": enUS,
};
}
3、切换语言
Get.updateLocale(const Locale('zh', 'CN'));
Get.updateLocale(const Locale('en', 'US'));
2、实现原理
.........
九、作业
0、基础配置
1、接口BaseUrl:apifoxmock.com/m1/2234660-…
2、应用主题色:#FF018786。
1、登录
1、用例
- 用户名或者密码未填写时,点击登录按钮弹出Toast提示。
- 用户名密码全部填入,点击按钮,弹出全屏进度条。登录成功跳转前关闭进度条。
- 登录成功后,用户信息保存到本地或者传递到后面的页面。
2、预览图
3、接口
说明:用户名为:user_01,密码为:123456。
url:/login
请求方式:post
body参数:
{
"username": "user_01",
"password": "123456"
}
正常响应结果:
{
"error": 0,
"data": {
"id": 1,
"token": "f89ssd9f8sdf98sdfsd9fs",
"username": "user_01",
"password": "123456",
"nickname": "太空先锋",
"imageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"gender": 0,
"birthday": "2000-01-01",
"email": "space@163.com",
"phone": "18566469874",
"address": [
"江苏",
"苏州"
]
},
"message": "登录成功"
}
2、文章列表
1、用例
- 头像为圆角图片。
- 点击右上角我的跳转到“我的”页面。
- 具体的文章内容限制最多显示2行,超出部分显示"..."。
- 预览图最多显示6个,超过的不显示。
- 点击列表item跳转到详情。
2、预览图
3、接口
说明:userId参数为1时可以正确获取数据: apifoxmock.com/m1/2234660-…
url:/list
请求方式:get
parameter参数:userId = 1
正常响应结果:
{
"error": 0,
"data": [
{
"id": 1,
"userId": 1,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行不是一个人或某群人的事,这是人类在其发展中合乎规律的历史进程。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
{
"id": 2,
"userId": 1,
"userNickname": "星辰大海",
"userImageUrl": "https://img.51miz.com/preview/element/00/01/16/49/E-1164929-803B938E.jpg",
"timestamp": "2020-08-11 01:09:59",
"text": "我们的征途是星辰大海 !🚀🚀🚀我们的征途是星辰大海 !🚀🚀🚀我们的征途是星辰大海 !🚀🚀🚀我们的征途是星辰大海 !🚀🚀🚀我们的征途是星辰大海 !🚀🚀🚀我们的征途是星辰大海 !🚀🚀🚀",
"images": [
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 69,
"likes": 33
},
{
"id": 3,
"userId": 1,
"userNickname": "超越",
"userImageUrl": "https://img.zcool.cn/community/01fb095f603d0511013e31874833b6.jpg@1280w_1l_2o_100sh.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇航是国家综合国力的重要体现,对国家的长远发展和民族未来具有深远影响。",
"images": [
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
{
"id": 4,
"userId": 1,
"userNickname": "我爱地球",
"userImageUrl": "https://www.keaitupian.cn/cjpic/frombd/0/253/811481000/1296899053.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行对增强民族自豪感和凝聚力有着重要作用,每一次成功的宇航任务都会激发国民的自豪感和信心。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb"
],
"readings": 8,
"likes": 16
},
{
"id": 5,
"userId": 1,
"userNickname": "勇往直前",
"userImageUrl": "https://img95.699pic.com/photo/40007/5788.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇指天际,航指航行,也就是航行于天际,寓意一飞冲天。宇航也指宇宙航行。在大气层外太阳系内的空间航行叫做航天。",
"images": [
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
{
"id": 6,
"userId": 1,
"userNickname": "新年快乐",
"userImageUrl": "https://bpic.588ku.com/element_origin_min_pic/19/03/07/4b2699b6b1461e1f3290e74af5f5e26b.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇航名字的含义可以根据不同的解读方式而有不同的意义。宇航一词可以理解为宇宙航行或航天事业,它代表着对宇宙和太空的探索精神和科学探索的冒险精神。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
{
"id": 7,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇指天际,航指航行,也就是航行于天际,寓意一飞冲天。作为亚洲第一航天强国、航天大国,中国宇航工业的发展不仅有利于自身的综合国力的不断提升,更有利于世界天缘政治格局的进一步优化。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
{
"id": 8,
"userId": 1,
"userNickname": "我爱地球",
"userImageUrl": "https://www.keaitupian.cn/cjpic/frombd/0/253/811481000/1296899053.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行不是一个人或某群人的事,这是人类在其发展中合乎规律的历史进程。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
{
"id": 9,
"userId": 1,
"userNickname": "我要飞得更高",
"userImageUrl": "https://bpic.588ku.com/element_water_img/19/10/13/ca8d4ddfd0fcb7d7231e80fc73ef3823.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "那么,载人航天到底意义何在呢?世界载人航天的发展 上世纪,世界经历了太长时间炮火的洗礼,人们都渴望能够进入一个正常发展的时期。",
"images": [
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg"
],
"readings": 8,
"likes": 16
},
{
"id": 10,
"userId": 1,
"userNickname": "全球⽂明",
"userImageUrl": "https://www.manpingou.com/uploads/allimg/170223/28-1F223105925311.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "载⼈航天的意义和价值 历史上,郑和下西洋到远洋航海技术成熟,推动了世界贸易的发展、世界市场的开辟和近代科学的⼀系列成就,开始了⼀个全球⽂明的时代。",
"images": [
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
}
],
"message": "成功"
}
3、我的
1、用例
- 顶部个人信息从本地读取或者从登录页面获取。
- 点击我的文章列表,跳转到详情页。
- 具体的文章内容限制最多显示2行,超出部分显示"..."。
2、预览图
3、接口
说明:userId参数为1时可以正确获取数据:
url:/myList
请求方式:get
parameter参数:userId = 1
正常响应结果:
{
"error": 0,
"data": [
{
"id": 1,
"userId": 1,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行不是一个人或某群人的事,这是人类在其发展中合乎规律的历史进程。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 22
},
{
"id": 7,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇指天际,航指航行,也就是航行于天际,寓意一飞冲天。作为亚洲第一航天强国、航天大国,中国宇航工业的发展不仅有利于自身的综合国力的不断提升,更有利于世界天缘政治格局的进一步优化。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 16,
"likes": 0
}
],
"message": "成功"
}
4、详情
1、用例
- 文章内容全部显示,如果是从“文章列表”进入详情,文章内容为只读状态。如果从“我的”页面进入详情,文章内容为可编辑状态。
- 如果从“我的”页面进入详情,右上角有一个确定按钮。
- 图片不限制数量,全部展示。
- 如果从“我的”页面进入详情,修改完文章内容后。点击右上角“确定”按钮,调用保存接口,成功后关闭当前页面,同时调用接口刷新“文章列表”、“我的”两个页面。
注意:接口为mock,内容是写死的,并不会真的更新内容。所以更新后调用列表以及详情,文章内容没有变化是正常的。只需要完成刷新操作即可。
2、预览图
3、接口
1、详情:
说明:apifoxmock.com/m1/2234660-…
url:/detail
请求方式:get
parameter参数:id = 1 ~ 10
正常响应结果:
{
"error": 0,
"data": {
"id": 1,
"userId": 1,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行不是一个人或某群人的事,这是人类在其发展中合乎规律的历史进程。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
"message": "登录成功"
}
2、更新:
url:/update
请求方式:post
body参数:
{
"id": 1,
"userId": 1,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行不是一个人或某群人的事,这是人类在其发展中合乎规律的历史进程。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
}
正常响应结果:
{
"error": 0,
"data": {
"id": 1,
"userId": 1,
"userNickname": "太空先锋",
"userImageUrl": "https://img95.699pic.com/photo/40034/7177.jpg_wh860.jpg",
"timestamp": "1980-05-10 04:32:59",
"text": "宇宙航行不是一个人或某群人的事,这是人类在其发展中合乎规律的历史进程。",
"images": [
"http://n.sinaimg.cn/spider2021617/84/w1014h670/20210617/687b-krpikqf6811833.jpg",
"https://photo.tuchong.com/6571731/f/1154097679.jpg",
"https://img.xianjichina.com/editer/20201230/image/17d28d1d5958143ef43c1648b1a4c24a.jpg",
"http://5b0988e595225.cdn.sohucs.com/images/20200427/1588f791e0b54a0182ac0103990d2469.jpeg",
"https://www.kepuchina.cn/more/202010/W020201005592055625133.jpg",
"https://pics6.baidu.com/feed/37d3d539b6003af3e95fc85b2c2389561038b633.png@f_auto?token=40225699bd577ae408613509f2239ceb",
"https://img.guanhai.com.cn/a/10001/202205/845de20e3ffeb3d6ddd80051f4111dc6.jpeg",
"https://d.ifengimg.com/w951_h521_q90_webp/x0.ifengimg.com/res/2021/884EAC0AC507E34A52A88B0E358AA80BD4DFAFBB_size90_w951_h521.jpeg",
"http://www.beidou.gov.cn/zy/bdsp/202006/W020200623412798276654.png"
],
"readings": 8,
"likes": 16
},
"message": "登录成功"
}