精华内容
下载资源
问答
  • Flutter实战.pdf

    2020-04-01 21:15:58
    flutter实战 inaction 闲鱼. Flutter 是 Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。Flutter 开源、免费,拥有宽松的开源协议,适合商业项目。
  • Flutter实战电子书

    2019-08-13 09:25:18
    本项目为Flutter中文网《Flutter实战》开源电子书项目
  • Flutter受React启发,虽然工作原理不同,但是玩法跟React类似,Flutter通过自绘制UI的方式,高性能的实现了跨平台APP的开发,值得入手。简单开发即使未来的趋势。
  • Flutter 实战

    2019-10-21 11:43:41
    Flutter 实战
  • Flutter实战,超清版PDF版(非 图片版,可复制文字),作者。。wendux
  • 读书笔记–《flutter实战》 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的...

    读书笔记–《flutter实战》

    在这里插入图片描述

    Flutter实战
    Dart
    数据类型声明
    变量
    var
    类似于JavaScript中的var,它可以接收任何类型的变量,但最大的不同是Dart中var变量一旦赋值,类型便会确定,则不能再改变其类型
    Object,dynamic
    Object 是Dart所有对象的根基类,也就是说所有类型都是Object的子类(包括Function和Null),所以任何类型的数据都可以赋值给Object声明的对象. dynamic与var一样都是关键词,声明的变量可以赋值任意对象。 而dynamic与Object相同之处在于,他们声明的变量可以在后期改变赋值类型。dynamic与Object不同的是,dynamic声明的对象编译器会提供所有可能的组合, 而Object声明的对象只能使用Object的属性与方法, 否则编译器会报错。如:
    常量
    final,const
    如果您从未打算更改一个变量,那么使用 final 或 const,不是var,也不是一个类型。 一个 final 变量只能被设置一次,两者区别在于:const 变量是一个编译时常量,final变量在第一次使用时被初始化。被final或者const修饰的变量,变量类型可以省略简单来说,两者定义的数据都是不可更改的,但final可以后确定,例如在构造方法中赋值,而const一开始定义的时候就必须赋值了。
    变量或常量的名称由"_“开头,表示私有,既private
    函数
    Dart里的函数是可以赋值给变量,并进行传递的。Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。注意点:Dart函数声明如果没有显式声明返回值类型时会默认做dynamic处理,注意,函数返回值没有类型推断:typedef bool CALLBACK(); //不指定返回类型,此时默认为dynamic,不是bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] != null; } void test(CALLBACK cb){ print(cb()); } //报错,isNoble不是bool类型 test(isNoble);
    可选参数写法
    可选的位置参数包装一组函数参数,用[]标记为可选的位置参数,并放在参数列表的最后面:String say(String from, String msg, [String device]) { var result = ‘$from says KaTeX parse error: Expected '}', got 'EOF' at end of input: … result = 'result with a KaTeX parse error: Expected 'EOF', got '}' at position 12: device'; }̲ return resul…{expression}将表达式的值放在字符串中。如果表达式是标识符,则可以跳过{}使用带有单引号或双引号的三引号创建多行字符串:var s1 = ‘’’ You can create multi-line strings like this one. ‘’’; var s2 = “”“This is also a multi-line string.””"; 可以通过在前面加上r来创建“原始”字符串:var s = r’In a raw string, not even \n gets special treatment.’; //输出s,会发现原本的换行符号成了普通字符输出了
    分支主题
    标签来控制流程
    void main() { outerloop: for (var i = 0; i < 5; i++) { print(“Innerloop: ${i}”); innerloop: for (var j = 0; j < 5; j++) { if (j > 3 ) break ; if (i == 2) break innerloop; if (i == 4) break outerloop; print(“Innerloop: ${j}”); } } }
    集合
    for…invoid main() { var obj = [12,13,14]; for (var prop in obj) { print(prop); } } 使用扩展运算符(…)将列表的所有元素插入另一个列表:var list = [1, 2, 3]; var list2 = [0, …list]; assert(list2.length == 4); 如果扩展运算符右侧的表达式可能为null,则可以通过使用支持null的扩展运算符(…?)来避免异常:var list; var list2 = [0, …?list]; assert(list2.length == 1);
    设置App图标
    Android
    在Flutter项目的根目录中,导航到…/android/app/src/main/res目录,里面包含了各种资源文件夹(如mipmap-hdpi已包含占位符图像“ic_launcher.png”,见图2-8)。 只需按照Android开发人员指南中的说明, 将其替换为所需的资源,并遵守每种屏幕密度(dpi)的建议图标大小标准。
    ios
    在Flutter项目的根目录中,导航到…/ios/Runner。该目录中Assets.xcassets/AppIcon.appiconset已经包含占位符图片(见图2-9), 只需将它们替换为适当大小的图片,保留原始文件名称。
    设置闪屏页面
    Android
    要将启动屏幕(splash screen)添加到您的Flutter应用程序, 请导航至…/android/app/src/main。在res/drawable/launch_background.xml,通过自定义drawable来实现自定义启动界面(你也可以直接换一张图片)。
    ios
    要将图片添加到启动屏幕(splash screen)的中心,请导航至…/ios/Runner。在Assets.xcassets/LaunchImage.imageset, 拖入图片,并命名为LaunchImage.png、LaunchImage@2x.png、LaunchImage@3x.png。 如果你使用不同的文件名,那您还必须更新同一目录中的Contents.json文件,图片的具体尺寸可以查看苹果官方的标准。您也可以通过打开Xcode完全自定义storyboard。在Project Navigator中导航到Runner/Runner然后通过打开Assets.xcassets拖入图片,或者通过在LaunchScreen.storyboard中使用Interface Builder进行自定义,
    路由管理
    普通路由
    跳转: Navigator.push( context, MaterialPageRoute(builder: (context) { return NewRoute(); })); 带参数跳转: var result = await Navigator.push( context, MaterialPageRoute( builder: (context) { return TipRoute( // 路由参数 text: “我是提示xxxx”, ); }, ), ); //输出TipRoute路由返回结果 print(“路由返回值: $result”); 或者可以写成: Navigator.push( context, MaterialPageRoute( builder: (context) { return TipRoute( // 路由参数 text: “我是提示xxxx”, ); }, ), ).then((value) => { //输出TipRoute路由返回结果 print(“路由返回值: $value”);}); 退出:Navigator.pop(context, “我是返回值”)
    命名路由
    注册路由表://在MaterialApp中做注册 routes:{ “/”:(context) => RandomWords(), //注册首页路由 “NewRoute”:(context) => NewRoute(), }, 带参数跳转:Navigator.of(context).pushNamed(“NewRoute”, arguments: _saved).then((value) => print(value)); 接受参数:Set _saved=ModalRoute.of(context).settings.arguments;
    (onGenerateRoute)命名路由跳转控制
    //在MaterialApp中配置 //只有没有在路由表中的命名路由跳转时才会回调该方法,既没有在 //MaterialApp中注册过的 onGenerateRoute: (RouteSettings settings){ return MaterialPageRoute(builder: (context){ String routeName = settings.name; } ); },
    参数传递
    可用构造方法传递参数,也可通过settings.arguments进行参数的传递
    资源管理
    Flutter APP安装包中会包含代码和 assets(资源)两部分。Assets是会打包到程序安装包中的,可在运行时访问。常见类型的assets包括静态数据(例如JSON文件)、配置文件、图标和图片(JPEG,WebP,GIF,动画WebP / GIF,PNG,BMP和WBMP)等。
    指定资源
    和包管理一样,Flutter也使用pubspec.yaml文件来管理应用程序所需的资源,举个例子:flutter: assets: - assets/my_icon.png - assets/background.png assets指定应包含在应用程序中的文件, 每个asset都通过相对于pubspec.yaml文件所在的文件系统路径来标识自身的路径。asset的声明顺序是无关紧要的,asset的实际目录可以是任意文件夹(在本示例中是assets文件夹)。在构建期间,Flutter将asset放置到称为 asset bundle 的特殊存档中,应用程序可以在运行时读取它们(但不能修改)。
    加载资源
    加载文本assets通过rootBundle 对象加载:每个Flutter应用程序都有一个rootBundle对象, 通过它可以轻松访问主资源包,直接使用package:flutter/services.dart中全局静态的rootBundle对象来加载asset即可。通过 DefaultAssetBundle 加载:建议使用 DefaultAssetBundle 来获取当前BuildContext的AssetBundle。 这种方法不是使用应用程序构建的默认asset bundle,而是使父级widget在运行时动态替换的不同的AssetBundle,这对于本地化或测试场景很有用。通常,可以使用DefaultAssetBundle.of()在应用运行时来间接加载asset(例如JSON文件),而在widget上下文之外,或其它AssetBundle句柄不可用时,可以使用rootBundle直接加载这些asset,例如:import ‘dart:async’ show Future; import ‘package:flutter/services.dart’ show rootBundle; Future<String> loadAsset() async { return await rootBundle.loadString(‘assets/config.json’); } 加载图片类似于原生开发,Flutter也可以为当前设备加载适合其分辨率的图像。声明分辨率相关的图片 assetsAssetImage 可以将asset的请求逻辑映射到最接近当前设备像素比例(dpi)的asset。为了使这种映射起作用,必须根据特定的目录结构来保存asset:…/image.png…/Mx/image.png…/Nx/image.png…etc.其中M和N是数字标识符,对应于其中包含的图像的分辨率,也就是说,它们指定不同设备像素比例的图片。主资源默认对应于1.0倍的分辨率图片。看一个例子:…/my_icon.png…/2.0x/my_icon.png…/3.0x/my_icon.png在设备像素比率为1.8的设备上,…/2.0x/my_icon.png 将被选择。对于2.7的设备像素比率,…/3.0x/my_icon.png将被选择。如果未在Image widget上指定渲染图像的宽度和高度,那么Image widget将占用与主资源相同的屏幕空间大小。 也就是说,如果…/my_icon.png是72px乘72px,那么…/3.0x/my_icon.png应该是216px乘216px; 但如果未指定宽度和高度,它们都将渲染为72像素×72像素(以逻辑像素为单位)。pubspec.yaml中asset部分中的每一项都应与实际文件相对应,但主资源项除外。当主资源缺少某个资源时,会按分辨率从低到高的顺序去选择 ,也就是说1x中没有的话会在2x中找,2x中还没有的话就在3x中找。加载图片要加载图片,可以使用 AssetImage类。例如,我们可以从上面的asset声明中加载背景图片:Widget build(BuildContext context) { return new DecoratedBox( decoration: new BoxDecoration( image: new DecorationImage( image: new AssetImage(‘graphics/background.png’), ), ), ); } 注意,AssetImage 并非是一个widget, 它实际上是一个ImageProvider,有些时候你可能期望直接得到一个显示图片的widget,那么你可以使用Image.asset()方法,如:Widget build(BuildContext context) { return Image.asset(‘graphics/background.png’); } 使用默认的 asset bundle 加载资源时,内部会自动处理分辨率等,这些处理对开发者来说是无感知的。 (如果使用一些更低级别的类,如 ImageStream或 ImageCache 时你会注意到有与缩放相关的参数)
    State生命周期
    reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。didUpdateWidget():在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。正如之前所述,Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。
    initState
    当Widget第一次插入到Widget树时会被调用,对于每一个State对象,Flutter framework只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在Widget树上获取离当前widget最近的一个父级InheritFromWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后,Widget树中的InheritFromWidget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。
    didChangeDependencies
    当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget,然后在之后的build() 中InheritedWidget发生了变化,那么此时InheritedWidget的子widget的didChangeDependencies()回调都会被调用。典型的场景是当系统语言Locale或应用主题改变时,Flutter framework会通知widget调用此回调。
    build
    build():此回调读者现在应该已经相当熟悉了,它主要是用于构建Widget子树的,会在如下场景被调用:在调用initState()之后。在调用didUpdateWidget()之后。在调用setState()之后。在调用didChangeDependencies()之后。在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其它位置之后。
    deactivate
    deactivate():当State对象从树中被移除时,会调用此回调。在一些场景下,Flutter framework会将State对象重新插到树中,如包含此State对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。
    dispose
    dispose():当State对象从树中被永久移除时调用;通常在此回调中释放资源。

    展开全文
  • Flutter中,如果我们需要打印日志,如果不进行自定义,我们只能使用自带的 print() 或者 debugPrint() 方法进行打印,但是这两种打印,日志都是默认 Info 层级的日志,很不友好,所以如果需要日志打印层级分明,...
  • flutter实战

    2019-07-05 18:48:41
    Flutter开源电子书- Flutter实战 flutter官方开源书籍 在线观看地址:https://book.flutterchina.club GitHub地址:https://github.com/flutterchina/flutter-in-action

    Flutter开源电子书- Flutter实战

    flutter官方开源书籍

    在线观看地址:https://book.flutterchina.club

    在这里插入图片描述
    GitHub地址:https://github.com/flutterchina/flutter-in-action

    展开全文
  • 由于Flutter代码的嵌套性比较繁乱,冗杂。所以我们就把每个界面的ListView显示的每个item数据都进行封装。 首页控件的封装: import 'package:flutter/material.dart'; import 'package:flutter_app_pneumonia/api/...
  • 此动画效果是我在浏览文章时发现的一个非常酷炫的效果,于是就使用 Flutter 实现了。 更多动画效果及Flutter资源: https://github.com/781238222/flutter-do 添加依赖 在项目的 pubspec.yaml 文件中添加依赖: ...
  • 一起从0开始Flutter实战! 作为Flutter实战的开篇,我们需要介绍下我们要做的内容以及我们的准备工作,为了能让我们的实战顺利进行需要一个开放的API接口服务平台,选择了半天最终选择了一个开发者平台,感谢玩安卓的...

    项目搭建(上)

    一起从0开始Flutter实战!

    作为Flutter实战的开篇,我们需要介绍下我们要做的内容以及我们的准备工作,为了能让我们的实战顺利进行需要一个开放的API接口服务平台,选择了半天最终选择了一个开发者平台,感谢玩安卓的开发者提供的开放API,可以让我们在练习一些项目的时候使用。玩安卓的开放API提供了很多的功能,具体的功能可以参照玩安卓API
    确定了我们要做什么我们就可以着手准备了,我们先把项目的结构分为哪些模块进行设计。

    lib
     	-constants   //存放一些常量
     	-events		 //跨界面的事件传递
     	-model		 //解析的数据和数据库数据
     	-network	 //网络请求以及网络配置
     	-pages		 //单独页面的集合
     	-route		 //页面的路由配置,避免多出配置导致的混乱
     	-utils		 //一些工具类
     	-widgets	 //小的工具组件,单独的widget
    

    整体的一个结构就出来了,根据习惯我们还是先准备一些工具类,和网络的请求,先把与业务无关的工具准备好,这样让我们以后开始的时候更加的顺畅。网络的三方库配置在以前已经有介绍,不再多说了,我们当时也说在以后的实践中是需要进行一个封装的,我们一起来研究下如何能做一个合理的封装:
    我选择的是创建一个单例的Http配置类,这样我们可以对Dio进行一次配置通用的效果:

    class HttpConfig {
      static String BASE_URL = "";
      static HttpConfig _config;
    
      static HttpConfig get instance => _getInstance();
      Dio dio;
    
      HttpConfig._internal() {
        var baseOption = BaseOptions(
          connectTimeout: 6000,
          sendTimeout: 3000,
          baseUrl: BASE_URL,
          headers: HttpHeaderConfig.getHeaderConfig(),//这里使用的一个方法而不是一个变量来配置的header,因为有些header我们会放到sharePerence里,所以我们使用一个方法来可以随时进行读取
        );
        dio = Dio(baseOption);
        dio.interceptors.add(InterceptorsWrapper(//这里创建一个拦截器,拦截器在我们根据需要进行消息的拦截
            onRequest: (option) {},
            onResponse: (response) {
              if (response.statusCode == 400 || response.statusCode == 404) {}//这里拦截了错误的状态的返回
            },
            onError: (error) {}));
      }
    
      static HttpConfig _getInstance() {
        if (_config == null) {
          _config = HttpConfig._internal();
        }
        return _config;
      }
    
      //这里创建一个get请求的方法,不是直接把dio做成公用而是提供get方法是为了以后如果我们替换整个网络请求库的时候方便更换,还有一个原因,是我们在一般请求的时候需要对参数进行签名,这里可以帮助我们做统一的处理。
      void get<T>(String path,
          {Map<String, dynamic> params,
          Function(T t) onSuccess,   		//这里对返回结果已经做了处理也是为了以后如果网络框架修改后能低成本迁移
          Function(int error) onError}) async {
        Response response;
        try {
          response = await dio.get(path, queryParameters: params);
          if (response.statusCode == 200 && onSuccess != null) {//先做过滤,这样我们在处理的时候就省去了很多重复代码
            onSuccess(response.data);
          } else if (onError != null) {
            onError(response.statusCode);	//如果状态码不对,则返回错误数据
          }
        } catch (e) {
          if (onError != null && response != null) {
            onError(-1);  //如果抛出了异常,则返回错误数据
          }
        }
      }
     //参照get的说明
      void post<T>(String path,
          {Map<String, dynamic> data,
          Function(T t) onSuccess,
          Function(int error) onError}) async {
        Response response;
        try {
          response = await dio.post(path, data: data);
          if (response.statusCode == 200 && onSuccess != null) {
            onSuccess(response.data);
          } else if (onError != null) {
            onError(response.statusCode);
          }
        } catch (e) {
          if (onError != null && response != null) {
            onError(-1);
          }
        }
      }
    }
    
    

    创建的HttpHeader的方法:

    class HttpHeaderConfig {
      static Map<String, dynamic> getHeaderConfig() {
        Map<String,dynamic> config = Map<String,dynamic>();
        config[""] = "";//这里我们堆放我们的header,如果需要从数据库或者sharePerence中获取数据可以在这里获取
        return config;
      }
    }
    

    网络请求的方法我们就算是封装完了,然后我们可能还需要将网络返回的数据进行一个保存,我们先将sharePreferences进行一个封装。
    sharePreference的第三方引入前面也有介绍过了,我们可以看下前面是如何进行引入的。然后我们对SharedPreferences进行封装:这里的封装参照了阿里开源的Flutter-Go

    //这里也是创建了一个单例的SharedPreferences
    class SharedPrefrencesUtils {
      static SharedPrefrencesUtils _prefrencesUtils;
      static SharedPreferences _sharedPerence;
    
      static Future<SharedPrefrencesUtils> get instance async {
        return await _getInstance();
      }
    
    	//因为SharedPreferences的初始化是耗时操作,需要在读取的时候需要添加await关键字,这也是这里的单例与网络请求有些许不同的原因。
      static Future<SharedPrefrencesUtils> _getInstance() async {
        if (_prefrencesUtils == null) {
          _prefrencesUtils = SharedPrefrencesUtils._internal();
        }
        if (_sharedPerence == null) {
          await _prefrencesUtils._init();
        }
        return _prefrencesUtils;
      }
    
      Future _init() async {
        _sharedPerence = await SharedPreferences.getInstance();
      }
    
      SharePrefrencesUtils._internal() {}
    
     //检查是否存在某个Key
      bool hasKey(String key) {
        if (_beforeCheck() || key == null) {
          return false;
        }
        return _sharedPerence.containsKey(key);
      }
      //存放字符串
      Future<bool> putString(String key, String value) {
        if (_beforeCheck()) return null;
        return _sharedPerence.setString(key, value);
      }
    
      Future<bool> putBool(String key, bool value) {
        if (_beforeCheck()) return null;
        return _sharedPerence.setBool(key, value);
      }
    
      Future<bool> putDouble(String key, double value) {
        if (_beforeCheck()) return null;
        return _sharePerence.setDouble(key, value);
      }
    
      Future<bool> putInt(String key, int value) {
        if (_beforeCheck()) return null;
        return _sharePerence.setInt(key, value);
      }
    
      String getString(String key) {
        if (_beforeCheck()) return null;
        return _sharedPerence.getString(key);
      }
    
      bool getBool(String key) {
        if (_beforeCheck()) return null;
        return _sharedPerence.getBool(key);
      }
    
      double getDouble(String key) {
        if (_beforeCheck()) return null;
        return _sharedPerence.getDouble(key);
      }
    
      int getInt(String key) {
        if (_beforeCheck()) return null;
        return _sharedPerence.getInt(key);
      }
      
      static bool _beforeCheck() {
        if (_sharedPerence == null) {
          return true;
        }
        return false;
      }
    }
    

    只是有SharedPreferences还不能满足我们现在APP的需要,我们还需要使用数据库来帮助我们存储更多的数据内容,数据库的引入在前面的介绍中我们也都已经进行了介绍,现在把数据库来进行一个封装:

    class DataBaseUtils {
      static DataBaseUtils _dataBaseUtils;
      static Database _dataBase;
    
      static String _DATABASE_PATH = "exercise_db.db";
      static int _DATABASE_VERSION = 1;
      static String _CREATE_TABLE =
          "create table my_table (id integer primary key,userid text,password text)";
    
      static Future<DataBaseUtils> get instance async {
        return await _getInstance();
      }
    
      //因为DataBase的初始化是耗时操作,需要在读取的时候需要添加await关键字,这也是这里的单例与网络请求有些许不同的原因。
      static Future<DataBaseUtils> _getInstance() async {
        if (_dataBaseUtils == null) {
          _dataBaseUtils = DataBaseUtils._internal();
        }
        if (_dataBase == null) {
          await _dataBaseUtils._init();
        }
        return _dataBaseUtils;
      }
    
      Future _init() async {
        _dataBase = await openDatabase(_DATABASE_PATH, version: _DATABASE_VERSION,
            onCreate: (dataBase, version) {
          if (version == 1 && dataBase.isOpen) {
            dataBase.execute(_CREATE_TABLE);
          }
        }, onUpgrade: (dataBase, oldVersion, newVersion) {
          //这里预留了更新的操作,在version为1的时候并不会触发
          if (oldVersion == 1 && newVersion == 1 && dataBase.isOpen) {}
        });
      }
    
      ///插入数据
      ///tableName 需要插入的表名
      ///params 需要插入的数据
      Future<int> insert(String tableName, Map<String, dynamic> params) async {
        if (_dataBase != null && _dataBase.isOpen) {
          var dataId = await _dataBase.insert(tableName, params);
          return dataId;
        }
        return 0;
      }
    
      ///删除数据
      /// tableName 需要查的表名
      /// conditions 查询的条件
      /// mod 查询的模式,是否是用And连接起来的查询条件,可选['And','Or']
      Future<int> delete(String tableName,
          {Map<String, dynamic> conditions, String mod = "And"}) async {
        return await _dataBase.delete(tableName,
            where: _formatCondition(conditions, mod: mod));
      }
    
      ///更新数据
      /// tableName 需要查的表名
      /// values 需要更新的数据
      /// conditions 查询的条件
      /// mod 查询的模式,是否是用And连接起来的查询条件,可选['And','Or']
      Future<int> update(String tableName,
          {Map<String, dynamic> values,
          Map<String, dynamic> conditions,
          String mod = "And"}) async {
        return await _dataBase.update(tableName, values,
            where: _formatCondition(conditions, mod: mod));
      }
    
      /// tableName 需要查的表名
      /// conditions 查询的条件
      /// mod 查询的模式,是否是用And连接起来的查询条件,可选['And','Or']
    
      Future<List> query(String tableName,
          {Map<String, dynamic> conditions, String mod = "And"}) async {
        if (_dataBase != null && _dataBase.isOpen) {
          return await _dataBase.query(tableName,
              where: _formatCondition(conditions, mod: mod));
        }
        return null;
      }
    
      DataBaseUtils._internal() {}
    
    
       ///格式化数据,将条件组合格式化为String
      String _formatCondition(Map<String, dynamic> conditions,
          {String mod = "And"}) {
        var conditionStr = "";
        var index = 0;
        conditions.forEach((key, value) {
          if (value == null) {
            return;
          }
          if (value.runtimeType == String) {
            conditionStr = '$conditionStr $key like $value';
          }
          if (value.runtimeType == int ||
              value.runtimeType == double ||
              value.runtimeType == bool) {
            conditionStr = '$conditionStr $key = $value';
          }
          if (index >= 0 && index < conditions.length - 1) {
            conditionStr = '$conditionStr $mod';
          }
          index++;
        });
        return conditionStr;
      }
    
      void close(){
        if(_dataBase != null && _dataBase.isOpen){
          _dataBase.close();
        }
      }
    }
    

    现在与我们数据请求相关的内容就封装好了,从网络请求到数据的持久化都已经准备好了,下面我们准备一下资源的管理。

    项目已经同步更新到了GitHub,有需要的同学可以看下。如有好的意见和建议可以提出共同探讨。

    展开全文
  • 从这篇文章开始,我准备新建一个实战合集,flutter实战20个小demo,记录自己学习这20个小实战的过程。 首先,新建一个文件夹,flutterDemo20,在这个文件夹里将会是我们这20个实战的代码。 首先,在文件夹下创建第一...

    从这篇文章开始,我准备新建一个实战合集,flutter实战20个小demo,记录自己学习这20个小实战的过程。
    首先,新建一个文件夹,flutterDemo20,在这个文件夹里将会是我们这20个实战的代码。
    第一个项目效果是这样的
    在这里插入图片描述

    一、创建

    首先,在文件夹下创建第一个项目

    flutter create demo01
    

    二、找到主文件并修改

    在这里插入图片描述
    在main.dart中将原本的代码修改为

    import 'package:flutter/material.dart';
    import 'bottom_navigation_widget.dart';
    
    void main()=> runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData.light(),
          home: BottomNavigationWidget()
        );
      }
    }
    

    此时,bottom_navigation_widget.dart文件是不存在的,所以会报错。

    三、编写bottom_navigation_widget.dart文件

    import 'package:flutter/material.dart';
    
    class BottomNavigationWidget extends StatefulWidget {
      @override
      _BottomNavigationWidgetState createState() => _BottomNavigationWidgetState();
    }
    
    class _BottomNavigationWidgetState extends State<BottomNavigationWidget> {
      final _BottomNavigationColor = Colors.blue;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          bottomNavigationBar: BottomNavigationBar(
            items: [
              BottomNavigationBarItem(
                icon: Icon(Icons.home, color:_BottomNavigationColor),
                title: Text('Home', style:TextStyle(color: _BottomNavigationColor))
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.email, color:_BottomNavigationColor),
                title: Text('Email', style:TextStyle(color: _BottomNavigationColor))
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.pages, color:_BottomNavigationColor),
                title: Text('Pages', style:TextStyle(color: _BottomNavigationColor))
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.airplay, color:_BottomNavigationColor),
                title: Text('AirPlay', style:TextStyle(color: _BottomNavigationColor))
              ),
            ],
            type: BottomNavigationBarType.fixed,
          ),
        );
      }
    }
    

    注意:type: BottomNavigationBarType.fixed,这句话一定要写上,一开始没有写这句话,排版出现了问题,文字没有完全显示出来。
    给大家bug复现一下:
    不加type: BottomNavigationBarType.fixed,这句话时样式是这样的
    在这里插入图片描述
    经多番排查,去掉最后一个BottomNavigationBarItem就好了,只保留三个就都显示出来了,我觉得应该是样式排版的问题,把文字挤下去了。
    在这里插入图片描述
    加上type: BottomNavigationBarType.fixed,这句话,四个icon就都可以正常显示出来了
    在这里插入图片描述

    四、根据选中的底部导航栏切换中间显示内容

    1、新建四个子页面文件

    在这里插入图片描述
    里面放入简单的代码
    在这里插入图片描述

    2、在bottom_navigation_widget.dart文件里引入并使用

    在这里插入图片描述

    3、根据index判断当前显示什么页面

    首先定义一个默认的index为0
    在这里插入图片描述
    然后写点击事件切换当前选中页面并赋值
    在这里插入图片描述
    最后根据index获取当前要显示的list
    在这里插入图片描述
    现在内容就可以正常显示了。
    在这里插入图片描述
    总结:
    1、StatefulWidget具有可变状态(state)的窗口组件(widget)。使用这个要根据变化状态,调整State值
    2、StatelessWidget快捷方式stl; StatefulWidget快捷方式stful
    3、页面的bottomBar的数量要和list的数量一致
    4、type:BottomNavigationBarType.fixed

    最终的完整代码会放在我的github上,github的地址随后奉上哦~

    展开全文
  • 本套餐包含入门+进阶实战课程,从简到难、从浅入深,逐步带领大家了解Flutter,熟悉掌握Flutter的组成部分,最后以一个完整的仿网易新闻的UI实战讲解,教会大家如何合理选择UI组件,并且使用组件快速实现我们的需求...
  • Flutter实战连贯项目

    2021-01-03 16:35:46
    疫情刚好静下心来回归过去的工作,博主要开始学习flutter,博主会边学习边实战项目连续连贯实现,大约一周会出一篇,源码地址,开源不易,麻烦动手点星,谢谢,本开源不做商业使用,里面涉及用到api接口资源等只供...
  • Flutter实战》 开源了,本书为 Flutter中文网开源电子书项目,本书系统介绍了Flutter技术的各个方面,本书属于原创书籍(并非翻译),希望对大家有帮助: 在线阅读地址:book.flutterchina.club 《Flutter实战》 ...
  • Flutter实战

    2019-05-27 02:21:58
    一个功能齐全的 flutter 金融理财APP项目,使用Node作为后台系统,使用了少量plugin,大体功能自己封装。 源码在GitHub,以便学习交流。github.com/zhongmeizhi… 寻找Flutter的爱好者,一起探讨交流学习。 ...
  • 02.Flutter实战建立项目和编写入口文件 创建项目: flutter create flutter_shop 创建完成之后呢,它会提示我们, 进入flutter_shop的目录,然后执行flutter run来运行项目 为什么起名flutter_shop用...
  • 来源公众号:开发者技术前线| 作者:白哥Flutter是Google开发的新一代跨平台方案,Flutter可以实现写一份代码同时运行在iOS和Android设备上,并且提...
  • Flutter 实战开发-网络请求,具体可参考https://blog.csdn.net/liuxingyuzaixian/article/details/120378959
  • Flutter实战一Flutter聊天应用(一)

    万次阅读 2017-06-05 20:02:08
    不知不觉,进阶的教程已经写了几十篇了,通过前面的学习,大家已经打下了良好的基础,接下来我们就开始进行项目实战吧!我们现在要写一个叫“谈天说地”的应用程序,这是一个简单、可扩展的聊天应用程序,能实时显示...
  • 写在前面 资源地址 用到的网址 用到的插件
  • Flutter实战之企业站APP

    2021-06-12 11:19:22
    Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的用户界面。本课程通过一个企业站APP的实例让大家能够快速的编写一个完整的APP项目。 功能模块: 启动页面 首页 Banner轮播 最新产品 产品列表 产品...
  • 先上效果图代码github地址:https://github.com/koudle/GDG_Flutter_Weather_Demo1.创建工程在Android Studio中,...Flutter Application创建完工程后,有三个目录android:Android工程的目录ios:iOS工程的目录li...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,498
精华内容 4,599
关键字:

flutter实战

友情链接: FDBPM_3D.zip