弗拉德

弗拉德 查看完整档案

青岛编辑  |  填写毕业院校  |  填写所在公司/组织 www.fulade.me 编辑
编辑

一生只有一个职业:学生
公众号: fulade_me
http://fulade.me

个人动态

弗拉德 发布了文章 · 4月13日

【Flutter 3-5】Flutter进阶教程——在Flutter中使用Lottie动画

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

Lottie动画

在移动开发中总是需要展示一些动画特效,作为程序员的我们并不是很擅长用代码做动画,即便是有些动画可以实现,在跨平台的过程中也会因为API的差异性导致动画在各个平台中展示的有差异。
所以为了释放程序员的双手,不再陷入写动画调参数的苦恼,Airbnb开源了一款专门用于跨平台的动画解决方案:Lottie
Lottie可以解析使用Bodymovin导出为json的Adobe After Effects动画,并在移动端和Web端展示。这样我们就可以把做动画的工作交给专业做动画的同事来完成,我们只需要使用导入json文件即可,这样是不是大大减少了程序员的工作量,并且能够:实现专业的人做专业的事。

导入Lottie框架

在Flutter中已经存在开源的Lottie库,所以我们只需要在pubspec.yaml中的dependencies导入相关的依赖即可

dependencies:
  lottie: ^0.7.0              

使用Lottie库

在需要展示Lottie动画Widget导入头文件

import 'package:lottie/lottie.dart';

默认读取本地json文件

    Lottie.asset("json/fun_do_like.json"),

2021_04_21_lottie_nomail
只需要一句话即可展示Lottie动画,是不是很简单。
我们来看其他的属性

  • repeat 是否重复执行。默认是true,如果是false,动画执行一遍就会停止
  • reverse 是否倒序播放。默认是false,如果设置为true,动画会先正序播放一遍,然后再倒序播放一遍
  • animate 是否允许播放动画。默认是true,如果设置为false,则不会播放动画

从网络读取json文件

Lottie.network("https://cdn.jsdelivr.net/gh/johnson8888/blog_pages/images/lottie_test.json",),

同样我们可以设置获取到网络资源后的回调

Lottie.network("https://cdn.jsdelivr.net/gh/johnson8888/blog_pages/images/lottie_test.json",
    onLoaded: (LottieComposition composition) {
        print("onLoaded");
    },
),

好了,关于Lottie的使用就总结这些了。

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->lottie_demo_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 3月30日

起名字老重名?使用这款利器可以快速帮你查询有哪些站点用了你的名字!


title: 起名字老重名?使用这款利器可以快速帮你查询有哪些站点用了你的名字!
author: 弗拉德
avatar: 'https://cdn.jsdelivr.net/gh/j...'
authorLink: 'http://fulade.me'
authorAbout: '一生只有一个职业:学生'
authorDesc: 技术改变生活
toc: true
comments: true
date: 2021-03-29 17:10:30
cover: https://cdn.jsdelivr.net/gh/j...
thumbnail: https://cdn.jsdelivr.net/gh/j...
tags:

- Tips

categories: Tips
keywords: sherlock
description:
photos:
fileName:

type:

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

2021_03_29_sherlock_demo

不知道有没有小伙伴跟我一样,常常在注册账号的时候输入了昵称往往会反回一个“用户名已存在”,然后尝试了好几个昵称之后才能成功。
今天介绍的这款工具可以帮助我们迅速的检索各大网站有没有我们自己的用户昵称,同样它也可以帮助我们快速的查询同一个用户名都注册了哪些网站。

简介

sherloc,在Github上面已经有24k的Star数,它的名字取自于电影《神探夏洛克》的英文名字——sherlock。
sherlock主要使用Python3来开发完成的,这使得它具有更多的灵活性,可以运行在Windows、Mac以及Linux操作系统上。

安装步骤

我们这里以Linux为例

# 从Github上面下载源码
$ git clone https://github.com/sherlock-project/sherlock.git

# 进入到源码文件夹内
$ cd sherlock

# 安装所需要的依赖
$ python3 -m pip install -r requirements.txt

得益于Python良好的包管理工具,使得我们的安装非常的简单和方便,只需要几个命令就可搞定。

使用方法

$ python3 sherlock --help
usage: sherlock [-h] [--version] [--verbose] [--folderoutput FOLDEROUTPUT]
                [--output OUTPUT] [--tor] [--unique-tor] [--csv]
                [--site SITE_NAME] [--proxy PROXY_URL] [--json JSON_FILE]
                [--timeout TIMEOUT] [--print-all] [--print-found] [--no-color]
                [--browse] [--local]
                USERNAMES [USERNAMES ...]

Sherlock: Find Usernames Across Social Networks (Version 0.14.0)

optional arguments:
  -h, --help           #展示帮助页面
  --version            # 输出当前的版本号以及项目的依赖
  --verbose, -v, -d, --debug # Log的输出等级
  --folderoutput FOLDEROUTPUT, -fo FOLDEROUTPUT #如果需要同时查询多个用户名,可以在这里定义传入保存结果的文件夹路径
  --output OUTPUT, -o OUTPUT  # 当只查询一个名字的时候,指定的保村结果的文件夹
  --tor, -t             # 通过Tor来发送请求,Tor必须安装在系统目录里面
  --unique-tor, -u      # 通过Tor来发送请求,并且每次都使用新的连接
  --csv                 # 创建CVS文件
  --site SITE_NAME      # 要查询的网站名字,可以传入多个
  --proxy PROXY_URL, -p PROXY_URL # 使用的代理
  --json JSON_FILE, -j JSON_FILE # 输入格式为json文件,支持从网络获取的json文件
  --timeout TIMEOUT     # 超时时间
  --print-all           # 输出所有结果 包括没有找到昵称的网站
  --print-found         # 只输出找到结果的信息
  --no-color            # 控制台不带有颜色的输出结果
  --browse, -b          # 使用默认浏览器打开所有的搜索结果
  --local, -l           # 强制使用本地的 data.json文件

使用方法也很简单,搜索单个名字,只需要在命令行输入:
python3 sherlock username
比如我们搜索川建国
搜索结果如下:
2021_03_29_sherlock_search

同时搜索多个名字
python3 sherlock tony heisenberg johnson
搜索结果如下:

[*] Checking username tony on:
[+] 3dnews: http://forum.3dnews.ru/member.php?username=tony
[+] 7Cups: https://www.7cups.com/@tony
[+] 9GAG: https://www.9gag.com/u/tony
[+] About.me: https://about.me/tony
[+] Academia.edu: https://independent.academia.edu/tony
[+] AllTrails: https://www.alltrails.com/members/tony
....
[+] Anobii: https://www.anobii.com/tony/profile
[+] Apple Discussions: https://discussions.apple.com/profile/tony
[+] Archive.org: https://archive.org/details/@tony
[+] Asciinema: https://asciinema.org/~tony
[+] AskFM: https://ask.fm/tony

[*] Checking username heisenberg on:
[+] 9GAG: https://www.9gag.com/u/heisenberg
[+] About.me: https://about.me/heisenberg
[+] Academia.edu: https://independent.academia.edu/heisenberg
[+] AllTrails: https://www.alltrails.com/members/heisenberg
[+] Anobii: https://www.anobii.com/heisenberg/profile
[+] Archive.org: https://archive.org/details/@heisenberg
...
[+] Asciinema: https://asciinema.org/~heisenberg
[+] AskFM: https://ask.fm/heisenberg
[+] Audiojungle: https://audiojungle.net/user/heisenberg
[+] BLIP.fm: https://blip.fm/heisenberg
[+] BOOTH: https://heisenberg.booth.pm/

[*] Checking username johnson on:
[+] 7Cups: https://www.7cups.com/@johnson
[+] 9GAG: https://www.9gag.com/u/johnson
[+] About.me: https://about.me/johnson
[+] Academia.edu: https://independent.academia.edu/johnson
[+] AllTrails: https://www.alltrails.com/members/johnson
...
[+] Anobii: https://www.anobii.com/johnson/profile
[+] Archive.org: https://archive.org/details/@johnson
[+] Audiojungle: https://audiojungle.net/user/johnson
[+] BLIP.fm: https://blip.fm/johnson
[+] Bandcamp: https://www.bandcamp.com/johnson

多个搜索结果会按顺序来展示

支持Docker

作者也增加了对docker的支持,我们只需要执行docker build -t mysherlock-image .就可以在docker中使用sherklock了。
更多有关docker的使用方法可以去github主页查看

实现原理

作者是把每一个用户的URL地址都去请求一下,如果有返回有结果就代表存在这个昵称,如果没有返回结果或者是404,就意味着不存在当前的用户。
比如请求:https://www.about.me/tony,可以请求到结果就代表存在这个昵称,如果没有相应的返回就是没有这个昵称。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 3月9日

金三银四招聘季,不想996?附赠一份955公司名单以及招聘链接

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

金三银四,又到了跳槽季,相信很多小伙伴都已经在摩拳擦掌,如果你厌倦了966制的工作,不妨看一下这个955的公司名单。

公司名称城市招聘链接
Amazon北京/上海https://www.amazon.jobs/zh
AMD上海https://www.amd.com/zh-hans/c...
Airbnb北京https://careers.airbnb.com/zh/
Apple北京/上海https://www.apple.com/jobs/cn/
ArcSoft杭州https://www.arcsoft.com.cn/jo...
Autodesk北京/上海https://www.zhipin.com/gongsi...
Booking上海https://www.zhipin.com/gongsi...
Citrix南京https://cn.indeed.com/%E5%B7%...
Cisco北京/上海/杭州/苏州https://www.zhipin.com/gongsi...
Coolapk (酷安)北京/深圳https://www.coolapk.com/about...
Coupang北京/上海https://www.zhipin.com/gongsi...
Douban (豆瓣)北京https://jobs.douban.com/jobs/...
eBay上海https://www.zhipin.com/gongsi...
eHealth厦门http://www.ehealth-china.com/...
Electronic Arts上海https://www.ea.com/zh-cn/careers
EMC上海https://chinajobs.dell.com/st...
Ericsson上海https://jobs.51job.com/yx/co2...
FreeWheel北京https://www.zhipin.com/gongsi...
GE上海http://careers.ge.com.cn/
Google北京/上海https://careers.google.com/jo...
Grab北京https://www.zhipin.com/gongsi...
Honeywell上海https://www.honeywell.com.cn/...
HP上海https://jobs.hp.com/zh-cn/Home
HSBC上海/广州/西安https://www.about.hsbc.com.cn...
Hulu北京https://careers.hulu.com/
IBM上海 (GBS除外)https://www.ibm.com/cn-zh/emp...
iHerb上海https://www.zhipin.com/gongsi...
Intel上海https://www.intel.com/content...
LeetCode上海https://www.zhipin.com/gongsi...
Linkedin北京https://cn.linkedin.com/compa...
Microsoft北京/上海/苏州https://www.microsoft.com/zh-...
MicroStrategy杭州https://www.microstrategy.cn/...
National Instruments上海https://jobs.51job.com/all/co...
Nokia南京/杭州https://www.zhipin.com/gongsi...
NVIDIA北京/上海https://www.nvidia.com/en-us/...
Oracle上海https://www.oracle.com/cn/cor...
PayPal上海https://www.zhipin.com/gongsi...
Pivotal北京/上海https://www.zhipin.com/gongsi...
Red Hat北京/上海/深圳https://www.zhipin.com/gongsi...
SAP上海https://www.sap.cn/about/care...
Shopee深圳https://app.mokahr.com/campus...
SmartNews北京/上海https://cn.recruit.net/compan...
Snap北京/深圳https://cn.indeed.com/%E5%B7%...
State Street杭州https://cn.indeed.com/%E5%B7%...
SUSE北京/上海/深圳https://jobs.suse.com/us/en
ThoughtWorks西安/北京/深圳/成都/武汉/上海/香港https://join.thoughtworks.cn/
Trend Micro南京https://careers.trendmicro.co...
TuSimple北京https://www.tusimple.com/cn/%...
Ubisoft上海https://zh-cn.ubisoft.com/hom...
Unity上海https://campus.unity.cn/
Vipshop (唯品会)上海https://recruitment.corp.vips...
VMware北京/上海https://careers.vmware.com/ch...
WeWork上海https://www.wework.cn/
Works Applications上海https://career.worksap.com/cn/
Zoom合肥/杭州/苏州https://www.zhipin.com/gongsi...

以上公司名单,基本不属于 996 的公司,相对接近 955/965 的水平,但是依旧要看部门和地区,不能保证完全准确性。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 2月16日

【Flutter 3-4】Flutter进阶教程——数据持久化sqflite使用

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

sqflite

数据持久化是在移动端开发中必不可少的技术手段。我们总是有一些用户信息,应用资源,列表数据等需要存储起来,这里我们主要来讲基于SQLite数据库的数据储存。
SQLite,是一款轻型的数据库。它的设计目标是嵌入式的,而且已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。更多详细信息可以参考:维基百科百度百科
Flutter已经帮助我们封装了操作SQLite的库,它就是:sqflite

集成sqflite库

使用sqflite第三方库需要我们在pubspec.yaml文件先添加库的名字和版本号
dependencies字段下添加:

sqflite: ^1.1.3

这里以1.1.3为例
添加完成后保存一下,VSCode默认会执行pub get帮我们把需要的库下载下来,同样我们也可以在项目根目录下执行pub get来手动拉取需要的库。

1. 创建本地数据文件

static Future<SqfliteManager> _initDataBase() async {
SqfliteManager manager = SqfliteManager();
String dbPath = await getDatabasesPath() + "/$sqlName";
if (manager.db == null) {
    manager.db = await openDatabase(
    dbPath,
    version: 1,
    onCreate: (db, version) async {
        /// 如果不存在 当前的表 就创建需要的表
        if (await manager.isTableExit(db, tableName) == false) {
        await db.execute(CREATE_DATA_TABLE);
        }
    },
    );
}
return manager;
}

首先我们通过getDatabasesPath()函数获取到本地保存数据库文件的路径,在此路径后面拼接上我们的数据库文件名字,就是保存数据库文件的路径。在iOS中该路径在沙河路径下的Documents文件夹内,在Android中时默认数据库目录。
之后我们声明一个Database对象,用来保存数据库操作对象

Database db;

先判断此对象是否存在,如果不存在我们调用openDatabase来创建
这里传入前面获取到的数据库地址,版本号,和onCreate回调函数。
onCreate回调内部判断是否存在我们需要使用的表名字,如果不存就执行创建数据库表的sql语句。

除了onCreate回调,还有onUpgradeonDowngradeonOpen等回调。另外一个参数singleInstance它表示当传入相同的数据库路径是否返回同一个的实例对象,默认是true

2. 插入数据

鉴于sqflite帮我们做了很多工作,所以在使用基本的也很简单

  /// 插入数据
  Future<int> insertData(Map<String, dynamic> value) async {
    return await db.insert(tableName, value);
  }

只需要传入表名字和要插入的数据就行
我们再来看一下insert函数的其他参数

Future<int> insert(String table, Map<String, dynamic> values,
      {String nullColumnHack, ConflictAlgorithm conflictAlgorithm});
  • nullColumnHack 是在传入的插入数据是空的时候 起到作用的

如果插入数据为空:
若不添加nullColumnHack则sql语句最终的结果将会类似insert into tableName()values(),这是不允许的。
若添加上nullColumnHack则sql语句将会变成insert into tableName (nullColumnHack)values(null),这是可以的。

  • conflictAlgorithm 是一个枚举,当插入的数据出现冲突或错误时,我们该使用哪种策略,有以下几个值
enum ConflictAlgorithm {
  rollback,
  abort,
  fail,
  ignore,
  replace,
}

3. 删除数据

删除数据的代码如下

  /// 删除一条数据
Future<int> deleteData(int id) async {
    return await db.delete(tableName, where: "id = ?", whereArgs: [id]);
}

来看一下详细的delete函数都有哪些参数:

Future<int> delete(String table, {String where, List<dynamic> whereArgs});
  • table是要删除数据所在的表的名字 例如:testTable
  • where是一个字符串,表示的是要删除的表达语句 例如:"id = ?"
  • whereArgs是一个数组,是用来补充where语句里面的?参数的 例如:[2]

当我们传入上面示例参数后,要标的意思就是:要删除testTable 表内 id = 2 的数据

4. 更新数据

删除数据代码如下

Future<int> updateData(Map<String, dynamic> value, int id) async {
    return await db.update(
        tableName,
        value,
        where: "id = ?",
        whereArgs: [id],
    );
}

详细的update函数如下

Future<int> update(String table, Map<String, dynamic> values,
      {String where,
      List<dynamic> whereArgs,
      ConflictAlgorithm conflictAlgorithm});

insert函数的参数基本一致,这里就不赘述了

5. 查询数据

直接来看查询语句的参数

Future<List<Map<String, dynamic>>> query(
    String table,  /// 表名字 是必传参数
    {bool distinct,  // 是否包含重复数据
    List<String> columns,  // 需要查询的列
    String where,       //  查询条件
    List<dynamic> whereArgs, // 查询条件参数
    String groupBy,  //按列分组 列的名字
    String having,   // 给分组设置条件
    String orderBy,   // 按列排序 asc/desc
    int limit,     // 限制查询结果数量
    int offset});  // 从第几条开始查询

查询语句的参数比较丰富,基本可以满足我们一些复杂场景的查询需求

好了,关于sqflite的使用就总结这些了。

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->sqflite_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 2月15日

【Flutter 3-3】Flutter进阶教程——http请求和FutureBuilder

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

异步请求

在移动开发过程中很多时候我们都需要依赖异步请求数据然后再来刷新UI。在用户打开界面的时候,先给出一个Loading提示,等数据请求完成后,我们再把数据展示在页面上,这是很常见的操作。

异步请求的好处就是不会阻塞主线程,用户虽然在“等”,但是页面不会卡死。
同步请求不适应于这种情况,同步请求会出现页面卡死现象,此时用户不能点击(即使点击也没有效果),体验非常不好。
所以大多数时候我们都是使用异步请求来获取数据

http 库

在Flutter中,我们可以用http库来做网络请求,它支持异步请求,并且有良好的API接口
使用http库的步骤:

  • 在项目中,打开pubspec.yaml文件
  • 找到dependencies字段,在下面添加http: ^0.12.2,其中0.12.2是版本号
  • 然后保存pubspec.yaml 并执行pub get命令把我们要使用的第三方库下载下来
具体 pub get命令的使用在之前的文章有介绍过

2021_02_02_http_request_yaml

然后在要使用http库的文件里面引入头文件

import 'package:http/http.dart' as http;

发送http请求的代码也比较简单,我们这里以get请求为例

http.get("https://cdn.jsdelivr.net/gh/johnson8888/blog_pages/images/request_demo_test.json");

只要传入要请求的地址即可,这里的URL地址是我自己上传的测试文件。

http 异步请求返回结果

前面我知道http库发送请求是支持异步的,那么异步请求的返回结果我们该如何接收呢?

  • 通过then函数获取,在get请求之后我们可以直接跟上then函数来作为回调,在回调内部可以获取到请求的结果
http.get(getURL).then((value) {
  print(value);
});

这样写确实很方便,但当我们的网络请求很多,并且一个网络请求依赖另外一个网络请求的时候,这个时候就会多个回调函数嵌套在一起(又称为回调地狱),代码就会显得很凌乱,很不适合Debug。

  • 使用await来接收异步操作的结果
var data = await http.get(getURL);

这样写代码就比上面的代码清爽多了
但是需要注意的是,如果函数内部有被await修饰的方法,那么函数应该被async来修饰,并且返回值需要被Future修饰,Future是一个延时计算的对象,在被await修饰的函数返回的时候才能拿到Future的具体值。
示例入下:

Future<Map> getData() async {
  var data = await http.get(getURL);
  return data.body; 
}

刷新页面

我们前面已经知道:调用setState()函数可以刷新页面,所以在http请求之后我们调用setState()函数即可刷新页面

http.get(getURL).then((value) {
  print(value);
  var data = jsonDecode(data.body);
  setState(() {
    /// 此处执行刷新页面的代码
  });
});

使用FutureBuilder来刷新页面

setState()固然是可以刷新页面,但是当我们页面内有多个网络请求的时候,就会不停的调用setState()来全量刷新页面,显然这就有点冗余。
Flutter为我们提供了更好的方式来实现获取数据并且刷新UI的操作,那就是FutureBuilder
来看它的初始化方法

const FutureBuilder({
  /// key
  Key key,
  /// 异步的操作
  this.future,
  /// 初始化数据
  this.initialData,
  /// 构建UI的函数
  @required this.builder,
})

由构造函数可见,我们需要传入future参数,也就是我们的耗时操作函数,还需要传入builder函数
builder方法里可以捕捉到两个参数BuildContext contextAsyncSnapshot snap
其中snap的属性会携带future的耗时函数的返回值,也就是说:在耗时操作函数返回结果之后,我们可以在builder方法内获取到这一返回值。
所以上面的请求我们也可以这么来实现:

FutureBuilder(
  future: getData(),
  builder: (BuildContext context, AsyncSnapshot snap) {
    /// 如果没有数据 我们就显示loading页面
    if (snap.hasData == false) {
      return CircularProgressIndicator();
    } else {
    /// 如果获取到了数据 我们就初始化一个 ListView来展示获取到的数据
      var dataSource = snap.data["tracks"];
      return ListView.builder(
        itemCount: dataSource.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(dataSource[index]["title"]),
            subtitle: Text(dataSource[index]["cover"]),
          );
        },
      );
    }
  },
),

在获取到数据之后,我们通过ListView.builder来构建一个ListView并返回,此时就完成了刷新UI的工作

我这里写的比较简单,只是用hasData来做为判断的依据
其实还有更优雅的做法:使用snap的另一个属性connectionState

enum ConnectionState {
  /// 没有异步任务,
  none,
  /// 异步任务正在等待
  waiting,
  /// 异步任务正在执行 或者 数据正在传输
  active,
  /// 异步任务已经终止.
  done,
}

我们也可以在connectionStatedone的时候在来判断是否存在数据
如果存在就展示数据!

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->http_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 2月14日

【Flutter 3-2】Flutter进阶教程——路由Router和导航Navigator以及传值

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

路由

在移动开发中,我们管页面之间的跳转叫做路由。在iOS中指的就是ViewController之间的跳转,在Android中就是Activity之间的跳转。路由是在移动端开发中非常重要的概念,它负责管理着各个页面之间的跳转还有传值工作,是必不可缺少的控件。

路由Map

为了方便我们管理跳转页面,Flutter为我们 提供了路由Map。
路由Map由在main.dart文件里面MaterialApp的参数routes管理,routes参数接收一个Map,Map里面就是我们项目的路由Map,你可以打开我的项目看到routes参数如下:

routes: {
  "/": (context) => MainPage(),
  "TextDemoPage": (context) => TextDemoPage(),
  "RaisedButtonDemoPage": (context) => RaisedButtonDemoPage(),
  "FlatButtonDemoPage": (context) => FlatButtonDemoPage(),
  "OutlineButtonDemoePage": (context) => OutlineButtonDemoePage(),
  "IconButtonDemoPage": (context) => IconButtonDemoPage(),
  "ContainerDemoPage": (context) => ContainerDemoPage(),
  "StatefulWidgetDemoPage": (context) => StatefulWidgetDemoPage(),
  "TextFieldDemoPage": (context) => TextFieldDemoPage(),
  "ImageDemoPage": (context) => ImageDemoPage(),
  "ColumnDemoPage": (context) => ColumnDemoPage(),
  "RowDemoPage": (context) => RowDemoPage(),
  "FlexibleDemoPage": (context) => FlexibleDemoPage(),
  "WrapDemoPage": (context) => WrapDemoPage(),
  "ListViewDemoPage": (context) => ListViewDemoPage(),
  "GridViewDemoPage": (context) => GridViewDemoPage(),
  "BottomNavigationBarDemoPage": (context) =>
      BottomNavigationBarDemoPage(),
  "RouterDemoPage": (context) => RouterDemoPage(),
  "RouterDemoPage2": (context) => RouterDemoPage2(),
},

其中key/对应的Value是整个Flutter项目的入口页面,这里需要另外一个很重要的参数initialRoute来配合使用
我们给initialRoute参数传值如下:

    initialRoute: "/",

这里表示的是Flutter项目的入口页面对应的key/,那么就会找到在routes/对应的页面,也就是MainPage()

需要注意的是:
默认我们新创建的Flutter项目中MaterialApp是带有home这个参数的,它也表示也是入口页面。如果我们想要要使用路由Map的方式来管理路由,一定需要把home参数删除掉。

Navigator.pushNamed

在我们声明好路由Map之后,我们就可以传入前面的key的值来实现页面的跳转工作,这个时候我们需要借助的API是Navigator.pushNamed

 @optionalTypeArgs
  static Future<T> pushNamed<T extends Object>(
    BuildContext context,    /// context
    String routeName, {     /// 路由Map中 key 的值
    Object arguments,        /// 参数
   }) {
    return Navigator.of(context).pushNamed<T>(routeName, arguments: arguments);
  }

只需要传入路由Map中key的值就可以实现跳转。
代码如下:

Navigator.pushNamed(context, "RouterDemoPage2");
由于我们是跨平台开发,Flutter帮助我们实现了跳转时候的转场动画,在iOS中动画是从右侧滑入到左侧,返回的时候同样是由左侧滑出到右侧。在Android则是由下方弹出显示到上方,返回的时候是由上方退出到下方弹出。

跳转传值

很多时候我们希望跳转的时候可以传值过去,这个时候我们可以通过自定义MaterialPageRoute来实现传值。

MaterialPageRoute({
    /// builder 方法
    @required this.builder,
    /// 配置信息
    RouteSettings settings,
    ///  默认情况下,当入栈一个新路由时,原来的路由仍然会被保存在内存中,如果想在路由没用的时候释放其所占用的所有资源,可以设置maintainState为false。
    this.maintainState = true,
    ///  表示新页面是否是全屏展示,在iOS中,如果fullscreenDialog为true,新页面将会从屏幕底部滑入
    bool fullscreenDialog = false,
})

我们只需要在构建新的页面的时候传入我们想要传递的参数即可

Navigator.of(context).push(MaterialPageRoute(builder: (context) {
  return RouterDemoPage3(passText: "Fulade");
}));

返回传值

传递返回值我们使用Navigatorpop方法即可

Navigator.pop(context, "pop value");

pop方法接收一个参数为返回的携带的参数,如果我们有多个参数,可以把它封装为ListMap即可。

返回值我们需要在push方法后面使用then来接收

Navigator.of(context)
    .push(MaterialPageRoute(builder: (context) {
  return RouterDemoPage3(passText: "Fulade");
})).then((value) {
  setState(() {
    title = value;
  });
});
then函数 涉及到了Dart语音中很重要的概念 await 和future,后面有机会我们再来详细的说。

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->router_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 1 收藏 1 评论 0

弗拉德 发布了文章 · 2月13日

【Flutter 3-1】Flutter进阶教程——底部导航栏BottomNavigationBar使用

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

BottomNavigationBar

BottomNavigationBarBottomNavigationBarItem 配合来共同展示Flutter里面的底部状态栏,底部状态栏是在移动端很重要的控件。

先看一下 BottomNavigationBar构造方法


BottomNavigationBar({
    // key
    Key key,
    /// BottomNavigationBarItem 数组
    @required this.items,
    /// 点击事件方法
    this.onTap,
    /// 当前选中的 元素下标
    this.currentIndex = 0,
    ///  底部导航栏的Z坐标
    this.elevation,
    /// 默认是 BottomNavigationBarType.shifting 一般我们使用 BottomNavigationBarType.fixed
    this.type,
    /// 选中项目颜色的值
    Color fixedColor,
    /// 背景颜色
    this.backgroundColor,
    /// BottomNavigationBarItem图标的大小
    this.iconSize = 24.0,
    /// 选中时图标和文字的颜色
    Color selectedItemColor,
    /// 未选中时图标和文字的颜色
    this.unselectedItemColor,
    // 选中时的子Item的样式
    this.selectedIconTheme,
    /// 未选中时的子Item的样式
    this.unselectedIconTheme,
    // 选中时字体大小
    this.selectedFontSize = 14.0,
    /// 未选中时的字体大小
    this.unselectedFontSize = 12.0,
    /// 选中的时候的字体样式
    this.selectedLabelStyle,
    /// 未选中时的字体样式
    this.unselectedLabelStyle,
    /// 是否为未选择的BottomNavigationBarItem显示标签
    this.showSelectedLabels = true,
    //// 是否为选定的BottomNavigationBarItem显示标签
    this.showUnselectedLabels,
    /// pc端或web端使用
    this.mouseCursor,
})

我们来做一个点击底部状态栏按钮切换颜色的Demo

class _BottomNavigationBarDemoPageState
    extends State<BottomNavigationBarDemoPage> {
  int selectedIndex = 0;
  List<Container> containerList = [
    Container(
      color: Colors.red,
    ),
    Container(
      color: Colors.blue,
    ),
    Container(
      color: Colors.yellow,
    ),
    Container(
      color: Colors.green,
    )
  ];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text("BottomNavigationBarDemo"),
        backgroundColor: Colors.blue,
      ),
      body: containerList[selectedIndex],
      bottomNavigationBar: BottomNavigationBar(
        /// 这个很重要
        type: BottomNavigationBarType.fixed,
        currentIndex: selectedIndex,
        onTap: (index) {
          setState(() {
            selectedIndex = index;
          });
        },
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            title: Text('F1'),
            icon: Icon(Icons.home),
          ),
          BottomNavigationBarItem(
            title: Text('F2'),
            icon: Icon(Icons.book),
          ),
          BottomNavigationBarItem(
            title: Text('F3'),
            icon: Icon(Icons.school),
          ),
          BottomNavigationBarItem(
            title: Text('F4'),
            icon: Icon(Icons.perm_identity),
          ),
        ],
      ),
    );
  }
}
  • Scaffold接收一个BottomNavigationBar作为bottomNavigationBar的参数,然后BottomNavigationBar接收一个items的数组,这个数组里面传入了4个BottomNavigationBarItem对象分别命名为F1F2F3F4
  • type参数传入的是BottomNavigationBarType.fixed,默认是BottomNavigationBarType.shifting,默认的效果是 只有在选中BottomNavigationBarItem时才会显示文字。设置成BottomNavigationBarType.fixed非选中状态下也会显示文字和图标
  • onTap实现的是一个方法,参数是被点击的当前BottomNavigationBarItem的下标,在这里被点击后调用setState来刷新页面的颜色

效果如下:

2020_01_29_bottom_navigation_bar

日常开发中以上效果基本能满足大多数需求
如果想要自定义下面Icon的样式,可以使用 BottomAppBar

这里也介绍两个不错的库

2020_01_29_bottom_th2

2020_01_29_bottom_th1

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->bottom_navigation_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 2月12日

【Flutter 2-12】Flutter手把手教程UI布局和Widget——网格列表GridView

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

GridView

GridView 是一个好用的网格布局控件,它的很多属性跟前面提到的ListView是一样的,重复的属性这里就不赘述了。我们重点了解初始化方法GridView.count的使用,还有两个代理SliverGridDelegateWithFixedCrossAxisCountSliverGridDelegateWithMaxCrossAxisExtent的参数以及使用。

GridView.count

我们先来看GridView.count的构造函数

GridView.count({
    /// key 
    Key key,
    /// 布局方向
    Axis scrollDirection = Axis.vertical,
    /// 是否 倒序显示
    bool reverse = false,
    /// ScrollController用于控制滚动位置和监听滚动事件
    ScrollController controller,
    /// 是否使用默认的controller
    bool primary,
    /// 滚动效果  可以通过此参数 设置 GridView 不可滚动
    ScrollPhysics physics,
    /// 是否根据子控件的总长度来设置 GridView 的长度,默认值为false
    bool shrinkWrap = false,
    ///  padding
    EdgeInsetsGeometry padding,
    /// 交叉轴 子控件的个数
    @required int crossAxisCount,
    /// 主轴方向的间距
    double mainAxisSpacing = 0.0,
    /// 交叉轴方向子元素的间距
    double crossAxisSpacing = 0.0,
    /// 子控件的宽高比例
    double childAspectRatio = 1.0,
    // 在 关闭屏幕时 是否释放子控件
    bool addAutomaticKeepAlives = true,
    /// 是否 避免列表项重绘
    bool addRepaintBoundaries = true,
    /// 该属性表示是否把子控件包装在IndexedSemantics里,用来提供无障碍语义
    bool addSemanticIndexes = true,
    // 预加载子控件的个数
    double cacheExtent,
    /// 子控件的数组
    List<Widget> children = const <Widget>[],
    /// 子控件的数量
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})

这里要说的是有两个比较重要的参数crossAxisSpacingchildAspectRatio,这个两个参数是用来定义子控件大小的。

假如我们设置 crossAxisSpacing = 2,那么每一行就会显示2个控件,而且控件的高度由childAspectRatio来确定。
childAspectRatio表示子控件的宽高比,假如我们设置为2 / 3,那么高就是宽的1.5倍,这样就可以计算子控件的大小了,并且按照 GridView设置好的方向来排列和布局子控件。

GridView.count(
    crossAxisCount: 3,
    childAspectRatio: 2 / 3,
    children: List.generate(
        50,
        (index) {
        return Card(
            child: Container(
            color: Colors.green,
                child: Center(
                    child: Text("$index"),
                ),
            ),
        );
        },
    ),
)

效果图如下:
2021_01_16_gridview_count

SliverGridDelegateWithFixedCrossAxisCount

除了GridView.count()这种构造方法,我们很多时候常用一个构造方法是GridView.builder(gridDelegate: itemBuilder:),它接收一个delegate对象,并且跟ListView一样接收一个itemBuilder方法。
SliverGridDelegateWithFixedCrossAxisCount的构造方法如下:

SliverGridDelegateWithFixedCrossAxisCount({
  @required this.crossAxisCount,
  this.mainAxisSpacing = 0.0,
  this.crossAxisSpacing = 0.0,
  this.childAspectRatio = 1.0,
})

可以看得出来它是把GridView.count()的几个参数封装了一下,具体的用法和效果跟GridView.count()一样,这里就不赘述了。

SliverGridDelegateWithFixedCrossAxisCount在很多情况下都能满足我们的布局需求,但是有一个不足,因为它设置的每一行数是一个定值。
当我们把屏幕旋转,此时原来的高度会变为现在的宽度,效果就会如下:
2021_01_16_gridview_la

所以我们可以使用下面delegate来解决这个问题。

SliverGridDelegateWithMaxCrossAxisExtent

我们可以在GridView.builder(gridDelegate: itemBuilder:)方法内传入另外一个参数SliverGridDelegateWithMaxCrossAxisExtent

构造方法如下:

const SliverGridDelegateWithMaxCrossAxisExtent({
    @required this.maxCrossAxisExtent,
    this.mainAxisSpacing = 0.0,
    this.crossAxisSpacing = 0.0,
    this.childAspectRatio = 1.0,
}) 

这里有一个比较重要的参数就是maxCrossAxisExtent,这个值表示的是子控件最大的宽度是多少。
举个例子:
手机的屏幕宽度为375,子控件之间的间距为0maxCrossAxisExtent的值设置为100,那么我们知道子控件的宽度取值区间在0~100之间。

  • 假设我们布局3个子控件,3 乘以最大值100等于300,很明显是不能满足布局在375的宽度上的。
  • 假设我们布局4个子控件,4乘以100等于400400 大于 375。我们知道宽度的取值区间在0~100375 / 4 = 93.75既满足了宽度小于100,又满足了可以充满375的宽度,那么子控件的个数为 4宽度为93.75

这就是maxCrossAxisExtent的用法。

其实GridView还可以做瀑布流效果,感兴趣的同学可以去查一下。

想体验以上的示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->gridview_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 2月10日

【Flutter 2-11】Flutter手把手教程UI布局和Widget——列表ListView

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

ListView

ListView是在移动端非常常见的控件,在大多数的展示场景中都离不开ListView。在Flutter中对ListView的封装也非常好,简单几行代码就可以满足我们布局一个滚动列表的需求。

先来看一下构造函数:

ListView({
    /// key
    Key key,
    /// 布局方向
    Axis scrollDirection = Axis.vertical,
    /// 是否 倒序显示
    bool reverse = false,
    /// ScrollController用于控制滚动位置和监听滚动事件
    ScrollController controller,
    /// 是否使用默认的controller
    bool primary,
    /// 滚动效果  可以通过此参数 设置 ListView 不可滚动
    ScrollPhysics physics,
    /// 是否根据子控件的总长度来设置ListView的长度,默认值为false
    bool shrinkWrap = false,
    ///  padding
    EdgeInsetsGeometry padding,
    /// 子控件高度
    this.itemExtent,
    // 在 关闭屏幕时 是否释放子控件
    bool addAutomaticKeepAlives = true,
    /// 是否 避免列表项重绘
    bool addRepaintBoundaries = true,
    /// 该属性表示是否把子控件包装在IndexedSemantics里,用来提供无障碍语义
    bool addSemanticIndexes = true,
    // 预加载子控件的个数
    double cacheExtent,
    /// 子控件数组
    List<Widget> children = const <Widget>[],
    /// 子控件的个数
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})

builder

Flutter给我们提供了四种构造ListView的方法,有ListView()ListView.builder()ListView.separated()ListView.custom()

构造函数描述
ListView()静态构造方法 初始化之前需要确定数据源的大小
ListView.builder()动态构造方法 可动态传入数据
ListView.separated()动态构造方法 可动态传入数据 可动态定制分割线的样式
ListView.custom()动态构造方法 需要传入SliverChildDelegate来做动态生成

静态构造方法和动态构造方法
ListView()是初始化的时候需要确定数据源的大小,一旦初始化成功后不能再次动态的插入数据。
ListView.builder()ListView.separated()ListView.custom()可以动态的插入数据,且能够更小的节省内存空间。
我们来看以下代码:

Flexible(
    child: ListView(
        children: List.generate(
            10,
            (index) {
                print("without builder index = $index");
                return Container(
                height: 60,
                child: Card(
                        color: Colors.blue,
                        child: Center(child: Text("$index")),
                    ),
                );
            },
        ),
    ),
),
Flexible(
    child: ListView.builder(
        itemCount: 10,
        itemExtent: 60,
        itemBuilder: (BuildContext contenxt, int index) {
            print("builder index = $index");
            return Container(
                height: 60,
                child: Card(
                color: Colors.red,
                child: Center(child: Text("$index")),
                ),
            );
        },
    ),
),

同样是需要初始化10个子控件,我们分别在List.generate方法和itemBuilder方法中做了打印操作
输出如下:

flutter: without builder index = 0
flutter: without builder index = 1
flutter: without builder index = 2
flutter: without builder index = 3
flutter: without builder index = 4
flutter: without builder index = 5
flutter: without builder index = 6
flutter: without builder index = 7
flutter: without builder index = 8
flutter: without builder index = 9
flutter: builder index = 0
flutter: builder index = 1
flutter: builder index = 2
flutter: builder index = 3
flutter: builder index = 4
flutter: builder index = 5
flutter: builder index = 6
flutter: builder index = 7

由输出的log可见,builder方法只初始化了7个子控件,ListView()方法完整的初始化了10个子控件。
builder方法是在需要使用的时候才会初始化,当页面滚动到第9个子控件的时候,这个时候才会初始化第9个子控件。
这样做的优势是:当我们的列表数据量很大的时候(比如说有成百上千个数据),我们只初始化几个来满足页面的显示需求,其他的控件在需要的时候,再做初始化这样就大大的帮助我们节省内存空间。

scrollDirection

ListView同时具备了水平布局和垂直布局的能力,我们只需要给scrollDirection设置不同的参数即可。
scrollDirection接收的参数值有两个Axis.verticalAxis.horizontal

Axis.vertical
效果如下
2021_01_16_listview_horizontal

Axis.horizontal
效果如下
2021_01_16_listview_vertical

reverse

参数reverse可以控制列表是按正序显示还是倒序显示。

reverse = true
表示倒序显示
2021_01_16_listview_reverse_true

reverse = false
表示正序显示
2021_01_16_listview_reverse_false

physics

某些情况下我们并不想要ListView可以滚动,只要把physics设置为NeverScrollableScrollPhysics即可。
physics还有其他两个比较重要的值:
ClampingScrollPhysics:在Android设备上有微光效果。
BouncingScrollPhysics:在iOS设备上有弹性效果。

separated

ListView.separated()构造函数中,我们可以传入一个自定义的Divider来作作为分隔的样式
这里我们来看一下Divider都有哪些参数:


const Divider({
    /// key
    Key key,
    // 高度
    this.height,
    /// 颜色的 高度
    this.thickness,
    /// 开头处的缩进
    this.indent,
    /// 结束处的缩进 
    this.endIndent,
    /// 颜色
    this.color,
})

height = 0
2021_01_16_listview_height_0

height = 10
2021_01_16_listview_height_10

thinkness = 10
2021_01_16_listview_thinkness_10

indent = 100
2021_01_16_listview_indent_100

end = 100
2021_01_16_listview_end_100

想体验以上示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->listview_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

弗拉德 发布了文章 · 2月9日

【Flutter 2-10】Flutter手把手教程UI布局和Widget——流式布局Wrap

作者 | 弗拉德
来源 | 弗拉德(公众号:fulade_me)

Wrap

FlutterWrap是流式布局控件,RowColumn在布局上是很好用,但是有一个缺点,如果当子控件数量过多导致RowColumn装载不下的时候,就会出现UI页面上的错误。Wrap可以完美的避免这个问题,当控件过多一行显示不全的时候,Wrap可以换行显示。

当然WrapRowColumn有着很多相似的地方。
我们先来看Wrap的构造函数:

Wrap({
    // Key
    Key key,
    // 子控件显示方向, 有垂直方向 水平方向两个值
    this.direction = Axis.horizontal,
    /// 子控件的 布局方式  跟Column的 mainAxisalignment类似 
    this.alignment = WrapAlignment.start,
    /// 子控件 主轴方向间距
    this.spacing = 0.0,
    /// 子控件 交叉方向的 布局方式
    this.runAlignment = WrapAlignment.start,
    /// 子控件 交叉方向间距
    this.runSpacing = 0.0,
    /// 交叉轴的对齐方式 与 Column 的crossAxisAlignment 一样
    this.crossAxisAlignment = WrapCrossAlignment.start,
    /// 书写方向 与 Column的 textDirection 一样
    this.textDirection,
    /// Wrap交叉轴方向上子控件的布局方向
    this.verticalDirection = VerticalDirection.down,
    /// 裁剪方式
    this.clipBehavior = Clip.hardEdge,
    /// 子控件
    List<Widget> children = const <Widget>[],
}) 

下面我们就来看看这些参数

direction

direction有两个参数值Axis.horizontalAxis.vertical,很明显它管理着Wrap的是水平布局还是垂直布局。
Axis.horizontal表示子控件按水平方向布局,Axis.vertical表示子控件按垂直方向布局显示。

Axis.horizontal
效果如下:

20202_01_15_wrap_horizontal

Axis.vertical
效果如下:
20202_01_15_wrap_vertical

alignment

alignment接收一个WrapAlignment类型的枚举,WrapAlignment共有六个枚举值,如下:

WrapAlignment的枚举值与效果与 ColumnmainAxisAlignment效果一样,想了解的可以看之前的文章
枚举值描述
start与 开始的位置对齐
end与 结束的位置对齐
center居中对齐
spaceBetween把剩余的空间拆分成n-1份(n是子控件的个数) 每一份都插入到子控件之间
spaceEvenly把剩余的空间拆分成n+1份(n是子控件的个数) 然后均匀分布
spaceAround把剩余空间拆分成 2n 份(n是子控件的个数) 每个子控件上下各放一份

WrapAlignment.start
20202_01_15_wrap_alignment_start

WrapAlignment.center
20202_01_15_wrap_alignment_center

WrapAlignment.end
20202_01_15_wrap_alignment_end

WrapAlignment.spaceBetween
20202_01_15_wrap_alignment_between

WrapAlignment.spaceEvenly
20202_01_15_wrap_alignment_spaceEvenly

WrapAlignment.spaceAround
20202_01_15_wrap_alignment_spaceAround

runAlignment

runAlignment接收一个WrapAlignment类型的枚举,WrapAlignment共有六个枚举值(跟alignment的枚举值是一样的),runAlignment控制是的是Wrap布局交叉方向的对齐方式。
如果Wrap的是水平方向布局,runAlignment控制的就是Wrap垂直方向的对齐方式。

verticalDirection

verticalDirection有两个值VerticalDirection.downVerticalDirection.up,表示从哪个方向开始布局。
VerticalDirection.down
2021_01_15_wrap_down

VerticalDirection.up
2021_01_15_wrap_up

注意 当设置为VerticalDirection.up的时候,第一个控件也就是Number 0是从最低端最左侧开始的。

spacing 和 runSpacing

spacing表示子控件主轴方向间距,runSpacing子控件在交叉方向间距。
在一个水平方向布局的Wrap为中,spacing表示的就是水平方向子控件之间的间距,runSpacing表示的就是子控件在垂直方向上的间距。

space
space等于10的样子
2021_01_15_wrap_space_10

space等于40的样子
2021_01_15_wrap_space_40
runSpacing
runSpacing等于10的样子
2021_01_15_wrap_runSpace_10

runSpacing等于40的样子
2021_01_15_wrap_runSpace_40

想体验以上示例的运行效果,可以到我的Github仓库项目flutter_app->lib->routes->wrap_page.dart查看,并且可以下载下来运行并体验。


公众号

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 7 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-11-13
个人主页被 2.9k 人浏览