分类 默认分类 下的文章

abstract class RzBaseModel {
const RzBaseModel();

factory RzBaseModel.fromJson(Map<String, dynamic> json) {

return switch (json['type']) {
  'user' => UserModel.fromJson(json),
  'product' => ProductModel.fromJson(json),
  'order' => OrderModel.fromJson(json),
  String() => throw FormatException('Unknown type: ${json['type']}'),
  _ => throw const FormatException('Missing type field in JSON'),
};

}
}

主要特点:

  1. 模式守卫 (Pattern Guards):使用 when 添加额外条件
    解构模式:可以直接从对象或列表中提取值
  2. 通配符模式:使用 _ 匹配任意值
    逻辑运算符:可以使用 &&、|| 组合多个模式

变量声明:可以在模式中声明变量

// API 响应处理
void handleApiResponse(Map<String, dynamic> response) {
switch (response) {

case {'status': 200, 'data': Map data}:
  handleSuccessResponse(data);

case {'status': int code, 'error': String message} when code >= 400:
  handleErrorResponse(code, message);

case {'status': int code} when code >= 500:
  handleServerError(code);

default:
  handleUnknownResponse(response);

}
}

基本是个静态页面,没啥有价值的东西

  1. 使用getx管理路由
    void main() {
    runApp(GetMaterialApp(
        title: 'Flutter Luckin Coffee',
        initialRoute: '/',
        getPages: pages,
    ));
    }


    List<GetPage<dynamic>> pages = [
    // toolbar
    GetPage(name: '/', page: () => Toolbar()),
    GetPage(name: '/mine', page: () => Toolbar()),
    GetPage(name: '/shopping_cart', page: () => Toolbar()),
    GetPage(name: '/menu', page: () => Toolbar()),

    // other
    GetPage(name: '/login_method', page: () => LoginMethod()),
    GetPage(name: '/login_mail', page: () => LoginMail()),
    GetPage(name: '/user_agreement', page: () => UserAgreement()),
    GetPage(name: '/order_evaluation', page: () => OrderEvaluation()),
    GetPage(name: '/order_confirm', page: () => OrderConfirm()),
    GetPage(name: '/order_remark', page: () => OrderRemark()),
    GetPage(name: '/order_detail', page: () => OrderDetail()),
    GetPage(name: '/coupon', page: () => Coupon()),
    GetPage(name: '/self_store', page: () => SelfStore()),
    GetPage(name: '/store_detail', page: () => StoreDetail()),
    GetPage(name: '/dining_code', page: () => DiningCode()),
    ];

源码项目地址:
flutter版本斗鱼源码

attention

  1. 数据来源于mock网络请求,但是用的是python tornado,与项目需要的json-server不吻合,跳过
  2. 使用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.
}
  1. 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的链式调用:

  1. 最最常见的那种嵌套,比如container里面设置一堆属性什么的,思路相似:通过一系列的配置构造一个复杂的对象
  2. 配置类的调用
    在这个例子中,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(实时双向通信协议)

  1. 握手阶段:
  2. 数据传输阶段; 双向,支持文本和二进制数据,双方随时可以发送和接收消息,无需再次建立连接
  3. 断开连接阶段; 任何一方都可以断开

使用场景:实时聊天,协同编辑,实时数据更新,多人游戏开发,我们的项目目前应该不需要

    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