淘先锋技术网

首页 1 2 3 4 5 6 7

Flutter设计模式

一、 代码

https://gitee.com/hellosunshine/design-mode-study-project.git
https://gitee.com/hellosunshine/bilibili_flutter_getx.git

二、 参考文档

https://flutter.cn/community/tutorials/singleton-pattern-in-flutter-n-dart
https://juejin.cn/post/7072625679296102413
https://flutter.cn/docs/cookbook/persistence/key-value
实线与虚线
继承与实现
https://blog.csdn.net/weixin_29230649/article/details/114773522

三、 单例模式

  • 单例类中包含一个引用自身类的静态属性实例,且能自行创建这个实例
  • 改实例只能通过静态方法访问
  • 类构造函数通常没有参数,且被标记为私有,确保不能从类外部实例化该类
///单例
class Singleton {
  ///私有的命名构造函数
  Singleton._internal();

  ///声明一个单例
  static final Singleton instance = Singleton._internal();

  ///工厂构造函数返回单例
  factory Singleton() => instance;
}
///非单例
class NotSingleton {}

main() {
  ///创建多个实例,但单例只会有一个
  Singleton singletonOne = Singleton();
  Singleton singletonTwo = Singleton();
  print(singletonOne == singletonTwo); //ture
  NotSingleton notSingletonOne = NotSingleton();
  NotSingleton notSingletonTwo = NotSingleton();
  print(notSingletonOne == notSingletonTwo); //false
}
项目中的使用(单例)
import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

///持久化存储数据
class SharedPreferenceUtil {
  static late SharedPreferences _preferences;

  ///初始化SharedPreference
  static void initSharedPreference() async {
    _preferences = await SharedPreferences.getInstance();
  }
}
  • Dio网络请求单例
import 'package:dio/dio.dart';
class HttpBaseRequest {
  ///单例
  static late Dio? _dio = null;

  HttpBaseRequest._internal();

  static final HttpBaseRequest _instance = HttpBaseRequest._internal();

  factory HttpBaseRequest() {
    _dio ??= Dio();
    return _instance;
  }

  Dio? get dio => _dio;

  Future request(String path) async {
    final result = await _dio?.get(path);
    return result;
  }
}

注意_dio ??= Dio();这句话,不要写成_dio = Dio();。否则仍然创建了多dio个实例

四、工厂模式

简单工厂模式

抽象生产对象

  • 定义抽象类
///定义一个抽象方法 人,人会跑
abstract class Person {
  void run();
}
  • 生产对象
///男人
class Man extends Person {
  @override
  void run() {
    print("man can run");
  }
}

///女人
class Woman extends Person {
  @override
  void run() {
    print("woman can run");
  }
}
  • 调用
class SimpleFactoryMode {
  static void createProduct(int type) {
    if (type == 1) {
      Man().run();
    }
    if (type == 2) {
      Woman().run();
    }
  }
}
main() {
  SimpleFactoryMode.createProduct(1); //man can run
  SimpleFactoryMode.createProduct(2); //woman can run
}
项目中的使用(简单工厂)

场景:在【Flutter】(聊天)中,用户可以发送各类数据,包括文本类型数据、图片数据、视频数据、音频数据。
构建类图
在这里插入图片描述

@startuml SendDataDialog
class BaseSendDataModel{
    List users; //接受者列表
    String sender; //发送者
    int date; //日期时间戳
    String avatar; //头像
}
abstract SendDataModel {
    +String() buildString //生成数据文本格式
}
note left: 抽象用户发送的数据

class TextData {
    String msg; //文字
    +String() buildString
}

class PictureData {
    File file; //图片文件
    +String() buildString
}

class VideoData {
    File file; //视频文件
    +String() buildString
}

class audioData {
    File file; //音频文件
    +String() buildString
}

class SendDataFactory {
    {abstract} SendDataModel() createSendData //创建发送消息
}
note left: 简单工厂
BaseSendDataModel <--* SendDataModel
SendDataModel <|.. TextData
SendDataModel <|.. PictureData
SendDataModel <|.. VideoData
SendDataModel <|.. audioData
TextData <.. SendDataFactory
PictureData <.. SendDataFactory
VideoData <.. SendDataFactory
audioData <.. SendDataFactory
@enduml
  • 创建抽象类(dart不支持interface)
///生产发送数据的简单工厂
class BaseSendDataModel {
  late List<String> users; //接受者列表
  late String sender; //发送者
  late int date; //日期时间戳
  late String avatar;

  BaseSendDataModel({
    required this.users,
    required this.sender,
    required this.date,
    required this.avatar,
  }); //头像
}

abstract class SendDataModel {
  late BaseSendDataModel baseSendDataModel;

  String buildString(); //生成数据文本格式
}
  • 实现接口,并构建文字数据类和图片数据类
///文本类型数据
class TextData implements SendDataModel {
  late String msg;
  late String dataStr;

  TextData({
    required this.msg,
    required this.baseSendDataModel,
  });

  
  String buildString() {
    String dataStr = "{\"users\": [\"${baseSendDataModel.users.toString()}\"],"
        "\"msg\": \"$msg\","
        "\"date\": \"${baseSendDataModel.date}\","
        "\"avatar\": \"${baseSendDataModel.avatar}\","
        "\"sender\": \"${baseSendDataModel.sender}\"}";
    return dataStr;
  }

  
  BaseSendDataModel baseSendDataModel;
}

///图片数据
class PhotoData implements SendDataModel {
  late String photoFilePath;

  PhotoData({
    required this.photoFilePath,
    required this.baseSendDataModel,
  });

  
  String buildString() {
    String dataStr = "{\"users\": [\"${baseSendDataModel.users.toString()}\"],"
        "\"msg\": \"$photoFilePath\","
        // "\"msg\": \"${MultipartFile.fromFileSync(photoFilePath)}\","
        "\"date\": \"${baseSendDataModel.date}\","
        "\"avatar\": \"${baseSendDataModel.avatar}\","
        "\"sender\": \"${baseSendDataModel.sender}\"}";
    return dataStr;
  }

  
  BaseSendDataModel baseSendDataModel;
}
  • 构建简单工厂类
///发送数据类型
enum SendDataType {
  text,
  video,
  audio,
  photo,
}

///简单工厂类
class SendDataFactory {
  static SendDataModel createSendData({
    required SendDataType sendDataType,
    required BaseSendDataModel baseSendDataModel,
    String? msg,
    String? photoFilePath,
  }) {
    if (sendDataType == SendDataType.text) {
      return TextData(
        msg: msg!,
        baseSendDataModel: baseSendDataModel,
      );
    } else if (sendDataType == SendDataType.photo) {
      return PhotoData(
        photoFilePath: photoFilePath!,
        baseSendDataModel: baseSendDataModel,
      );
    } else {
      throw Exception();
    }
  }
}
  • 实现
main() {
  BaseSendDataModel baseSendDataModel = BaseSendDataModel(
    users: ["2", "3"],
    sender: '4',
    date: 5,
    avatar: '6',
  );
  SendDataModel sendTextData = SendDataFactory.createSendData(
      sendDataType: SendDataType.text,
      baseSendDataModel: baseSendDataModel,
      msg: "我发送了文本消息");
  SendDataModel sendPhotoData = SendDataFactory.createSendData(
      sendDataType: SendDataType.photo,
      baseSendDataModel: baseSendDataModel,
      photoFilePath: "这是一张图片地址");
  String textJson = sendTextData.buildString();
  String photoJson = sendPhotoData.buildString();
  print(textJson);
  print(photoJson);
}

-运行

{"users": ["[2, 3]"],"msg": "我发送了文本消息","date": "5","avatar": "6","sender": "4"}
{"users": ["[2, 3]"],"msg": "这是一张图片地址","date": "5","avatar": "6","sender": "4"}

Process finished with exit code 0
工厂方法模式

抽象类方法

  • 定义一个抽象类,里面声明抽象方法及业务方法
abstrast class LearningFactory {
	///抽象方法
	String learning();
	///业务代码
	void show() {
		String skill = learning();
		print(skill)
	}
}
  • 子类实现这个抽象类,并重写抽象方法
class LearningWalk extends LearningFactory {
  @override
  String learning() {
    return "I can walk";
  }
}

class LearningRun extends LearningFactory {
  @override
  String learning() {
    return "I can run";
  }
}
  • 实例化子类,调用业务方法
main() {
  final walkResult = LearningWalk();
  final runResult = LearningRun();
  walkResult.show();
  runResult.show();
}
项目中使用(工厂方法模式)

场景:应对Android和IOS分别实现拍摄媒体功能
在这里插入图片描述

@startuml
abstract TakeMediaFactory {
    +String() methodName
    +void() invoke
}
note left: 拍摄媒体
class AndroidTakeMedia {
    +String() methodName
}
class IosTakeMedia {
    +String() methodName
}
TakeMediaFactory <-- AndroidTakeMedia
TakeMediaFactory <-- IosTakeMedia
@enduml
  • 创建抽象类
///拍摄媒体(Android & Ios)
abstract class TakeMediaFactory {
  late MethodChannel takeMediaChannel;

  ///构建拍摄媒体界面
  String methodName();

  ///展示
  void invoke() {
    ChannelUtil()
        .takeMediaChannel
        .invokeMethod(methodName(), "")
        .then((value) => print(value));
  }
}
  • 定义子类
///Android拍摄媒体
class AndroidTakeMedia extends TakeMediaFactory {
  
  String methodName() {
    return "takeMediaAndroid";
  }
}

///ios拍摄媒体
class IosTakeMedia extends TakeMediaFactory {
  
  String methodName() {
    return  "takeMediaIos";
  }
}
  • Android端监听通道(MainActivity.java)
package com.example.take_media_factory_project;

import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.Nullable;

import java.util.Objects;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
    private static final String takeMediaChannel = "take_media_channel";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        new MethodChannel(Objects.requireNonNull(getFlutterEngine()).getDartExecutor().getBinaryMessenger(), takeMediaChannel).setMethodCallHandler((call, result) -> {
            if ("takeMediaAndroid".equals(call.method)) {
                openTakeMediaView();
                result.success("启动Android的拍摄功能");
            } else {
                result.success("没有对应的方法");
            }
        });
    }

    private void openTakeMediaView() {
        Intent intent = new Intent(this, TakeMediaActivity.class);
        startActivity(intent);
    }
}
  • TakeMediaActivity.java
package com.example.take_media_factory_project;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.Preview;
import androidx.camera.core.UseCaseGroup;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.concurrent.ExecutionException;

public class TakeMediaActivity extends AppCompatActivity {
    //拍照
    Button takePhotoButton;
    //预览
    PreviewView previewView;
    //权限
    private static final String[] REQUIRE_PERMISSION = new String[]{Manifest.permission.CAMERA};
    public static final int REQUEST_CODE_PERMISSIONS = 10;
    //capture
    ImageCapture imageCapture;
    ListenableFuture<ProcessCameraProvider> processCameraProviderListenableFuture;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_take_media);
        takePhotoButton = findViewById(R.id.takePhotoBtn);
        previewView = findViewById(R.id.preview_view);
        if (havePermissions()) {
            initCamera();
        } else {
            ActivityCompat.requestPermissions(this, REQUIRE_PERMISSION, REQUEST_CODE_PERMISSIONS);
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            initCamera();
        } else {
            finish();
        }
    }

    private boolean havePermissions() {
        for (String permission : REQUIRE_PERMISSION) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    private void initCamera() {
        imageCapture = new ImageCapture.Builder()
                .setFlashMode(ImageCapture.FLASH_MODE_ON)
                .build();
        processCameraProviderListenableFuture = ProcessCameraProvider.getInstance(this);
        processCameraProviderListenableFuture.addListener(() -> {
            try {
                previewView.setScaleType(PreviewView.ScaleType.FIT_CENTER);
                Preview preview = new Preview.Builder().build();
                preview.setSurfaceProvider(previewView.getSurfaceProvider());
                ProcessCameraProvider processCameraProvider = processCameraProviderListenableFuture.get();
                processCameraProvider.unbindAll();
                UseCaseGroup useCaseGroup = new UseCaseGroup
                        .Builder()
                        .addUseCase(preview)
                        .addUseCase(imageCapture)
                        .build();
                processCameraProvider.bindToLifecycle((LifecycleOwner) TakeMediaActivity.this, CameraSelector.DEFAULT_BACK_CAMERA, useCaseGroup);
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
        }, ContextCompat.getMainExecutor(this));

    }
}
  • activity_take_media.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".TakeMediaActivity">
    <androidx.camera.view.PreviewView
        android:id="@+id/preview_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <Button
        android:id="@+id/takePhotoBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/takePhoto"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias=".9" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • IOS端待补充
  • 调用
    • 通道单例
import 'package:flutter/services.dart';

class MediaChannel {
  static String takeMediaChannel = "take_media_channel";
}

class ChannelUtil {
  late MethodChannel takeMediaChannel;

  ChannelUtil._internal() {
    ///获取媒体
    takeMediaChannel = MethodChannel(MediaChannel.takeMediaChannel);
  }

  static final ChannelUtil _instance = ChannelUtil._internal();

  factory ChannelUtil() => _instance;
}
    • main.dart
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (defaultTargetPlatform == TargetPlatform.android) {
            AndroidTakeMedia().invoke();
          } else if (defaultTargetPlatform == TargetPlatform.iOS) {
            IosTakeMedia().invoke();
          }
        },
        child: Icon(Icons.phone_iphone_outlined),
      ),
    );
  }
}
抽象工厂模式

抽象生产对象的工厂

  • 定义抽象的工厂类
abstract class ElectronicProductFactory {
  Product createComputer();
  Product createMobile();
  Product createPad();
}
class Product {
  String? name;
  factory Product.createProductOne(String name) {
    return productOne;
  }
  Product._createProductOne();
  static Product productOne = Product._createProductOne();
}
  • 生成一系列对象,他们之间存在一定的联系
///苹果
class Apple extends ElectronicProductFactory {
  @override
  Product createComputer() {
    return Product.createProductOne("Mac");
  }

  @override
  Product createMobile() {
    return Product.createProductOne("IPhone");
  }

  @override
  Product createPad() {
    return Product.createProductOne("IPad");
  }
}

///小米
class XiaoMi extends ElectronicProductFactory {
  @override
  Product createComputer() {
    return Product.createProductOne("RedMi");
  }

  @override
  Product createMobile() {
    return Product.createProductOne("MIUI");
  }

  @override
  Product createPad() {
    return Product.createProductOne("MIPad");
  }
  
}
项目中使用(抽象工厂模式)

应用场景:面对不同平台,生产一系列组件。如Android风格的系列组件,Ios风格的系列组件等

在这里插入图片描述
抽象工厂模式抽象工厂,从而获得一个个生产一系列产品的工厂

  • 定义抽象工厂类
import 'package:flutter/material.dart';

///定义抽象工厂类,生产一系列组件
abstract class WidgetFactory {
  ///按钮
  Widget buildButton({
    Color? color,
    Size? size,
    Widget? child,
    Function()? onTap,
    BorderRadius? borderRadius,
    double? elevation,
  });

  //构建弹框
  void buildDialog(
    BuildContext context, {
    BorderRadius? borderRadius,
    double? elevation,
    Color? color,
    Size? size,
    Widget? child,
  });
}

定义Android工厂类,生产Android风格的系列组件

import 'dart:ui';

import 'package:bilibili_getx/ui/widgets/widget_factory/abstract_factory/widget_abstract_factory.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

// android风格组件工厂
class AndroidWidgetFactory extends WidgetFactory {
  
  Widget buildButton({
    Color? color,
    Size? size,
    Widget? child,
    Function()? onTap,
    BorderRadius? borderRadius,
    double? elevation,
  }) {
    return Material(
      elevation: elevation ?? .5,
      borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(6.r)),
      color: color ?? Colors.blue,
      child: Ink(
        child: InkWell(
          borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(6.r)),
          onTap: onTap,
          child: Container(
            alignment: Alignment.center,
            width: size != null ? size.width : 100.r,
            height: size != null ? size.height : 50.r,
            child: child,
          ),
        ),
      ),
    );
  }

  
  void buildDialog(
    BuildContext context, {
    BorderRadius? borderRadius,
    double? elevation,
    Color? color,
    Size? size,
    Widget? child,
  }) {
    ///获取OverlayState
    OverlayState? overlayState = Overlay.of(context);

    ///覆盖层
    OverlayEntry? overlayEntry;
    overlayEntry = OverlayEntry(
      builder: (ctx) {
        return Stack(
          alignment: Alignment.center,
          children: [
            GestureDetector(
              onTap: () {
                overlayEntry?.remove();
              },
              child: Container(
                color: Colors.black.withOpacity(.5),
                width: 1.sw,
                height: 1.sh,
              ),
            ),
            ClipRRect(
              borderRadius:
                  borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
              child: Material(
                elevation: elevation ?? 1,
                borderRadius:
                    borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
                color: color ?? Colors.white,
                child: child,
              ),
            )
          ],
        );
      },
      opaque: false,
    );

    overlayState.insert(overlayEntry);
  }
}

  • 定义Ios工厂类,生产Ios风格的系列组件
import 'dart:ui';

import 'package:bilibili_getx/ui/widgets/widget_factory/abstract_factory/widget_abstract_factory.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

// IOS风格组件工厂
class IosWidgetFactory extends WidgetFactory {
  
  Widget buildButton({
    Color? color,
    Widget? child,
    Size? size,
    BorderRadius? borderRadius,
    double? elevation,
    Function()? onTap,
  }) {
    return ClipRRect(
      borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
      child: Material(
        elevation: elevation ?? 1,
        borderRadius: borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
        color: color ?? Colors.grey.shade200,
        child: Ink(
          child: InkWell(
            borderRadius:
                borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
            onTap: onTap,
            child: BackdropFilter(
              filter: ImageFilter.blur(sigmaX: 1.0, sigmaY: 1.0),
              child: Opacity(
                opacity: .5,
                child: Container(
                  alignment: Alignment.center,
                  width: size != null ? size.width : 100.r,
                  height: size != null ? size.height : 50.r,
                  child: child,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  
  void buildDialog(
    BuildContext context, {
    BorderRadius? borderRadius,
    double? elevation,
    Color? color,
    Size? size,
    Widget? child,
  }) {
    ///获取OverlayState
    OverlayState? overlayState = Overlay.of(context);

    ///覆盖层
    OverlayEntry? overlayEntry;
    overlayEntry = OverlayEntry(
      builder: (ctx) {
        return BackdropFilter(
          filter: ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
          child: Stack(
            alignment: Alignment.center,
            children: [
              GestureDetector(
                onTap: () {
                  overlayEntry?.remove();
                },
                child: Container(
                  color: Colors.grey.shade400.withOpacity(.5),
                  width: 1.sw,
                  height: 1.sh,
                ),
              ),
              ClipRRect(
                borderRadius:
                    borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
                child: Material(
                  elevation: elevation ?? 1,
                  borderRadius:
                      borderRadius ?? BorderRadius.all(Radius.circular(10.r)),
                  color: color ?? Colors.white,
                  child: child,
                ),
              )
            ],
          ),
        );
      },
      opaque: false,
    );

    overlayState.insert(overlayEntry);
  }
}
  • 运行测试
    在IOS平台中和在Android平台中,需要构建一个按钮,再点击按钮出现弹框(弹框覆盖层用OverlayState & OverlayEntry)。
  • main.dart
import 'package:bilibili_getx/ui/shared/image_asset.dart';
import 'package:bilibili_getx/ui/widgets/widget_factory/widget_factory/android_widget_factory.dart';
import 'package:bilibili_getx/ui/widgets/widget_factory/widget_factory/ios_widget_factory.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

import 'abstract_factory/widget_abstract_factory.dart';

main() {
 runApp(
   ScreenUtilInit(
     builder: (BuildContext context, Widget? child) {
       return MaterialApp(
         home: MyApp(),
       );
     },
   ),
 );
}

class MyApp extends StatelessWidget {
 late final WidgetFactory widgetFactory;

 
 Widget build(BuildContext context) {
   if (defaultTargetPlatform == TargetPlatform.android) {
     widgetFactory = AndroidWidgetFactory();
   } else if (defaultTargetPlatform == TargetPlatform.iOS) {
     widgetFactory = IosWidgetFactory();
   } else {
     return Container();
   }
   return Scaffold(
     backgroundColor: Colors.white,
     body: Center(
       child: Column(
         mainAxisSize: MainAxisSize.min,
         children: [
           Image.asset(
             ImageAssets.appLogoPNG,
             width: 50.r,
             height: 50.r,
           ),
           200.verticalSpace,
           widgetFactory.buildButton(
             child: const Text("button"),
             onTap: () {
               widgetFactory.buildDialog(
                 context,
                 child: Container(
                   color: Colors.white,
                   alignment: Alignment.center,
                   width: 200.r,
                   height: 200.r,
                   child: const Text("Dialog"),
                 ),
               );
             },
           )
         ],
       ),
     ),
   );
 }
}

在这里插入图片描述在这里插入图片描述

  • 改进
    在创建不同平台的过程中,似乎每使用抽象工厂,就需要执行一次以下代码
if (defaultTargetPlatform == TargetPlatform.android) {
      widgetFactory = AndroidWidgetFactory();
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      widgetFactory = IosWidgetFactory();
    } else {
      return Container();
    }

也就是创建工厂,Flutter中有混入的功能(with)。再者,把工厂的实例化做成单例。

  • 创建WidgetFactoryPlugin
import 'package:bilibili_getx/ui/widgets/widget_factory/abstract_factory/widget_abstract_factory.dart';
import 'package:flutter/foundation.dart';

import '../widget_factory/android_widget_factory.dart';
import '../widget_factory/ios_widget_factory.dart';

///根据平台不同建造不同工厂
mixin WidgetFactoryPlugin {
  late WidgetFactory _widgetFactory;

  ///获取组件工厂实例
  WidgetFactory wFactory() {
    if (defaultTargetPlatform == TargetPlatform.android) {
      _widgetFactory = AndroidWidgetFactory.getInstance();
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      _widgetFactory = IosWidgetFactory.getInstance();
    } else {
      _widgetFactory = AndroidWidgetFactory.getInstance();
    }
    return _widgetFactory;
  }
}

  • 使用
    只需要在需要使用的地方,混入WidgetFactoryPlugin ,就可以直接调用wFactory()获取到对应平台下的工厂的单例
class MyApp extends StatelessWidget with WidgetFactoryPlugin {
  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Image.asset(
              ImageAssets.appLogoPNG,
              width: 50.r,
              height: 50.r,
            ),
            200.verticalSpace,
            wFactory().buildButton(
              child: const Text("button"),
              onTap: () {
                wFactory().buildDialog(
                  context,
                  child: Container(
                    color: Colors.white,
                    alignment: Alignment.center,
                    width: 200.r,
                    height: 200.r,
                    child: const Text("Dialog"),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

五、观察者模式

普通观察者模式

在这里插入图片描述

  • 定义通知和观察者类
///观察者类
class Observer {
  String name;

  Observer(this.name);

  void notify(Notification notification) {
    print(
        "[${notification.timestamp.toIso8601String()}] Hey $name, ${notification.message}!");
  }
}

///通知
class Notification {
  late DateTime timestamp;
  late String message;

  Notification.forNow(String name) {
    timestamp = DateTime.now();
    message = "msg: $name";
  }
}
  • 创建一个被观察者类
///被观察者
class Subject {
  late List<Observer> _observers;

  ///传入观察者列表
  Subject([List<Observer>? observers]) {
    _observers = observers ?? [];
  }

  ///注册观察者
  void registerObserver(Observer observer) {
    _observers.add(observer);
  }

  ///注销观察者
  void unregisterObserver(Observer observer) {
    _observers.remove(observer);
  }

  ///通知观察者
  void notifyObservers(Notification notification) {
    for (var observer in _observers) {
      observer.notify(notification);
    }
  }
}
  • 创建业务类
///创建一个业务类CoffeeMaker继承被观察者类Subject
class CoffeeMaker extends Subject {
  CoffeeMaker([List<Observer>? observers]) : super(observers);

  void brew() {
    print("Brewing the coffee...");
    notifyObservers(Notification.forNow("coffee's done"));
  }
}
  • 测试
void main() {
  ///创建观察者
  var me = Observer("Tyler");
  ///初始化观察者列表
  var mrCoffee = CoffeeMaker(List.from([me]));
  ///创建观察者
  var myWife = Observer("Kate");
  ///注册观察者
  mrCoffee.registerObserver(myWife);
  ///通知观察者
  mrCoffee.brew();
}

在这里插入图片描述

项目中使用(观察者模式)
简单使用
  • 场景
    接收消息,更新界面。
    观察者模式,Flutter中有Stream,ChangeNotifier.局部刷新会用到,又比如实时数据推送websocket.
  • 接收消息案例

在这里插入图片描述

  • 定义观察者和被观察者
class Observer {
  void notify(int number) {
    print("未读消息数量为$number");
  }
}

///被观察者,发放消息
class Subject {
  late List<Observer> observers;

  ///初始化被观察者
  initObserver() {
    observers = [];
  }

  ///注册观察者
  registerObserver(Observer observer) {
    observers.add(observer);
  }

  ///注销观察者
  unRegisterObserver(Observer observer) {
    observers.remove(observer);
  }

  unRegisterAllObservers() {
    observers.clear();
  }

  ///通知观察者
  notifyListener(int unReadMessage) {
    for (var observer in observers) {
      observer.notify(unReadMessage);
    }
  }
}
  • 定义业务类MessageObserver 和 MessageSubject
///观察者,根据消息更新界面
class MessageObserver extends Observer {
  int message = 0;

  
  void notify(int number) {
    message = number;
    super.notify(number);
  }
}

///被观察者
class MessageSubject extends Subject {
  ///有几条未读消息
  int unReadMessage = 0;

  ///接收到新消息
  void receiveMessage(int messageNumber) {
    unReadMessage++;
    notifyListener(unReadMessage);
  }

  ///已读消息
  void readMessage(int messageNumber) {
    unReadMessage--;
    notifyListener(unReadMessage);
  }
}
  • 运行
import 'package:flutter/material.dart';

import 'model/message_observer.dart';
import 'model/message_subject.dart';

main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  ///被观察者
  late MessageSubject messageSubject;

  ///观察者
  late MessageObserver messageObserver;

  ///初始化观察者和被观察者
  
  void initState() {
    messageSubject = MessageSubject();
    messageSubject.initObserver();
    messageObserver = MessageObserver();
    messageSubject.registerObserver(messageObserver);
    super.initState();
  }

  ///注销所有的观察者
  
  void dispose() {
    messageSubject.unRegisterAllObservers();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              width: 100,
              height: 100,
              color: Colors.blue,
              alignment: Alignment.center,
              child: Text(
                '消息数量${messageObserver.message}',
                style: const TextStyle(
                  color: Colors.white,
                ),
              ),
            ),
            ElevatedButton(
              onPressed: () {
                messageSubject.receiveMessage(1);
                setState(() {});
              },
              child: const Text("收到1条消息"),
            ),
            ElevatedButton(
              onPressed: () {
                if(messageObserver.message > 0) {
                  messageSubject.readMessage(1);
                  setState(() {});
                }
              },
              child: const Text("读了1条消息"),
            ),
          ],
        ),
      ),
    );
  }
}

图片

Stream & ChangeNotifier

场景:实现仿微信中收到消息后出现的未读消息红点。接受消息推送流websocket。

  • 定义消息MessageChangeNotifier
import 'package:flutter/cupertino.dart';

class MessageChangeNotifier extends ValueNotifier<Map<String, int>> {
  Map<String, int> unReadMessage = {};

  MessageChangeNotifier._internal() : super({});

  static final MessageChangeNotifier _instance =
      MessageChangeNotifier._internal();

  factory MessageChangeNotifier.getInstance() => _instance;

  ///获取数据库中所有该用户的未读消息
  initLocalUnreadMessage(Map<String, int> initMap) {
    unReadMessage.addAll(initMap);
    notifyListeners();
  }

  ///接受消息(给这个人加一条未读消息)
  receiveMessage(String sender) {
    int? unReadMessageNumber = unReadMessage[sender];
    if (unReadMessageNumber != null) {
      unReadMessage[sender] = unReadMessageNumber + 1;
    } else {
      unReadMessage[sender] = 1;
    }
    notifyListeners();
  }

  ///读取消息(移除掉所有这个人的未读消息数量)
  readMessage(String sender) {
    unReadMessage.removeWhere((key, value) => key == sender);
    notifyListeners();
  }

  ///计算所有未读消息数量
  int get allUnReadMessage {
    int number = 0;
    unReadMessage.forEach((key, value) {
      number += value;
    });
    return number;
  }

  ///计算某个好友的未读消息
  int userUnReadMessage(String userId) {
    return unReadMessage[userId] ?? 0;
  }
}

  • websocket消息流监听
connectToServer() {
    state.webSocketChannel = IOWebSocketChannel.connect(
      Uri.parse(WebSocketUtil.wssUrl),
      pingInterval: const Duration(seconds: 10),
      headers: {
        "token": "{\"userId\": \"${state.isLoginUserId}\"}",
      },
    );

    ///监听消息接受
    state.webSocketChannel.stream.listen((event) {
      // print(event);
      try {
        ///将消息发送给聊天室
        final receiveData = ReceiveDataModel.fromJson(json.decode(event));
        ChatRoomLogic chatRoomLogic = Get.find<ChatRoomLogic>();
        chatRoomLogic.state.chatRoomMessageList.add(receiveData);
        chatRoomLogic.update();

        ///存储信息到本地(同时更新已读未读状态)
        if (chatRoomLogic.state.isStay) {
          if (chatRoomLogic.state.userModel != null) {
            String chatRoomUserId = chatRoomLogic.state.userModel!.userId!;
            if (chatRoomUserId == receiveData.sender) {
              receiveData.isRead = true;
            }
          }
        } else {
          ///更新未读消息数量
          MessageChangeNotifier.getInstance()
              .receiveMessage(receiveData.sender);
        }
        saveMessageToDatabase(receiveData);
        state.latestMsgData[receiveData.sender] = receiveData;
        update();

        ///延迟计算最大滑动距离
        if (chatRoomLogic.state.messageListScrollController.hasClients) {
          Future.delayed(const Duration(milliseconds: 500)).then((value) {
            double max = chatRoomLogic
                .state.messageListScrollController.position.maxScrollExtent;
            chatRoomLogic.state.messageListScrollController.animateTo(
              max,
              duration: const Duration(milliseconds: 200),
              curve: Curves.linear,
            );
          });
        }
      } catch (e) {
        if (kDebugMode) {
          print("$e-$event");
        }
      }
    });
  }
  • 界面上的更新
///头像部分
  Widget buildWechatMainUserLogo(String userId) {
    return ValueListenableBuilder(
      valueListenable: MessageChangeNotifier.getInstance(),
      builder: (_, __, ___) {
        return wFactory().buildRightTag(
          mainPart: Container(
            padding: EdgeInsets.all(6.sp),
            child: Container(
              padding: EdgeInsets.all(3.r),
              decoration: BoxDecoration(
                  color: HYAppTheme.norGrayColor,
                  borderRadius:
                  BorderRadius.all(Radius.circular(3.r))),
              width: 43.sp,
              height: 43.sp,
              child: Image.asset(
                ImageAssets.arPNG,
              ),
            ),
          ),
          tagPart: Text(
            "${MessageChangeNotifier.getInstance().userUnReadMessage(userId)}",
            style: const TextStyle(
              color: Colors.white,
            ),
          ),
        );
      },
    );
  }
  • 底部按钮
///底部按钮
  BottomNavigationBarItem buildBottomNavigationBarItem({
    required String title,
    required String iconName,
    required bool showTagPart,
  }) {
    return BottomNavigationBarItem(
      label: title,
      icon: showTagPart
          ? ValueListenableBuilder<Map<String, int>>(
              valueListenable: MessageChangeNotifier.getInstance(),
              builder: (_, __, ___) {
                return wFactory().buildRightTag(
                  mainPart: Container(
                    padding: EdgeInsets.all(6.sp),
                    child: Image.asset(
                      "assets/image/wechat/${iconName}_unselected.png",
                      width: 22.sp,
                      height: 22.sp,
                      gaplessPlayback: true,
                    ),
                  ),
                  tagPart: Text(
                    "${MessageChangeNotifier.getInstance().allUnReadMessage}",
                  ),
                );
              },
            )
          : Container(
              padding: EdgeInsets.all(6.sp),
              child: Image.asset(
                "assets/image/wechat/${iconName}_unselected.png",
                width: 22.sp,
                height: 22.sp,
                gaplessPlayback: true,
              ),
            ),
      activeIcon: showTagPart
          ? ValueListenableBuilder<Map<String, int>>(
              valueListenable: MessageChangeNotifier.getInstance(),
              builder: (_, __, ___) {
                return wFactory().buildRightTag(
                  mainPart: Container(
                    padding: EdgeInsets.all(6.sp),
                    child: Image.asset(
                      "assets/image/wechat/${iconName}_selected.png",
                      width: 22.sp,
                      height: 22.sp,
                      gaplessPlayback: true,
                    ),
                  ),
                  tagPart: Text(
                      "${MessageChangeNotifier.getInstance().allUnReadMessage}"),
                );
              },
            )
          : Container(
              padding: EdgeInsets.all(6.sp),
              child: Image.asset(
                "assets/image/wechat/${iconName}_selected.png",
                width: 22.sp,
                height: 22.sp,
                gaplessPlayback: true,
              ),
            ),
    );
  }
  • 运行

请添加图片描述

响应式编程(待补充)

六、适配器模式

适配器模式可以将不兼容的接口转为可以兼容的接口

对象适配器
  • 定义被适配类
import 'dart:convert';
import 'package:xml/xml.dart';
///被适配类
class TargetAdapter {
  String name;

  TargetAdapter({required this.name});
}
  • 规范化结构定义
///规范化结构定义
abstract class ITarget {
  TargetAdapter getTargetAdapterList();
}
  • 不同的对象适配器
///对象适配器
///种类A(json类型的string)
class DataBox {
  static String receivedJsonData = '''
      {
        "name": "data from json"
      }
    ''';
  static String receivedXmlData = '''
      <name>date from xml</name>
    ''';
}

class TypeJsonAdapter extends ITarget {
  var targetAdapter = TargetAdapter();

  
  TargetAdapter getTargetAdapterList() {
    final jsonData = json.decode(DataBox.receivedJsonData);
    String name = jsonData["name"];
    targetAdapter.name = name;
    return targetAdapter;
  }
}

///种类B(xml类型的string)
class TypeXmlAdapter extends ITarget {
  var targetAdapter = TargetAdapter();

  
  TargetAdapter getTargetAdapterList() {
    XmlDocument xmlDocument = XmlDocument.parse(DataBox.receivedXmlData);
    String text = xmlDocument.getElement("name")!.text;
    targetAdapter.name = text;
    return targetAdapter;
  }
}
  • 客户端调用
///客户端调用
class Client {
  late ITarget iTarget;

  Client({required this.iTarget});

  getString() {
    final result = iTarget.getTargetAdapterList();
    print(result.name);
  }
}

main() {
  Client clientJson = Client(iTarget: TypeJsonAdapter());
  Client clientXml = Client(iTarget: TypeXmlAdapter());
  clientJson.getString(); //data from json
  clientXml.getString(); //date from xml
}
类适配器
  • 案例
class TargetAdapter {
  String conCreate() {
    return "targetAdapter";
  }

}

class ClassAdapter extends TargetAdapter {
  String operate() {
    return super.conCreate();
  }
}
  • SliverToBoxAdapter案例
    CustomScrollView的Sliver属性接收Sliver系列组件
  • SliverToBoxAdapter源码
    SliverToBoxAdapter
class SliverToBoxAdapter extends SingleChildRenderObjectWidget {
  /// Creates a sliver that contains a single box widget.
  const SliverToBoxAdapter({
    super.key,
    super.child,
  });

  
  RenderSliverToBoxAdapter createRenderObject(BuildContext context) => RenderSliverToBoxAdapter();
}

这里child传给的父类SingleChildRenderObjectWidget,这里重写抽象类RenderObjectWidget中的createRenderObject
最终返回了RenderSliverToBoxAdapter()
在这里插入图片描述
一句话就是将 SingleChildRenderObjectWidget 中 createRenderObject 接口重写转换成可以包含 RenderBox (对应一般 widget 的 RenderObject) 的 RenderSliver (对应 sliver 系列 widget 的 RenderObject),即这里的 RenderSliverToBoxAdapter

七、MVC模式

该模式是在学习IOS(swift)中获知的。
M意味Model,管理模型以及相关计算的方法
V意味界面,管理展示给用看的UI界面
C意味逻辑控制层,管理一些逻辑操作,不需要考虑界面问题
在这里插入图片描述
在Flutter工程项目中也有体现!但不完全像,因为是GETX进行的状态管理,但个人感觉比MVC更好
在这里插入图片描述
如果是Provider管理项目,则很像是MVC了
在这里插入图片描述