FLutter 踩坑笔记
Android 集成部分
Flutter 编写部分
- MaterialButton RaisedButton 控件内部与child之间存在padding ,没有属性可以去掉。
- 使用最新版sdk(1.22.0 以及以上)中的TextButton
- 在Button 外面使用SizeBox等可以设置size的控件,设置size可以缩减 padding
- 在 State中声明的Color 类型的属性,要么直接赋值,要么使用 final 进行修饰,不然会报错。
- 在 main.dart 中 使用 runZonedGuarded 可以 捕获异常和输出,代码如下
bool get isInDebugMode {
bool inDebugMode = false;
return inDebugMode;
}
Future<Null> main() async {
FlutterError.onError = (FlutterErrorDetails details) async {
if (isInDebugMode) {
// In development mode simply print to console.
FlutterError.dumpErrorToConsole(details);
} else {
// In production mode report to the application zone to report to
// Sentry.
Zone.current.handleUncaughtError(details.exception, details.stack);
}
};
//自定义错误提示页面
ErrorWidget.builder = (FlutterErrorDetails flutterErrorDetails) {
return Scaffold(
appBar: AppBar(
title: Text("错误页面"),
),
body: Center(
child: Text("Custom Error Widget"),
));
};
runZonedGuarded(
() => runApp(MyApp()),
zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {collectLog(line); // 捕获所有的print
},
),
(error, stack) {
var details = makeDetails(obj, stack);
reportErrorAndLog(details);
},
);
}
- flutter app 的正常模式和暗黑模式风格的定义是在MaterialApp中 theme 和 darkTheme 指定的。ThemeData中的brightness 属性 指定是正常模式还是暗黑模式
- 使用 dart语言中extension on 语法 可以扩展系统中的类的属性,比如我们可以扩展 ThemeData 来添加项目中使用到的颜色,demo如下:
extension CustomThemeData on ThemeData {
Color get primaryBlue => brightness == Brightness.dark ? Color(0xFF228AEE) : Color(0xFF1880EE);
// ignore: non_constant_identifier_names
Color get primaryBlue_outline => brightness == Brightness.dark ? Color(0xFF228AEE) : Color(0xFF1880EE);
}
- ThemeData中涉及到很多系统中使用到的颜色,和样式,这些内容的修改可以影响到所有使用的页面和控件(自定义过颜色样式的除外)。修改时需要谨慎。
- Platform 类中涉及到了app运行平台的信息,如:运行app的系统是android还是ios,运行环境(Map<String, String> environment)等内容
- dart:ui 包下面的Window对象 包含了app的默认route,设备的PixelRatio等信息,可以之间在 state或 Widget 的方法中之间使用window这个对象
- 可以使用 TextPainter 来提前获取Text中写入字符串时的size,Text 中的style,必须和TextSpan中的style一致,代码如下:
TextPainter painter = TextPainter(text: TextSpan(text: v, style: fieldTextStyle), textDirection: TextDirection.ltr);
painter.layout();
print(painter.width);
- 使用 GlobalKey 的方式获取控件的size,需要在绘制完界面后才有效代码如下:
final GlobalKey _textFieldKey = GlobalKey();
Text("aaa", key: _textFieldKey,);
double inputWidth = _textFieldKey.currentContext.size.width;
-
获取屏幕Size,使用
MediaQuery.of(context).size
-
flutter 中图片的使用需要在pubspec.yaml中 声明,代码如下:
图片放在和pubspec.yaml同级的iamges文件夹中
flutter:
uses-material-design: true
assets:
- images/
//图片使用时的代码,使用在pubspec.yaml声明时的图片路径+图片的名字
//Contanier 的 decoration中可以设置image decoration ,使用 DecorationImage对象,可以直接加载 asserts 中 的image 资源
Container(
……
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("images/warnIcon.png")),
) )
)
- 在 自定义的StatefulWidget中若有需要动态刷新的数据或子widget ,请在build()方法中使用widet.xx 来获取最新的值
- notification 用于 子widget 给父widget 传递数据,使用方式如下:
- 继承 Notification 类,创建 notification 数据类,demo 如下:
class BannerNotification extends Notification {
BannerNotification(this.isShow,
{
this.type,
this.tip,
……
});
String tip;
int type;
……
}```
- 在父widget的build中 return NotificationListener 对象,代码如下:
```dart
@override
Widget build(BuildContext context) {
return NotificationListener<BannerNotification>(
onNotification: (notification) {
//处理逻辑 的代码
……
return ture;
},
child: Text("aaa")
);
}
}
- 子widget中使用 notification ,代码如下:
notification.dispatch(context);
- dispatch(context)中的context必须是在子widget的context,若在如点击事件等callback 方法中使用时,可以在子widget的外层加上Builder,确保context层级小于父widget,代码如下:
Builder(
builder: (context) {
return MaterialButton(onPressed: (){
return MaterialButton(onPressed: (){
……
},);
},);
},
)
- 在 base body 这种 全局式的 父widget ,可以在需要使用notification的子widget的外层加上 Builder(),然后将context赋值给static 修饰的全局 context对象,在定义的static 方法中集中调用 ,使用 notification.dispatch(context);,代码如下:
class BaseBody extends StatelessWidget {
final Widget child;
static BuildContext _buildContext;
static void switchBanner(BannerNotification notification) {
notification.dispatch(_buildContext);
}
@override
Widget build(BuildContext context) {
return NotificationListener<BannerdNotification>(
onNotification: (notification) {
……
//逻辑代码
return true;
},
child: Stack(
children: <Widget>[
Builder(
builder: (context) {
BaseBody._buildContext = context;
return widget.child;
},
),
],
),
);
}
}
- 自定义 widget中 使用了AnimationController ,Timer 等需要dispose的widget时,若在 state 的dispose()方法中调用 dispose方法时,要在super.dispose();之前调用,因为有时会出现controller 在页面退出后没有dispose的问题,代码如下:
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
//若还是出现controller 没有dispose ,可以在deactivate 中执行 controller?.dispose();
@override
void deactivate() {
_controller?.dispose();
super.deactivate();
}
-
完全自定义 TextField中的contentPadding,代码如下:
TextField( scrollPadding: EdgeInsets.zero,//默认是20,现在改成0 decoration: InputDecoration( contentPadding: EdgeInsets.only(left: 12.px, right: 2.px, top: 12.px, bottom: 12.px),//自定义 contentPadding isDense: true,//为true时 ,TextField的vertical方向没有space,默认是false。 ), )
-
TextField 中 keyboardType 属性中 TextInputType.visiblePassword 为设置键盘模式为输入密码模式,但是 输入的字符是可见的,若想不可见,可以设置 TextField的obscureText 属性为 ture,则输入字符不可见。代码如下:
TextField(
keyboardType: TextInputType.visiblePassword,
autofocus: true,
……
),
- flutter中的动画,一般动画使用代码如下,
//动画的控制类,如动画的播放,暂停,反转,重复。
_controller = AnimationController(
duration: Duration(milliseconds: 250), vsync: provider)
..addStatusListener((status) {
//动画状态的监听,completed,dissmiss,forward,reverse
}
})..addListener(() {
//在此获取动画的当前值。
var a = _controller.value; //a的值为0到1之间
var b = _animation.value;/ /b的值在Tween定义的begin与end之间。
});
//CurvedAnimation 定义运动变化的速率,curve: 决定动画的差值器
var curved = CurvedAnimation(parent: _bannerController, curve: Curves.easeOut)
// Tween 中定义动画的取值区间,数值可以是任意类型,如 Color,Offset,double等。
_animation = Tween(begin: Offset(0, -1), end: Offset(0, 0)).animate(curved);
}
- 使用TweenSequence和 TweenSequenceItem 可以定义分阶段的连续动画,代码如下:
//weight:执行时长占总时长的权重
//TweenSequenceItem : 定义不同的动画
Animation animation=TweenSequence([
TweenSequenceItem(tween: Tween(begin: 0,end: 100),weight: 20),
TweenSequenceItem(tween: CurveTween(curve:Curves.easeIn ),weight: 80)
]).animate( _controller);
- ShaderMask 设置 widget的颜色渐变图层
-
Gradient颜色渐变的方式:LinearGradient(线形变换);RadialGradient(放射状变化);SweepGradient(扇形变化)
-
blendMode:颜色混合模式
-
tileMode:指定在begin,end之外的区域颜色如何渲染,repeated:重复使用区域之内的渲染颜色;clamp:按照接近的区域内的颜色;mirror:以镜像模式重复区域内的渲染颜色
-
demo如下:
-
ShaderMask(
shaderCallback: (Rect bounds) {
return RadialGradient(
center: Alignment.bottomCenter,
radius: _gradientValue,
colors: <Color>[_iconColor, _loadingColor],
stops: [_gradientValue, 1],
tileMode: TileMode.repeated,
).createShader(bounds);
},
blendMode: BlendMode.srcATop,
child: Icon(
_icon,
size: _iconSize,
),
)
ShaderMask(
shaderCallback: (Rect bounds) {
return SweepGradient(
center: Alignment.bottomCenter,
startAngle: 0,
endAngle: pi,
tileMode: TileMode.clamp,
colors: <Color>[_iconColor, _loadingColor],
stops: [_gradientValue, 1],
).createShader(bounds);
},
blendMode: BlendMode.dstOver,
child: Icon(
_icon,
size: _iconSize,
),
)
LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
tileMode: TileMode.mirror,
colors: <Color>[_iconColor, _loadingColor],
stops: [_gradientValue, 1],
).createShader(bounds);
},
blendMode: BlendMode.dstATop,
child: Icon(
_icon,
size: _iconSize,
),
);
-
Container 中设置阴影效果
-
的style 有 outer,inner,normal,solid
-
demo
Container( …… decoration: BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Color(0xFF000000),//阴影的颜色 blurRadius: 10.px,//阴影的半径 offset: Offset(2.px, 2.px),//shadow 对于 box的偏移量 spreadRadius: 10.px,//box扩展的大小 ), ], ), )
-
-
box shadow的原理
//ui.shadow
static double convertRadiusToSigma(double radius) {
return radius * 0.57735 + 0.5;//阴影的逻辑像素计算公式
}
//根据传入的半径,计算阴影的逻辑像素
double get blurSigma => convertRadiusToSigma(blurRadius);
Paint toPaint() {
return Paint()
..color = color
//使用 MaskFilter.blur 实现paint的阴影效果
..