UI综合案例
- 屏幕适配
- 自定义AppBar
- 多类型列表项
- 悬浮框控件
- 第三方库使用
- 状态管理框架
本案例需要去 Github 仓库 下载测试服务器,按要求配置
屏幕适配
分辨率
分辨率,又称解析度、解像度,可以细分为显示分辨率、图像分辨率、打印分辨率和扫描分辨率等。
显示分辨率(屏幕分辨率)是屏幕图像的精密度,是指显示器所能显示的像素有多少。由于屏幕上的点、线和面都是由像素组成的,显示器可显示的像素越多,画面就越精细,同样的屏幕区域内能显示的信息也越多,所以分辨率是个非常重要的性能指标。可以把整个图像想象成是一个大型的棋盘,而分辨率的表示方式就是所有经线和纬线交叉点的数目。显示分辨率一定的情况下,显示屏越小图像越清晰,反之,显示屏大小固定时,显示分辨率越高图像越清晰。

1080p和720p是什么意思
P指逐行扫描,1080p指横向或纵向达到1080个像素,如果屏幕宽高是16:9,那就是1920X1080。同理,720p就是1280X720分辨率。
计算图片在内存中的大小
高 * 宽 * 像素占用的字节数
一个像素占用的字节数与图片的编码格式有关,通常的ARGB_4444每个用4位,共占用两字节,ARGB_8888则每个用8位,共占四字节。
屏幕大小
大家平常说的手机屏幕几寸,是指屏幕对角线的长度,单位是英寸。

常见单位
in(inch 英寸):物理长度单位。是英国(英联邦)及其前殖民地的长度单位,1英寸=2.54厘米px(pixel 像素):,电子屏幕上组成一幅图画或照片的最基本单元pt(point 点):原印刷行业常用单位,等于1/72英寸,ios开发使用ppi(pixel per inch):每英寸包含的像素个数,该值越高,则屏幕越细腻dpi(dot per inch):每英寸包含多少点,该值越高,则图片越细腻dp(dip,Density-independent pixel):,独立密度像素。意即与density密度(dpi)无关。是安卓开发用的长度单位,使用dp作为单位,不用操心屏幕大小,屏幕的dpi,它显示的效果始终保持一致。sp(scale-independent pixel):安卓开发中用的字体大小单位
dpi最初用于衡量打印物上每英寸的点数密度。值越小图片越不精细。当dpi的概念用在计算机屏幕上时,就应称之为ppi。ppi就是计算机屏幕上每英寸可以显示的像素点的数量,因此,ppi和dpi是等同的。
转换公式
dpi = ppi
1pt = (dpi / 72) px
1dp =(dpi / 160)px

安卓规格

为什么规定在160dpi的屏幕上,1dp = 1px呢?
因为第一款Android设备(HTC的T-Mobile G1)是属于160dpi。
安卓适配原理
px = dp * (dpi / 160) = dp * (屏幕对角线像素 / in(屏幕尺寸) / 160)
dp不变且in不变时,屏幕对角线像素数量越大,px越大。
假如不使用dp,而使用原始的px单位,例如在手机屏幕上绘制一条直线,如果在密度为160dpi(每英寸160个像素点),宽度是1英寸的手机上,这条直线长度是160px(占据160个像素点),也就是直线长度正好是手机的宽度,但在240dpi(每英寸240个像素点),宽度是1英寸的手机上安装了这个app,长度只有屏幕宽度的2/3。
iPhone规格
| 设备 | 屏幕尺寸 | 分辨率(pt) | Reader | 分辨率(px) | 渲染后 | PPI |
|---|---|---|---|---|---|---|
| iPhone 3GS | 3.5吋 | 320x480 | @1x | 320x480 | 163 | |
| iPhone 4/4s | 3.5吋 | 320x480 | @2x | 640x960 | 330 | |
| iPhone 5/5s/5c | 4.0吋 | 320x568 | @2x | 640x1136 | 326 | |
| iPhone 6 | 4.7吋 | 375x667 | @2x | 750x1334 | 326 | |
| iPhone 6Plus | 5.5吋 | 414x736 | @3x | 1242x2208 | 1080x1920 | 401 |
| iPhone 6s | 4.7吋 | 375x667 | @2x | 750x1334 | 326 | |
| iPhone 6Plus | 5.5吋 | 414x736 | @3x | 1242x2208 | 1080x1920 | 401 |
| iPhone 7 | 4.7吋 | 375x667 | @2x | 750x1334 | 326 | |
| iPhone 7Plus | 5.5吋 | 414x736 | @3x | 1242x2208 | 1080x1920 | 401 |
| iPhone 8 | 4.7吋 | 375x667 | @2x | 750x1334 | 326 | |
| iPhone 8Plus | 5.5吋 | 414x736 | @3x | 1242x2208 | 1080x1920 | 401 |
| iPhone X | 5.8吋 | 375x812 | @3x | 1125x2436 | 458 |
Flutter 规格
Flutter中为了处理屏幕的适配,使用逻辑像素的概念,见官方文档 devicePixelRatio,在不同手机上,Flutter框架会动态的将逻辑像素换算成真实的物理像素。逻辑像素的概念与安卓中的dp类似,但概念不能混淆,关于该值的详细内容,参见我的博客《Flutter 大小单位详解》。
物理像素 = devicePixelRatio * 逻辑像素

但实际开发过程中,往往需要直接将设计师给出的尺寸标注换算成Flutter的逻辑像素。这个动态换算的过程,我们可以使用第三方库 flutter_screenutil 来简化编码。
悬浮框
OverlayState overlayState = Overlay.of(this.ctx);
// 创建OverlayEntry
OverlayEntry entry = OverlayEntry(builder:(ctx){
return Container();
});
// 往 Overlay 中插入插入OverlayEntry
overlayState.insert(overlayEntry);
// 移除
entry.remove();
第三方库使用
flutter_staggered_grid_view (交错网格)的使用
构建方式
StaggeredGridView.count构建一个横轴方向固定Tile个数的网格StaggeredGridView.countBuilder同上,适用于动态构建的情景StaggeredGridView.extent构建一个横轴方向上固定最大宽度的Tile网格StaggeredGridView.extentBuild同上,适用于动态构建的情景
StaggeredTile 的构建
StaggeredTile.count固定横轴和主轴上的数量(设置横轴和主轴占据的单元数)StaggeredTile.extent设置横轴上的数量和主轴上的最大范围StaggeredTile.fit设置横轴上的数量,而主轴上则自适应
需要注意,StaggeredGridView的列数,等于crossAxisCount除以StaggeredTile上设置的横轴的数量。
状态管理框架 Provider
现有的 providers
provider 为不同类型的对象暴露了几种不同的 "provider"。provider 文档
所有可用对象的完整清单见这里
| 名字 | 描述 |
|---|---|
| Provider | provider的最基本形式。它接受一个值并将其公开,不管这个值是什么。 |
| ListenableProvider | 一个特定的Listenable对象的provider。ListenableProvider将监听对象,并要求依赖它的widget在监听器被调用时进行重建。 |
| ChangeNotifierProvider | 用于ChangeNotifier的ListenableProvider的规范。 它将在需要时自动调用ChangeNotifier.dispose。 |
| ValueListenableProvider | 监听一个ValueListenable,并且只暴露ValueListenable.value。 |
| StreamProvider | 监听一个流,并暴露出最新发出的值。 |
| FutureProvider | 接受Future并在Future完成时更新依赖关系。 |
需要注意,从3.0.0版本开始,有了一种新的Provider——ProxyProvider。它是一个将其他Provider的多个值合并到一个新对象中的Provider,并将结果发送给这个 Provider。每当它所依赖的一个Provider更新时,这个新对象就会被更新。
- 声明数据 Model
- 创建共享数据
- 获取共享数据
获取共享数据有几种方式
- 使用
Provider.of<T>(context)获取指定泛型的实例对象 - 使用
Consumer获取 - 使用
Selector获取 - 使用扩展方法
context.watch/read/select获取
聊天室案例
主要掌握Flutter中WebSocket的使用
服务端
json 模型
{
"event": "message",
"data": {
"type": "text",
"content": "",
"from": ""
}
}
event类型:message/name/name_ack/broadcast/error/keepalive
Flutter 客户端
-
Flutter中用于websocket 访问的库
为 websocket 添加心跳机制
使用 protobuf 改造案例
全称是Google Protocol Buffer,是一种高效轻便的结构化数据存储方式,可用于(数据)通信协议、数据存储等。它与语言无关,平台无关,且高效性能好。

Protobuf 语法简介
Protobuf 使用.proto文件来定义消息,一个简单示例如下
// 语法版本
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page = 2;
int32 result = 3;
}
整体看起来和Dart中的类声明相似,message后面指定的名称,会映射成Dart的类名。消息定义中的每个字段都有一个唯一的数字 ,这是字段编号,这些字段编号用于以消息二进制格式标识字段,一旦消息类型被使用,就不应该更改这些字段。
消息嵌套
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page = 2;
// 声明Result类型消息
message Result {
string url = 1;
string title = 2;
}
// 在SearchRequest中定义一个Result字段
Result result = 3;
}
这些消息中还能定义枚举。枚举的第一个常量编号映射到零,意即必须有一个字段编号为0的值
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page = 2;
int32 result = 3;
// 使用snippets修改,声明列表,此处为字符串列表
repeated string snippets = 3;
// 声明一个枚举类型
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
}
// 在SearchRequest中定义一个枚举字段
Corpus corpus = 4;
}
在Dart 中的使用
下载protobuf 工具
安装dart语言的protoc插件
pub global activate protoc_plugin安装插件后,需要将该插件路径配置到PATH环境变量中,Windows下是
%APPDATA%\Pub\Cache\bin,Mac 或 Linux系统下是$HOME/.pub-cache/bin将
.proto文件编译成dart文件protoc --dart_out=. test.proto将生成的带pb的dart文件拷贝到项目中
项目中引入protobuf 依赖
dependencies: protobuf: ^1.0.1
附录:字段类型对应表
| .proto | Notes | Java | Python[2] | Go | Dart |
| double | double | float | float64 | double | |
| float | float | float | float32 | double | |
| int32 | 使用可变长度编码。编码负数效率低,如果您的字段可能具有负值,请改用sint32。 | int | int | int32 | int |
| int64 | 使用可变长度编码。负数编码效率低下,如果您的字段可能具有负值,请改用sint64。 | long | int/long[3] | int64 | Int64 |
| uint32 | 使用可变长度编码。 | int[1] | int/long[3] | uint32 | int |
| uint64 | 使用可变长度编码。 | long[1] | int/long[3] | uint64 | Int64 |
| sint32 | 使用可变长度编码。有符号的int值。这些比普通的int32更有效地编码负数。 | int | int | int32 | int |
| sint64 | 使用可变长度编码。有符号的int值。这些比普通的int64更有效地编码负数。 | long | int/long[3] | int64 | Int64 |
| fixed32 | 始终为四个字节。如果值通常大于228,则比uint32更有效。 | int[1] | int/long[3] | uint32 | int |
| fixed64 | 始终为八个字节。如果值通常大于256,则比uint64更有效。 | long[1] | int/long[3] | uint64 | Int64 |
| sfixed32 | 始终为四个字节。 | int | int | int32 | int |
| sfixed64 | 始终为八个字节。 | long | int/long[3] | int64 | Int64 |
| bool | boolean | bool | bool | bool | |
| string | 字符串必须始终包含UTF-8编码或7位ASCII文本,并且不能超过232。 | String | str/unicode[4] | string | String |
| bytes | 可以包含不超过232个任意字节序列。 | ByteString | str | []byte | List |
[1]在Java中,无符号的32位和64位整数使用带符号的对等体表示,最高位仅存储在符号位中。
[2]在所有情况下,对字段设置值都会进行类型检查以确保其有效。
[3]64位或无符号32位整数在解码时始终表示为long,但是如果在设置字段时给出了int,则可以为int。 在所有情况下,该值都必须适合设置时表示的类型。 参见[2]。
[4]Python字符串在解码时表示为unicode,但如果给出了ASCII字符串,则可以为str(此字符串可能会发生变化)。
公众号“编程之路从0到1”