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
}
项目中的使用(单例)
- shared_preferences插件(轻量化存储数据)源码里单例
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了