源码项目地址:
flutter版本斗鱼源码
attention
- 数据来源于mock网络请求,但是用的是python tornado,与项目需要的json-server不吻合,跳过
- 使用bloc做状态管理,用bloc控制首页tab跳转,本项目目前只用getx,跳过
基础概念与专业名词
1. 空安全 null safety
代码编写时要求明确处理空值引用
2. 抽象类abstract 的使用与实例化
// 所有Widget继承的抽象类
abstract class DYBase {
static final baseSchema = 'http';
static final baseHost = '192.168.97.142';
static final basePort = '1236';
static final baseUrl = '${DYBase.baseSchema}://${DYBase.baseHost}:${DYBase.basePort}';
// 默认斗鱼主题色
static final defaultColor = Color(0xffff5d23);
// 初始化设计稿尺寸
static final double dessignWidth = 375.0;
static final double dessignHeight = 1335.0;
static final double statusBarHeight = MediaQueryData.fromWindow(window).padding.top;
// flutter_screenutil px转dp
num dp(double dessignValue) => ScreenUtil.getInstance().setWidth(dessignValue);
}
————————————————————————————————————————————
// 定义一个抽象类 Animal
abstract class Animal {
// 定义一个抽象方法 makeSound,子类必须实现这个方法
void makeSound();
// 定义一个具体的方法 move,子类可以直接使用或重写
void move() {
print("The animal moves.");
}
// 还可以定义一些静态方法和属性
static String category = "Animal";
// 静态方法,不需要子类实现
static void printCategory() {
print("This is a $category.");
}
}
————————————————————————————————————————
// 定义一个具体的类 Dog,它继承自抽象类 Animal
class Dog extends Animal {
// 实现抽象类中的抽象方法 makeSound
@override
void makeSound() {
print("Woof! Woof!");
}
// 可以选择重写父类中的方法 move
@override
void move() {
print("The dog runs.");
}
}
————————————————————————————————————————————————————
// 定义一个具体的类 Cat,它也继承自抽象类 Animal
class Cat extends Animal {
// 实现抽象类中的抽象方法 makeSound
@override
void makeSound() {
print("Meow! Meow!");
}
// 在这里,Cat 类没有重写 move 方法,所以它继承了 Animal 类中的 move 方法
}
————————————————————————————————————————
void main() {
// 由于 Animal 是一个抽象类,我们不能直接实例化它
// Animal myAnimal = new Animal(); // 这会报错
// 我们可以实例化具体的子类 Dog 和 Cat
Dog myDog = Dog();
Cat myCat = Cat();
// 调用子类的方法
myDog.makeSound(); // 输出: Woof! Woof!
myDog.move(); // 输出: The dog runs.
myCat.makeSound(); // 输出: Meow! Meow!
myCat.move(); // 输出: The animal moves.(因为 Cat 没有重写 move 方法)
// 调用静态方法
Animal.printCategory(); // 输出: This is a Animal.
Dog.printCategory(); // 输出: This is a Animal.(因为静态方法属于类本身,不依赖于实例)
Cat.printCategory(); // 输出: This is a Animal.
}
- flutter,export ‘AAA.dart'
表示当前文件想要到处AAA文件中定义的所有公共成员,也就是说,任何倒入当前文件的文件,也能够访问AAA里面的公共类,函数,而无需直接导入AAA.dart;
这样做的好处?
相关功能房子啊不同文件,并用主文件重新到处他们;只需要一个文件就可以访问多个模块的功能;封装,控制哪些成员被导出;但export只会重新导出公共成员;
4. 网络服务
写在公共里面
// 接口URL
abstract class API {
static const nav = '/dy/flutter/nav'; // 首页顶部导航
static const swiper = '/dy/flutter/swiper'; // 首页轮播图
static const broadcast = '/dy/flutter/broadcast'; // 首页推荐广播
static const liveData = '/dy/flutter/liveData'; // 首页直播视频列表
static const lotteryConfig = '/dy/flutter/lotteryConfig'; // 抽奖配置信息
static const lotteryResult = '/dy/flutter/lotteryResult'; // 点击抽奖结果
static const yubaList = '/dy/flutter/yubaList'; // 鱼吧列表
static const areaList = '/static/areaTel.json'; // 国家地区号码静态文件
}
1.1 禁用本地缓存
DioCacheManager,负责创建和管理HTTP请求的缓存;
不缓存,一般是因为应用需要实时数据,或者出于安全考虑,不希望存储敏感数据
final dioManager = DioCacheManager(
CacheConfig(
skipDiskCache: true
)
);
http请求
回到base.dart,获取base URL;
响应类型为json
连接服务器超时5秒,请求失败,接收时间超过三秒,请求失败
..interceptors.add():
通过链式调用(..)给实例添加了一个拦截器,拦截器通常用于在请求发送前,或者响应接收后执行一些操作,比如添加认证信息,处理错误等等
// http请求
final httpClient = Dio(BaseOptions(
baseUrl: DYBase.baseUrl,
responseType: ResponseType.json,
connectTimeout: 5000,
receiveTimeout: 3000,
))..interceptors.add(
dioManager.interceptor,
);
5. 链式调用 Chaining Call
在一个表达式中,连续调用同一个对象上的多个方法,每个方法返回的都是对象本身;通常是this;这样可以紧接着调用下一个方法;
面向对象编程中的常见模式;
在面向对象编程中,链式调用是一种常见的模式,特别是在构建器(Builders)模式、流式接口(Fluent Interfaces)或命令模式(Command Pattern)中。
JS的链式调用:
let person = {
firstName: "John",
lastName: "Doe",
setFirstName: function(firstName) {
this.firstName = firstName;
return this; // 返回对象本身,以便链式调用
},
setLastName: function(lastName) {
this.lastName = lastName;
return this; // 返回对象本身,以便链式调用
},
getFullName: function() {
return `${this.firstName} ${this.lastName}`;
}
};
// 使用链式调用设置名字并获取全名
let fullName = person.setFirstName("Jane").setLastName("Smith").getFullName();
console.log(fullName); // 输出 "Jane Smith"
Flutter的链式调用:
- 最最常见的那种嵌套,比如container里面设置一堆属性什么的,思路相似:通过一系列的配置构造一个复杂的对象
- 配置类的调用
在这个例子中,options,transformer,interceptor都返回dio实例本身
final dio = Dio()
.options(BaseOptions(
baseUrl: "https://api.example.com/",
connectTimeout: 5000,
receiveTimeout: 3000,
))
.transformer(DioTransformer.defaultBuilder())
.interceptor(LoggingInterceptor());
6. websocket(实时双向通信协议)
- 握手阶段:
- 数据传输阶段; 双向,支持文本和二进制数据,双方随时可以发送和接收消息,无需再次建立连接
- 断开连接阶段; 任何一方都可以断开
使用场景:实时聊天,协同编辑,实时数据更新,多人游戏开发,我们的项目目前应该不需要
import 'package:web_socket_channel/io.dart';
class SocketClient {
IOWebSocketChannel? channel; // 使用可空类型来表示连接可能未初始化
// 构造函数,现在它符合 Dart 的命名规范
SocketClient(String url) {
// 将 URL 作为参数传递,增加了灵活性
this.channel = IOWebSocketChannel.connect(url).catchError((error) {
// 简单的错误处理,您可能希望在这里添加更复杂的逻辑
print('WebSocket connection error: $error');
});
}
// 可以在需要时关闭连接
void close() {
channel?.close();
channel = null; // 清除引用,避免内存泄漏
}
// 其他与 WebSocket 交互的方法可以添加在这里
}
// 使用示例
// 假设 DYBase.baseHost 和 DYBase.basePort 是已经定义好的变量
void main() {
String url = 'ws://${DYBase.baseHost}:${DYBase.basePort}/socket/dy/flutter';
SocketClient socketClient = SocketClient(url);
// 使用 socketClient.channel 进行通信...
// 当不再需要 WebSocket 连接时,调用 close 方法
// socketClient.close();
}
7. 本地缓存
import 'dart:io';
import 'package:fluttertoast/fluttertoast.dart';
需要用到dart:io那个包...查一下suqin那个包里面用了吗?
class DYio {
// 获取缓存目录
static Future<String> getTempPath() async {
var tempDir = await getTemporaryDirectory();
return tempDir.path;
}
// 设置缓存
static Future<void> setTempFile(String fileName, String str) async {
String tempPath = await getTempPath();
await File('$tempPath/$fileName.txt').writeAsString(str);
}
// 读取缓存
static Future<dynamic> getTempFile(String fileName) async {
String tempPath = await getTempPath();
try {
String contents = await File('$tempPath/$fileName.txt').readAsString();
try {
return jsonDecode(contents);
} catch (_) {
// 如果不是 JSON 格式,可以返回原始字符串或其他默认值
return contents;
}
} catch (e) {
print('$fileName: 缓存不存在或读取失败');
return null; // 或者抛出一个更具体的异常
}
}
// 清缓存
static Future<void> clearCache() async {
try {
Directory tempDir = await getTemporaryDirectory();
await _delDir(tempDir);
Fluttertoast.showToast(msg: '清除缓存成功');
} catch (e) {
Fluttertoast.showToast(msg: '清除缓存失败');
}
}
// 递归方式删除目录
static Future<void> _delDir(FileSystemEntity file) async {
if (file is Directory) {
final List<FileSystemEntity> children = await file.list();
for (final FileSystemEntity child in children) {
await _delDir(child);
}
}
await file.delete();
}
}
8. 强制竖屏
void main() {
WidgetsFlutterBinding.ensureInitialized();
// 强制竖屏
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown
]);
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(statusBarColor: Colors.transparent));
runApp(DyApp());
}
9. Map映射
// StringMap.dart
class StringMap {
// 私有映射,用于存储键值对
private Map<int, String> _map;
// 构造函数,初始化映射
StringMap() {
_map = {
1: 'One',
2: 'Two',
3: 'Three',
};
}
// 公共方法,根据键返回对应的字符串值
// 如果键不存在,则返回 'Key not found'
String getString(int key) {
return _map[key] ?? 'Key not found';
}
// (可选)公共方法,允许外部向映射中添加键值对
void addString(int key, String value) {
_map[key] = value;
}
}
封装好的映射的调用
// main.dart
// 导入 StringMap 类所在的文件
import 'StringMap.dart';
void main() {
// 创建 StringMap 类的实例
StringMap stringMap = StringMap();
// 调用 getString 方法并打印结果
print(stringMap.getString(1)); // 输出: One
print(stringMap.getString(4)); // 输出: Key not found(因为键 4 不存在)
// (可选)使用 addString 方法添加新的键值对
stringMap.addString(4, 'Four');
print(stringMap.getString(4)); // 输出: Four
}
10. 使用rxdart包完成消息全局推送
用全局getx,把推送组件放overlay或者showdialog里面也是一样的;
创建一个全局的消息推送服务,例如 GlobalMessageBus。这个服务将使用 PublishSubject 来广播消息,允许任何监听器订阅并接收这些消息。
// global_message_bus.dart
import 'package:rxdart/rxdart.dart';
class GlobalMessageBus {
// 使用 PublishSubject 来广播消息
final _subject = PublishSubject<String>();
// 获取消息流
Stream<String> get messages => _subject.stream;
// 发送消息
void sendMessage(String message) {
_subject.sink.add(message);
}
// 关闭消息流(通常在应用关闭时调用)
void dispose() {
_subject.close();
}
}
// 创建一个全局可访问的实例
final globalMessageBus = GlobalMessageBus();
————————————————————————————
在一个组件中发送消息
// some_component.dart
import 'package:flutter/material.dart';
import 'global_message_bus.dart';
class SomeComponent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// 发送消息到全局总线
globalMessageBus.sendMessage("Hello, this is a global message!");
},
child: Text("Send Global Message"),
);
}
}
————————————————————————————
在一个组件中接收消息
// another_component.dart
import 'package:flutter/material.dart';
import 'global_message_bus.dart';
class AnotherComponent extends StatefulWidget {
@override
_AnotherComponentState createState() => _AnotherComponentState();
}
class _AnotherComponentState extends State<AnotherComponent> {
String _receivedMessage = "";
@override
void initState() {
super.initState();
// 订阅全局消息流
globalMessageBus.messages.listen((message) {
setState(() {
_receivedMessage = message;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Global Message Listener"),
),
body: Center(
child: Text(_receivedMessage),
),
);
}
}
11.不知道何时出现的登陆页
做成弹窗
详见service.dart
_
// login
static void showLogin(context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return LoginDialog();
}
);
}
12. 拍照,ImagePicker
13. 继承两个类的方法
class LoginDialog extends Dialog with DYBase {
LoginDialog({Key key}) : super(key: key);
......
}
14. NestedScrollView 嵌套式滚动试图
那种套娃式的tab
lib => comment => index.dart