flutter:小票標(biāo)簽打印【跨平臺解決方案】

引言

使用 flutter 進(jìn)行 pos 軟件開發(fā)勇蝙,諸多的業(yè)務(wù)場景都涉及到了 小票標(biāo)簽 打印综液】羁基于通用化,跨平臺的設(shè)計(jì)要求谬莹,小編整理了這篇文章檩奠。

核心設(shè)計(jì)思想

跨平臺通用化方案我們主要解決以下三點(diǎn):


  • 票據(jù)樣式: 拒絕硬編碼,直接使用 flutter-widget 進(jìn)行樣式開發(fā)附帽,更加直觀靈活

  • 打印指令集: 不嵌入廠商的打印SDK埠戳,適配一碼多用,無后續(xù)接入開發(fā)消耗

  • 傳輸方式: 指令集傳輸方式可擴(kuò)展蕉扮,底層代碼無需變動

整體方案流程

票據(jù)樣式使用 flutter-widget 進(jìn)行開發(fā)整胃,打印策略使用光柵位圖的統(tǒng)一標(biāo)準(zhǔn)進(jìn)行指令集中轉(zhuǎn),所有的數(shù)據(jù)轉(zhuǎn)換在 dart 層完成喳钟,網(wǎng)絡(luò)打印機(jī)使用 dart-socket 進(jìn)行傳輸屁使,usb 傳輸封裝各平臺的 write 方法在岂。

整個(gè)工具庫做統(tǒng)一數(shù)據(jù)中轉(zhuǎn),各平臺無需考慮數(shù)據(jù)層的處理蛮寂,只處理數(shù)據(jù)傳輸蔽午。

步驟一: 將 widget 轉(zhuǎn)換打印圖層

使用 flutter-widget 開發(fā)票據(jù)樣式,數(shù)據(jù)填充 widget 后酬蹋,加入隊(duì)列轉(zhuǎn) Uint8List 圖像數(shù)據(jù)

功能實(shí)現(xiàn)可直接使用我們的開源庫 print_image_generate_tool及老,提供能力將 widget 視圖轉(zhuǎn)換成 Uint8List 數(shù)據(jù),內(nèi)部維護(hù)隊(duì)列范抓,按加入順序生成返回圖像數(shù)據(jù)骄恶。

使用方式:

首先,初始化打印圖層
  1. 在頁面根節(jié)點(diǎn)下將打印圖層 PrintImageGenerateWidget 初始化
MaterialApp(
    onGenerateTitle: (context) => '打印測試',
    home: Scaffold(
    body: PrintImageGenerateWidget(
       contentBuilder: (context) {
       return const HomePage();
       },
       onPictureGenerated: _onPictureGenerated,  //用于接收 widget 轉(zhuǎn) Uint8List
     ),
   ),
)

//打印圖層生成成功
  Future<void> _onPictureGenerated(PicGenerateResult data) async {
    //widget生成的圖像的字節(jié)結(jié)果
    final imageBytes = data.data;
    //打印票據(jù)類型(標(biāo)簽尉咕、小票)
    final printTypeEnum = printTask.printTypeEnum;
    //... 打印邏輯下面補(bǔ)充
  }
  
  1. 將 widget 生成圖層數(shù)據(jù)叠蝇,注意: 傳入的 tempWidget 必須實(shí)現(xiàn)或繼承父類 ATempWidget
///生成打印的模板 Widget 需要繼承這個(gè)類
mixin ATempWidget {
  //生成圖片的縮放倍數(shù)
  double get pixelRatio => 1;

  //需要生成的票據(jù)像素寬度
  int get pixelPagerWidth;

  //需要生成的票據(jù)像素高度
  int get pixelPagerHeight => -1;
}
示例1:創(chuàng)建一個(gè) 寬45mm,高70mm 的標(biāo)簽?zāi)0澹?/h5>
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

// ignore: depend_on_referenced_packages
import 'package:print_image_generate_tool/print_image_generate_tool.dart';

// 標(biāo)簽樣式 demo
class LabelTemp extends StatelessWidget with ATempWidget {
  final String data;

  const LabelTemp(this.data, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Text(data),
    );
  }

  @override
  int get pixelPagerWidth => 360; //1mm對應(yīng)8像素年缎,45mm 對應(yīng)像素寬度為 360

  @override
  int get pixelPagerHeight => 560; //1mm對應(yīng)8像素悔捶,70mm 對應(yīng)像素高度為 560
}

業(yè)務(wù)方觸發(fā)打印任務(wù)時(shí),使用以下代碼將票據(jù)模板轉(zhuǎn)成圖層數(shù)據(jù)

// 生成打印圖層任務(wù)单芜,指定任務(wù)類型為標(biāo)簽
    PictureGeneratorProvider.instance.addPicGeneratorTask(
      PicGenerateTask<PrinterInfo>(
        tempWidget: LabelTemp('標(biāo)簽內(nèi)容') as ATempWidget,
        printTypeEnum: PrintTypeEnum.label, //標(biāo)識是標(biāo)簽
        params: printerInfo,
      ),
    );
示例2:創(chuàng)建一個(gè) 寬80mm 的小票模板:
// 小票樣式 demo
class ReceiptTemp extends StatelessWidget with ATempWidget {
  final String data;

  const ReceiptTemp(this.data, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Text(data),
    );
  }

  @override
  int get pixelPagerWidth => 550;
  
  // 小票不限制高度蜕该,pixelPagerHeight = -1 就行,無需重寫
}

業(yè)務(wù)方觸發(fā)打印任務(wù)時(shí)洲鸠,使用以下代碼將票據(jù)模板轉(zhuǎn)成圖層數(shù)據(jù)

// 生成打印圖層任務(wù)堂淡,指定任務(wù)類型為小票
    PictureGeneratorProvider.instance.addPicGeneratorTask(
      PicGenerateTask<PrinterInfo>(
        tempWidget: ReceiptTemp('小票內(nèi)容') as ATempWidget,
        printTypeEnum: PrintTypeEnum.receipt,
        params: printerInfo,
      ),
    );

步驟二:打印圖層轉(zhuǎn)打印指令集

將 widget 轉(zhuǎn)換打印圖層后,我們需要將 Uint8List 數(shù)據(jù)轉(zhuǎn)換為打印機(jī)可識別的數(shù)據(jù)類型扒腕,ESC 對應(yīng)小票機(jī)绢淀,TSC 對應(yīng)標(biāo)簽機(jī)。

使用 flutter_printer_plus 開源庫進(jìn)行功能實(shí)現(xiàn):

// 轉(zhuǎn) TSC 字節(jié)瘾腰,imageBytes 類型為 Uint8List
var printData = await PrinterCommandTool.generatePrintCmd(
        imgData: imageBytes,
        printType: PrintTypeEnum.label,
      );
// 轉(zhuǎn) ESC 字節(jié)皆的,imageBytes 類型為 Uint8List
var printData = await PrinterCommandTool.generatePrintCmd(
        imgData: imageBytes,
        printType: PrintTypeEnum.receipt,
      );

步驟三:目標(biāo)設(shè)備發(fā)送數(shù)據(jù)

可以自行擴(kuò)展實(shí)現(xiàn) write 方法,也可以使用 flutter_printer_plus 內(nèi)提供的方式蹋盆。目前已提供的實(shí)現(xiàn)如下:

  • USB 傳輸 (USB打印機(jī))

flutter_printer_plus 提供獲取當(dāng)前已連接的打印機(jī)列表费薄,列表內(nèi)每一個(gè)元素類型為 usbDevice。

// usb 打印
        final conn = UsbConn(usbDevice);
        conn.writeMultiBytes(printData, 1024 * 3);
  • IP 傳輸(網(wǎng)口打印機(jī))

example 內(nèi)提供獲取局域網(wǎng)內(nèi)可用打印機(jī)樣例

// IP 打印
        final conn = NetConn(ip);
        conn.writeMultiBytes(printData);

結(jié)合以上三個(gè)步驟栖雾,我們在步驟一內(nèi)接收 Uint8List 后可以這么寫:

//打印圖層生成成功
Future<void> _onPictureGenerated(PicGenerateResult data) async {
  final imageBytes = data.data;
  final printTask = data.taskItem;

  //獲取指定的目標(biāo)打印機(jī)
  final printerInfo = printTask.params as PrinterInfo;
  //打印票據(jù)類型(標(biāo)簽楞抡、小票)
  final printTypeEnum = printTask.printTypeEnum;

  if (imageBytes != null) {
    // Uint8List 轉(zhuǎn)小票或標(biāo)簽指令集
    var printData = await PrinterCommandTool.generatePrintCmd(
      imgData: imageBytes,
      printType: printTypeEnum,
    );
    // 發(fā)送打印指令
    if (printerInfo.isUsbPrinter) {
      // usb 打印
      final conn = UsbConn(printerInfo.usbDevice!);
      conn.writeMultiBytes(printData, 1024 * 3);
    } else if (printerInfo.isNetPrinter) {
      // 網(wǎng)絡(luò) 打印
      final conn = NetConn(printerInfo.ip!);
      conn.writeMultiBytes(printData);
    }
  }
}

完整流程級具體實(shí)現(xiàn)邏輯可參考 example 示例

建議使用者將上層進(jìn)行封裝(維護(hù)隊(duì)列)析藕,打印圖層生成成功后先將圖像保存本地召廷,等待上一個(gè)打印任務(wù)結(jié)束后再從隊(duì)列中獲取本地圖片進(jìn)行下一個(gè)打印任務(wù),避免造成內(nèi)存抖動。

附上 demo 打印的小票樣式

小票樣式

疑難點(diǎn)記錄

1. 打印的票據(jù)柱恤,一半正常数初,一半顯示亂碼

原因:數(shù)據(jù)太大導(dǎo)致打印機(jī)內(nèi)存溢出輸出亂碼找爱。
處理:對圖片進(jìn)行分割處理梗顺,分成n個(gè)小段進(jìn)行打印 長圖切割實(shí)現(xiàn)方式參考

2. 打印機(jī)打印票據(jù),出票緩慢车摄,聲音卡頓

原因:打印的位圖寬度太大寺谤。
處理:將位圖寬度進(jìn)行縮小,問題解決吮播。以 80mm 寬度小票為例变屁,通常 1mm 等于 8個(gè)像素,因?yàn)閷挾冗m配在實(shí)際打印中有偏差意狠,不宜設(shè)置為完全吻合因此粟关,適合小票打印機(jī)打印的圖片像素尺寸應(yīng)為:558px、372px环戈。

開源工具庫

print_image_generate_tool:提供 widget 轉(zhuǎn)圖像數(shù)據(jù)(Uint8List)能力闷板;

flutter_printer_plus:提供圖像數(shù)據(jù)(Uint8List) 轉(zhuǎn) TSC 、ESC能力院塞,提供 ip遮晚、usb 打印支持;

esc_utils:小票打印機(jī)數(shù)據(jù)轉(zhuǎn)換工具拦止,提供圖像數(shù)據(jù)(Uint8List) 轉(zhuǎn) ESC 能力县遣;

tsc_utils:標(biāo)簽打印機(jī)數(shù)據(jù)轉(zhuǎn)換工具,提供圖像數(shù)據(jù)(Uint8List) 轉(zhuǎn) TSC 能力汹族;

android_usb_printer:flutter android 端插件萧求,提供 usb 打印機(jī) 搜索、寫入 等能力顶瞒;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夸政,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搁拙,更是在濱河造成了極大的恐慌秒梳,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箕速,死亡現(xiàn)場離奇詭異酪碘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盐茎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門兴垦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事探越〗拼停” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵钦幔,是天一觀的道長枕屉。 經(jīng)常有香客問我,道長鲤氢,這世上最難降的妖魔是什么搀擂? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮卷玉,結(jié)果婚禮上哨颂,老公的妹妹穿的比我還像新娘。我一直安慰自己相种,他們只是感情好威恼,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寝并,像睡著了一般箫措。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上食茎,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天蒂破,我揣著相機(jī)與錄音,去河邊找鬼别渔。 笑死附迷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哎媚。 我是一名探鬼主播喇伯,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拨与!你這毒婦竟也來了稻据?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤买喧,失蹤者是張志新(化名)和其女友劉穎捻悯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淤毛,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡今缚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了低淡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姓言。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瞬项,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出何荚,到底是詐尸還是另有隱情囱淋,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布餐塘,位于F島的核電站妥衣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唠倦。R本人自食惡果不足惜称鳞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一涮较、第九天 我趴在偏房一處隱蔽的房頂上張望稠鼻。 院中可真熱鬧,春花似錦狂票、人聲如沸候齿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慌盯。三九已至,卻和暖如春掂器,著一層夾襖步出監(jiān)牢的瞬間亚皂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工国瓮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灭必,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓乃摹,卻偏偏與公主長得像禁漓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子孵睬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容