源码项目地址:
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

标签: none

已有 85 条评论

  1. 作者的观点新颖且实用,让人在阅读中获得了新的思考和灵感。

  2. 五郎八卦棍

  3. 快乐赢家

  4. 怪物大乱捣

  5. 熄灯追缉令

  6. 三个秘密

  7. 第三帝国邪恶的欺骗

  8. 碧血剑

  9. 无限旅程

  10. 幕间子

  11. 九月五日

  12. 大有前途

  13. 神出鬼没ghosted

  14. 刺客公会

  15. 条子骇客

  16. 我的离婚派对

  17. 杰西

  18. 爱在离别时

  19. 战锋尖峰对决

  20. 梦幻岛

  21. 霍元甲之精武天下

  22. 蓝百万2

  23. 帕洛玛之旅

  24. 我的离婚派对

  25. 第三帝国邪恶的欺骗

  26. 赌神2

  27. 燃爱之高岭之花第二季

  28. 鬼娃回魂3

  29. 假日旅途希腊之旅

  30. 阿胡贾

  31. 血十三

  32. 烈火中

  33. 新河东狮吼

  34. 猫头鹰

  35. 沙丘

  36. 如积雪般的永寂

  37. 哈利波特与密室

  38. 蓬莱仙踪

  39. 消失的她

  40. 足不出户

  41. 407航班

  42. 霍元甲之精武天下

  43. 皇家圣诞假期

  44. 孤岛混战

  45. dj特工

  46. 狙击精英背水一战

  47. 美国国家公园第二季

  48. 骗骗喜欢你

  49. 零号追杀

  50. 孤儿泪

  51. 妈妈走了GoneMom

  52. 极速搏杀

  53. 狄仁杰之幽兵借路

  54. 来自樱桃巷的节日问候

  55. 超级轰天雷

  56. 他她他她

  57. 聊斋之极道天师

  58. 寻凶

  59. 红尾鸽

  60. 百万小宝贝

  61. 聊斋志异之瞳人语

  62. 地狱来的战尸

  63. 俱乐部的目的

  64. 外接手

  65. 谈吐人生

  66. 突破者

  67. 荒野迷案

  68. 唤醒

  69. 独行猎手

  70. 小银幕大电影

  71. 波哥大

  72. 只有我能喜欢你

  73. 新洗冤录

  74. 梦想无限

  75. 威廉斯姊妹港

  76. 爱情诅咒

  77. 油鬼子

  78. 水班长许东奎

  79. 浴室墙上的字

  80. 下班后

  81. 法国贩毒网2

  82. 罗伯特希梅尔此后的生活

  83. 狂暴2资本的惩罚

  84. 超能一家人

  85. 绝色青春

添加新评论