Flutter 常见问题总结

Posted 王睿丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter 常见问题总结相关的知识,希望对你有一定的参考价值。

喜欢记得点个赞哟,我是王睿,很高兴认识大家!

1、内容简介

本文主要总结了开发时遇到的Bug、报错、开发时遇到的技术难题等。同时,让大家能够快速解决自己遇到额问题和避免采坑。如果对您有帮助,希望在文章的末尾能点个赞,谢谢!

2、使用Column等容器包裹ListView报错的问题

报错如下所示:

I/flutter ( 4625): EXCEPTION CAUGHT BY RENDERING LIBRARY
I/flutter ( 4625): The following assertion was thrown during performResize():
I/flutter ( 4625): Vertical viewport was given unbounded height.
I/flutter ( 4625): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter ( 4625): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter ( 4625): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter ( 4625): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter ( 4625): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter ( 4625): instead. Otherwise, consider using the “shrinkWrap” property (or a ShrinkWrappingViewport) to size
I/flutter ( 4625): the height of the viewport to the sum of the heights of its children.

解决方案:

使用扩展小部件Expanded包裹ListView

new Expanded(
child: new ListView(
          .....
          )
);

这告诉ListView尽可能地获取宽度和高度。

3、Navigator operation requested … does not include a Navigator.

原错误: Navigator operation requested with a context that does not include a Navigator.

flutter 最容易报的一个错误就是does not include,因为其思想是组合

这种情况即使是外面包裹了materialapp也是无效的,因为flutter会根据这个context一直上溯,一直到根节点的widget,注意,上溯是根据context的,会上溯到这个context相关的widget的最根节点

14down vote

This error is unrelated to the destination. It happens because you used a context that doesn’t contain a Navigator instance as parent.

How do I create a Navigator instance then ?

This is usually done by inserting in your widget tree a MaterialApp or WidgetApp. Although you can do it manually by using Navigator directly but less recommended. Then, all children of such widget can access NavigatorState using Navigator.of(context).

Wait, I already have a MaterialApp/WidgetApp !

That’s most likely the case. But this error can still happens when you use a context that is a parent of MaterialApp/WidgetApp.

This happens because when you do Navigator.of(context), it will start from the widget associated to the context used. And then go upward in the widget tree until it either find a Navigator or there’s no more widget.

In the first case, everything is fine. In the second, it throws a

Navigator operation requested with a context that does not include a Navigator.

So, how do I fix it ?

First, let’s reproduce this error :

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Center(
        child: RaisedButton(
          child: Text("Foo"),
          onPressed: () => Navigator.pushNamed(context, "/"),
        ),
      ),
    );
  }
}

This example creates a button that attempts to go to ‘/’ on click but will instead throw an exception.

Notice here that in the

onPressed: () => Navigator.pushNamed(context, “/”),
we used context passed by to build of MyApp.

The problem is, MyApp is actually a parent of MaterialApp. As it’s the widget who instantiate MaterialApp! Therefore MyApp’s BuildContext doesn’t have a MaterialApp as parent!

To solve this problem, we need to use a different context.

In this situation, the easiest solution is to introduce a new widget as child of MaterialApp. And then use that widget’s context to do the Navigator call.

There are a few ways to achieve this. You can extract home into a custom class :

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHome()
    );
  }
}
 
class MyHome extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: RaisedButton(
          child: Text("Foo"),
          onPressed: () => Navigator.pushNamed(context, "/"),
        ),
      );
  }
}

Or you can use Builder :

import 'package:flutter/material.dart';
 
void main() => runApp(MyApp());
 
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Builder(
        builder: (context) => Center(
              child: RaisedButton(
                child: Text("Foo"),
                onPressed: () => Navigator.pushNamed(context, "/"),
              ),
            ),
      ),
    );
  }
}

4、设置Container背景色

child: Container(

  decoration: new BoxDecoration(
    color: Colors.grey,
  ),

5、去除AppBar 阴影

页面本身是白色,所以我想将appbar 也设置成白色,并且需要去除阴影

child: Scaffold(
          appBar: AppBar(
            title: Text("xixi"),
            backgroundColor: Colors.white,
            elevation: 0,  //默认是4, 设置成0 就是没有阴影了
          ),
          backgroundColor: Colors.white,

6、组件描边,圆角

通常用Container组件的decoration来做

Container(
              padding: EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0),
              decoration: BoxDecoration(
                  border: Border.all(color: Colors.grey, width: 1.0),
                  borderRadius: BorderRadius.circular(3.0)),
              child: Text(_products[index]['description'])),

padding内边距属性,如果不添加描边会紧贴文字image加上边距之后image

EdgeInsets支持多种自定义方法

EdgeInsets.all() 全方向
EdgeInsets.only(left,top,right,bottom) 自定义各方向的边距
EdgeInsets.symmetric(vertical, horizontal)自定义垂直,水平对称边距
EdgeInsets.fromWindowPadding(ui.WindowPadding padding, double devicePixelRatio) 根据机型屏幕尺寸定义
decoration这里用到BoxDecoration组件 常用属性

color颜色
border 描边宽度
borderRadius 圆角值
boxShadow 阴影 支持gradient 梯度,混合模式backgroundBlendMode shape自定义形状 Border BorderRadius同样支持多种自定方法.

7、如何给row或column布局添加手势监听?

直接在需要坚挺的布局外面套一层如下的代码即可!

GestureDetector(
  behavior: HitTestBehavior.translucent,
  onTap(){		//监听器
	Navigator.pushNamed(context, '/guide');//处理一些相关逻辑
	
}

8、ListView和GridView嵌套报错?

直接在GridView布局里,加上这两句代码,即可解决问题!

physics: new NeverScrollableScrollPhysics(),//增加
shrinkWrap: true,//增加

例子如下:

GridView.count(
      physics: new NeverScrollableScrollPhysics(),//增加
      shrinkWrap: true, //增加
      ...
      ...
      children: <Widget>[
       ...
       ...
      ],
    );

9、在android studio中导入Flutter项目报错

第一步:左上角File→Settings→Dart
配置如下:

第二步:

10、给新页面传值

效果图一:
点击跳转


效果图二:

点击第四项后并传值到下一个页面来显示

代码+注释:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

/**
 * 定义主页。
    添加一个打开选择页面的按钮。
    在选择页面上显示两个按钮。
    点击一个按钮时,关闭选择的页面。
    主页上弹出一个snackbar以显示用户的选择。
 */
class Todo {
  final String title;
  final String description;

  Todo(this.title, this.description);
}

void main() {
  runApp(new MaterialApp(
    title: 'Passing Data',
    home: new TodosScreen(
      todos: new List.generate(
        20,
            (i) => new Todo(
          'Todo $i',
          'A description of what needs to be done for Todo $i',
        ),
      ),
    ),
  ));
}

class TodosScreen extends StatelessWidget {
  final List<Todo> todos;

  TodosScreen({Key key, @required this.todos}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Todos'),
      ),
      body: new ListView.builder(
        itemCount: todos.length,
        itemBuilder: (context, index) {
          return new ListTile(
            title: new Text(todos[index].title),
            // When a user taps on the ListTile, navigate to the DetailScreen.
            // Notice that we're not only creating a new DetailScreen, we're
            // also passing the current todo through to it!
            /**
             *当用户点击ListTile时,导航到DetailScreen。请注意,我们不仅在创建新的DetailScreen,而且还在传递当前的待办事项!
             */
            onTap: () {
              Navigator.push(
                context,
                new MaterialPageRoute(
                  builder: (context) => new DetailScreen(todo: todos[index]),   //传入Todo对象
                ),
              );
            },
          );
        },
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  // Declare a field that holds the Todo
  final Todo todo;  //接收Todo 对象

  // In the constructor, require a Todo
  DetailScreen({Key key, @required this.todo}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Use the Todo to create our UI
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("${todo.title}"),
      ),
      body: new Padding(
        padding: new EdgeInsets.all(16.0),
        child: new Text('${todo.description}'),
      ),
    );
  }
}

喜欢记得点个赞哟,我是王睿,很高兴认识大家!

更多原理请参考谷歌官网:给新页面传值

11、实现滑动关闭、删除item

效果图一:

效果图二:
右滑删除第六个item

效果图三:
左滑删除第4个item

效果图四:
最后看到,第四项和第六项都被删除了

代码+注释:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(new MyApp(
    items: new List<String>.generate(20, (i) => "Item ${i + 1}"),
  ));
}

class MyApp extends StatelessWidget {
  final List<String> items;

  MyApp({Key key, @required this.items}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final title = 'Dismissing Items';

    return new MaterialApp(
      title: title,
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text(title),
        ),
        body: new ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) {
            final item = items[index];

            return new Dismissible(
              // Each Dismissible must contain a Key. Keys allow Flutter to
              // uniquely identify Widgets.
              /**
               * 每个可豁免项都必须包含一个密钥。 键使Flutter能够唯一标识小部件。
               */
              key: new Key(item),
              // We also need to provide a function that will tell our app
              // what to do after an item has been swiped away.
              /**
               * 我们还需要提供一个可以告诉我们应用程序的功能刷掉物品后该怎么办。
               * onDismissed —— 被解雇
               * direction —— 方位表示:删除方向是左边还是右边,startToEnd 左边,endToStart 右边
               */
              onDismissed: (direction) {
                print(direction);
                items.removeAt(index);    //根据坐标移除具体的item项
                // 显示移除了哪一条item
                Scaffold.of(context).showSnackBar(
                    new SnackBar(content: new Text("$item dismissed")));
              },
              // Show a red background as the item is swiped away
              // 滑动删除item时显示红色背景
              background: new Container(color: Colors.red),
              child: new ListTile(title: new Text('$item')),
            );
          },
        ),
      ),
    );
  }
}

喜欢记得点个赞哟,我是王睿,很高兴认识大家!

更多原理请参考谷歌官网:实现滑动关闭、删除item

12、添加Material触摸水波效果

效果图一:

点击按钮出现灰色水波纹特效

效果图二:

显示底部弹出框

代码+注释:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'InkWell Demo';

    return new MaterialApp(
      title: title,
      home: new MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(title),
      ),
      body: new Center(child: new MyButton()),
    );
  }
}

class MyButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // The InkWell Wraps our custom flat button Widget
    /// InkWell 有水波纹, GestureDetector 没有水波纹
    return new InkWell(
      // When the user taps the button, show a snackbar
      // 当用户点击按钮时,显示 snackbar
      onTap: () {
        Scaffold.of(context).showSnackBar(new SnackBar(
          content: new Text('Tap'),
        ));
      },
      child: new Container(
        padding: new EdgeInsets.all(12.0),
        child: new Text('Flat Button'),
      ),
    );
  }
}

喜欢记得点个赞哟,我是王睿,很高兴认识大家!

更多原理请参考谷歌官网:添加Material触摸水波效果

13、处理点击

效果图一:
点击按钮

效果图二:

显示底部弹出框

代码+注释:

import 'package:chapter02one/Api.dart';
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'Gesture Demo';

    return new MaterialApp(
      title: title,
      home: new MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(

以上是关于Flutter 常见问题总结的主要内容,如果未能解决你的问题,请参考以下文章

flutter解决 dart:html 只支持 flutter_web 其他平台编译报错 Avoid using web-only libraries outside Flutter web(代码片段

Flutterflutter doctor 报错Android license status unknown. Run `flutter doctor --android-licenses‘(代码片段

Flutter 报错 DioError [DioErrorType.DEFAULT]: Bad state: Insecure HTTP is not allowed by platform(代码片段

Flutter 布局备忘录

BootStrap有用代码片段(持续总结)

BootStrap实用代码片段(持续总结)