flutter开发过程中的一些小技巧和知识点

/// 自定义的用户协议组件。

class UserAgreement extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    // 文本(`Text`)组件,是一系列具有单一样式的文本。
    // 文本.丰富(`Text.rich`)构造函数,则是使用文字跨度(`TextSpan`)组件创建文本。
    return Text.rich(
      // 文字跨度(`TextSpan`)组件,不可变的文本范围。
      TextSpan(
        // 文本(`text`)属性,跨度中包含的文本。
        text: '登录即同意',
        // 样式(`style`)属性,应用于文本和子组件的样式。
        style: _lowProfileStyle,
        children: [
          TextSpan(
            // 识别(`recognizer`)属性,一个手势识别器,它将接收触及此文本范围的事件。
            // 手势(`gestures`)库的点击手势识别器(`TapGestureRecognizer`)类,识别点击手势。
            recognizer: TapGestureRecognizer()..onTap = () {
              print('点击了“服务条款”');
            },
            text: '“服务条款”',
            style: _highProfileStyle,
          ),
          TextSpan(
            text: '和',
            style: _lowProfileStyle,
          ),
          TextSpan(
            recognizer: TapGestureRecognizer()..onTap = () {
              print('点击了“隐私政策”');
            },
            text: '“隐私政策”',
            style: _highProfileStyle,
          ),
        ],
      ),
    );
  }
}

解决Flutter ListView 或者SingleChildScrollView 嵌套 ListView.builder滑动冲突

原因
SingleChildScrollView 和 ListView 都有滚动属性physics 他们默认是都是可以滚动的,
ListView 嵌套 ListView.builder 需要后者shrinkWrap = true,不然报错;
解决方式
禁用 ListView 的滚动physics 保留 SingleChildScrollView 的滚动
Listview 执行 physics 属性 new NeverScrollableScrollPhysics(), //禁用滚动事件

new ListView.builder(
              shrinkWrap: true,
              physics: new NeverScrollableScrollPhysics(),
)

您可以使用Flutter团队使用相机插件进行颤振.

https://pub.dartlang.org/packages/camera

然后将您的图像和摄像机视图定位在Stack Widget中,如下所示:

return new Stack(
  alignment: FractionalOffset.center,
  children: <Widget>[
    new Positioned.fill(
      child: new AspectRatio(
          aspectRatio: controller.value.aspectRatio,
          child: new CameraPreview(controller)),
    ),
    new Positioned.fill(
      child: new Opacity(
        opacity: 0.3,
        child: new Image.network(
          'https://picsum.photos/3000/4000',
          fit: BoxFit.fill,
        ),
      ),
    ),
  ],
);
TextField(
          inputFormatters: [
            WhitelistingTextInputFormatter(RegExp("[a-zA-Z]")),//只允许输入字母
          ],
   ),
    TextField(
          inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],//只允许输入数字
   ),
    TextField(
                          inputFormatters: [
                            WhitelistingTextInputFormatter(RegExp("[0-9.]")),//只允许输入小数
                          ],
    )

flutter listView顶部空白
ListView头部有一段空白区域,是因为当ListView没有和AppBar一起使用时,头部会有一个padding,为了去掉padding,可以使用MediaQuery.removePadding:

@override
Widget build(BuildContext context){
    return MediaQuery.removePadding(
        removeTop: true,
        context: context,
        child: ListView.separated(
            itemCount: hotMovies.length,
            itemBuilder: (context, index){
                return HotMovieItemWidget(hotMovies[index]);
            },
            separatorBuilder: (context, index){
              return Divider(
                height: 1,
                color: Colors.black26,
              );  
            },
        ),
    );
}

ListView无法充满全面,顶部会有一个导航栏宽度的缝隙,应该是自适应屏幕造成,这时只需将ListView的padding属性,设置为:EdgeInsets.only(top: 0) 即可解决问题。

Flutter 获取某个控件的坐标

1.首先先需要对控件进行渲染

初始化GlobalKey :GlobalKey anchorKey = GlobalKey();

2.在需要测量的控件的下面添加key:

child: Text("点击弹出悬浮窗",
  style: TextStyle(fontSize: 20),
  key: anchorKey
),

3.获取控件的坐标:

RenderBox renderBox = anchorKey.currentContext.findRenderObject();
var offset =  renderBox.localToGlobal(Offset.zero);

控件的横坐标:offset.dx

控件的纵坐标:offset.dy

如果想获得控件正下方的坐标:

 RenderBox renderBox = anchorKey.currentContext.findRenderObject();
 var offset =  renderBox.localToGlobal(Offset(0.0, renderBox.size.height));

控件下方的横坐标:offset.dx

控件下方的纵坐标:offset.dy

////                                var offset =  renderBox.localToGlobal(Offset(0.0, renderBox.size.height));
//                                var offset =  renderBox.localToGlobal(Offset(0.0, 0.0));
//                                RenderBox getBox = context.findRenderObject();

GestureDetector(
onTapUp: (TapUpDetails tapUp) {
// print(" onTapUp ============= {tapUp.globalPosition}"); RenderBox renderBox = anchorKey.currentContext.findRenderObject(); //这个坐标是全屏幕的坐标,而绘制的坐标是widget内的坐标,所以我们需要将这个坐标转换为我们widget内的坐标系 var localOffset = renderBox.globalToLocal(tapUp.globalPosition); // print(" onTapUp 2 =============dx ={localOffset.dx} dy ={localOffset.dy}"); _mineProductionList.forEach((item) { MineProduction result = item.isReaped(localOffset.dx, localOffset.dy); if (result != null){ result.isValidValue = false; toRemove.add(result); }; } ); setState(() { // result.isValidValue = false // _mineProductionList.forEach((item) { // print(" onTapUp removeWhere ============={toRemove.contains(item)}");
// if(toRemove.contains(item)){
// item.isValidValue = false;
// }
// });
this._mineProductionList.removeWhere((value) {
// print(" onTapUp removeWhere 2 ============= {this.toRemove.contains(value)}{value.toString()}");
return this.toRemove.contains(value);
});
// print(" onTapUp remove ============= ${_mineProductionList.length}");
_minePainter = MinePainter( mineProductionList: this._mineProductionList);
});
},
child: CustomPaint(
painter: _minePainter,
size: Size(double.infinity, double.infinity),
),
),



# [No internet in Android Virtual device [duplicate]](https://stackoverflow.com/questions/54622647/no-internet-in-android-virtual-device)

Maybe you have DNS address problem. Try these steps on MacOs:

Open "System Preferences"
Click on "Network"
Select the network which your computer is connected and click on "Advanced"
Select "DNS", Select the "+" button, type "8.8.8.8"
Select "Ok" and "Apply"
After that close the emulator and start it again.



我们需要实现组件绑定观察者(WidgetsBindingObserver)抽象类,使用组件(Widgets)图层绑定注册的类的接口,我们会覆盖它的App生命周期状态发生改变(didChangeAppLifecycleState)方法,其返回值类型是应用生命周期状态(AppLifecycleState)类。

应用生命周期状态(AppLifecycleState)类有几个常量,分别是:等待(AppLifecycleState.inactive)、暂停(AppLifecycleState. paused)和恢复(AppLifecycleState. resumed)。


class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
@override
void initState() {
// 在当前页面放一个观察者。
WidgetsBinding.instance.addObserver(this);
super.initState();
}

@override
void dispose() {
// 移除当前页面的观察者。
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// 当App生命周期状态为恢复时。
if (state == AppLifecycleState.resumed) {
getClipboardContents();
}
}

/// 使用异步调用获取系统剪贴板的返回值。
getClipboardContents() async {
// 访问剪贴板的内容。
ClipboardData clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
// 剪贴板不为空时。
if (clipboardData != null && clipboardData.text.trim() != '') {
String _name = clipboardData.text.trim();
// 淘口令的正则表达式,能判断类似“¥123456¥”的文本。
if (RegExp(r'[\uffe5]+.+[\uffe5]').hasMatch(_name)) {
// 处理淘口令的业务逻辑。
showDialog<Null>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text('淘口令'),
content: Text(_name),
);
},
);
}
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo 主页'),
),
);
}



在Flutter中,事件流是“向上”传递的,而状态流是“向下”传递的
),重定向这一流程的共同父元素是State。


[https://book.flutterchina.club/](https://book.flutterchina.club/)

To maximize container's width, set its width to MediaQuery.of(context).size.width
double.infinity有时候会报错

MediaQuery.of(context).size.height*0.35
Iterable<Widget> listTitles=items.map((String item){//将items的内容设置给Adapter
      return buildListTile(context,item);
    });

最后将Adapter设置给ListVIew:

new ListView(
        children: listTitles.toList(),//添加ListView控件
      )

在StatefulWidget中State通过widget.获取StatefulWidget里面的全局变量
项目结构:

class test extends StatefulWidget{ 
   var title;   
   _MainUi createState() =>new _MainUi();
}
class _MainUi extends State<test> {    
    @override    
    Widgetbuild(BuildContext context) {            
        widget.title;//获取全局变量   
    }
}
// Binary data
List<int> postData = <int>[...];
await dio.post(
  url,
  data: Stream.fromIterable(postData.map((e) => [e])), //create a Stream<List<int>>
  options: Options(
    headers: {
      HttpHeaders.contentLengthHeader: postData.length, // set content-length
    },
  ),
);

意思就是dio实例就是指new 的一个dio对象;拦截器是应该在发送请求之前就设置好的,不应该在请求的同时去修改。另外:

不建议动态改变baseUrl,一般一个dio实例对应一个baseUrl,如果要用该实例去发送其它域的请求可以在url参数中写完整链接,这样便会忽略baseUrl, 详情可以看example下面的示例。如:

  var dio = Dio();
  dio.options.baseUrl = "http://www.dtworkroom.com/doris/1/2.0.0/";
  // 下面使用完整域名访问会忽略baseUrl
  Response response = await dio.get("https://www.google.com/");
  print(response.data);

如果非要动态改baseUrl,可以通过dio.options.baseUrl直接修改,没必要通过拦截器去设置。

GlobalKey _scaffold = Global Key();
Scaffold(
  key: _scaffold
);
...
showDialog(context: _scaffold.currentContext)

[flutter] 手机拍摄的照片base64编码后显示方向不正确

flutter读取照片然后压缩并base64编码时发现了这个问题.
后来了解了一下:
手机拍摄的照片会把拍摄的相机信息存到exif信息里面
所以读取手机拍摄的照片后要先从exif信息里面读取下拍摄时的手机角度,然后旋转下照片才能得到正常照片
我的解决方法:
https://pub.dartlang.org/packages/exifdart
pubspec.yml的dependencies节点添加:

dependencies:  exifdart: ^0.7.0+3

这是一个用来读取exif信息的库

使用方法:

import 'package:exifdart/exifdart.dart'; /// 获取一个照片要旋转多少度才是正常方向Future<int> getImageRotateAngular(List<int> bytes) async {  Map<String, dynamic> tags = await readExif(MemoryBlobReader(bytes));  var orientation = tags['Orientation']; //获取该照片的拍摄方向  switch (orientation) {    case 3:      return 180;    case 6:      return 90;    case 8:      return -90;    default:      return 0;  }}

获取到旋转方向后把图片旋转下再压缩编码就可以了!

通过 ClipRect 的 clipper 属性,我们可以对显示区域进行限制,接下来自定义一个

CustomClipper
class CustomRect extends CustomClipper<Rect> {
  @override
  Rect getClip(Size size) {
    Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
    return rect;
  }

  @override
  bool shouldReclip(CustomRect oldClipper) {
    return false;
  }
}

这样,我们就可以把显示内容限制在 rect 的范围内

等待加载image后更新布局

class _MyHomePageState extends State<MyHomePage> {
  ui.Image image;
  bool isImageloaded = false;
  void initState() {
    super.initState();
    init();
  }

  Future <Null> init() async {
    final ByteData data = await rootBundle.load('images/lake.jpg');
    image = await loadImage(new Uint8List.view(data.buffer));
  }

  Future<ui.Image> loadImage(List<int> img) async {
    final Completer<ui.Image> completer = new Completer();
    ui.decodeImageFromList(img, (ui.Image img) {
      setState(() {
        isImageloaded = true;
      });
      return completer.complete(img);
    });
    return completer.future;
  }

  Widget _buildImage() {
    if (this.isImageloaded) {
      return new CustomPaint(
          painter: new ImageEditor(image: image),
        );
    } else {
      return new Center(child: new Text('loading'));
    }
  }
  @override
  Widget build(BuildContext context) {

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Container(
        child: _buildImage(),
      )
    );
  }
}

运行flutter的时候显示警告
Waiting for another flutter command to release the startup lock
复制代码当项目异常关闭,或者android studio用任务管理器强制关闭,下次启动就会出现上面的一行话,
此时需要打开 flutter/bin/cache/lockfile,删除就行了
或者直接用下面的命令:rm ./flutter/bin/cache/lockfile
有时候注意自己的目录是否不一样,我的rm ./flutter/flutter/bin/cache/lockfile

如果上述方法还是不行,则删除 flutter SDK中 bin/cache 文件夹,然后在cmd命令行执行flutter upgrade

import 'package:flutter/services.dart';
  
 // 强制只能竖屏
  SystemChrome.setPreferredOrientations(
      [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);

iOS白屏

<key>io.flutter.embedded_views_preview</key>
    <string>YES</string>

如果需要访问 HTTP 网页,还需要添加以下。
    <key>NSAppTransportSecurity</key>
    <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    </dict>

Flutter 有两种可选参数,一种是基于名称,一种是基于位置

{}是基于名称的,[]是基于位置的

1.基于名称

Person(String name,{String gender,int number}){
 
}
 
//调用
Person('zhangsan')//可不传
Person('zhangsan',gender:'man',number:20);

2.基于位置

Person(String name,[String gender,int number]){
}
 
//调用
Person('zhangsan')//可不传
Person('zhangsan','man',20);//必须按照顺序传递

Flutter闭包

  makeFunction(int xx){
    print(xx);
     int callback(yy){
 
       print(yy);
      return xx + yy;
    };
 
 
     return callback;
  }
 
 
//调用
int result = makeFunction(110)(10);
 
//result = 120

其实就是函数嵌套一个返回的函数

Flutter函数做参数传递

 void bbb(){
    print('bbbbb');
  }
 
 
   aaa(int num,Function func){
    print(num);
 
    if(num == 111) {
      bbb();
    }
    
  }
 
//调用
aaa(111,bbb);
 
//或者
aaa(111, (){
     print('kkkk');
   });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容