【Android】佳博打印机Gprinter实现打印功能(云打印、竖向打印)

【Android】佳博打印机Gprinter实现打印功能(云打印、竖向打印)之前公司有个需求 想要打印学生的假条 但是所用纸张较小 宽度在 100mm 150mm 之间 打印如外卖小票 快递单据等的标签打印机 打印机基本上只用于横向打印 由于纸张太窄只能想办法实现竖向打印

大家好,欢迎来到IT知识分享网。


前言

之前公司有个需求,想要打印学生的假条,但是所用纸张较小,宽度在100mm~150mm之间,打印如外卖小票、快递单据等的标签打印机,打印机基本上只用于横向打印,由于纸张太窄只能想办法实现竖向打印。因为佳博的SDK的比较完善,且有云打印功能,所以选用佳博的打印机。

本文将介绍使用佳博打印机实现云打印,竖向打印。

实现效果:

在这里插入图片描述


一、打印原理

先了解打印机的工作原理:

打印机只能接受符合一定标准的指令才能去执行打印,通过指令去控制打印格式,所以只需要控制好发送给打印机的指令信息就可以完成打印。无论使用佳博、TSC还是其他第三方SDK、DEMO,只要发送的打印指令标准一致,均可控制不同品牌的打印机

在这里插入图片描述

打印指令:直接驱动打印机使用的指令,非常精简,通常需要专用的手册解释其含义(行业标准,如TSPL、CPCL、ESC、ZPLII指令,不同指令有不同的语法)
SDK:将抽象的打印机指令封装为可调用,易读的接口,不可单独运行
DEMO:演示SDK或打印机特定功能的例子,可单独运行
文档:用于阐述SDK、DEMO或指令的定义与使用方法


二、实现

打印机主要注意这几点:打印类型、打印宽度(纸张大小)、打印指令、打印机接口(是否有网口或者wifi去支持云打印)

  1. 小票打印机:58mm或者80mm
    在这里插入图片描述
  2. 标签打印机:40mm、50mm、70mm
    在这里插入图片描述
  3. 面单打印机:76mm、100mm (不想用竖向打印,可以直接用这种)
    在这里插入图片描述

打印资料下载:

进入佳博官网:https://cn.gainscha.com/

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

查看打印机是否支持:

在这里插入图片描述

这里我使用的是标签云打印机,ZPLII指令:

在这里插入图片描述

1. 本地打印

连接方式主要就是USB或蓝牙打印,厂家也提供了相应的demo,里面有详细代码,可以直接用。打印内容可以用指令,还可以用xml文件或者pdf文件(某些格式用指令不方便时),这里不详细展开。

demo在打印资料里面的佳博官方->Android->标签打印机安卓SDK-V3.3.1-中:

在这里插入图片描述
这些打印方法需要导入jar包,jar包在标签打印机demo里面

implementation files('libs/SDKLib.jar') 

2. 云打印

云打印就是网络远程由调用佳博接口,由他们的服务器替我们发送指令给打印机,需要支持网络的打印机,下面具体实现。

佳博云打印是软硬件深度整合的云打印服务商,拥有多类型打印设备的研发、制造能力、与各行业打印业务的云端服务能力。满足多终端、跨网络的异地远程打印需求。同时,佳博开放者中心提供丰富的Web API接口、详尽的文档手册及各种解决方案,例如,测试工具、API调试工具、服务监控告警、日志分析系统等。

佳博开发者中心:https://dev.poscom.cn/

  1. 去开发者中心注册账号:获取接口需要的商户编号和api密钥(开发中心->开发信息),管理打印机,查看接口文档
  2. 登录后添加打印机:终端编号就是打印机底部的条形码上的编号
    在这里插入图片描述3. 调用接口:查看接口文档,打印需要哪些参数,安全校验码最关键
    在这里插入图片描述

 / * 发送指令 */ @FormUrlEncoded //必须要这个注解 @POST("/apisc/sendMsg") fun sendPrintMsg( @Field("reqTime") reqTime: String, @Field("securityCode") securityCode: String, @Field("memberCode") memberCode: String, @Field("deviceID") deviceID: String, @Field("mode") mode: String, @Field("charset") charset: String, @Field("msgDetail") msgDetail: String, ): Call<PrintResult> 

我的调用:

val reqTime = Calendar.getInstance().timeInMillis.toString() //时间戳 model.sendPrintMsg( reqTime, RxUtils.Md5(memberCode + deviceID + "" + reqTime + apiKey), memberCode, deviceID, "2", "4", "", msgDetail ) 

生成md5的方法:

 / * 生成MD5加密32位字符串 * * @param MStr :需要加密的字符串 * @return */ public static String Md5(String MStr) { 
      try { 
      final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(MStr.getBytes()); return bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { 
      return String.valueOf(MStr.hashCode()); } } // MD5内部算法 private static String bytesToHexString(byte[] bytes) { 
      StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { 
      String hex = Integer.toHexString(0xFF & bytes[i]); if (hex.length() == 1) { 
      sb.append('0'); } sb.append(hex); } return sb.toString(); } 
  1. 生成打印指令
    查看对应指令手册,熟悉语法
    在这里插入图片描述
    先模板中心生成一段基础的指令,^XA和 ^XZ是开始和结束
    在这里插入图片描述
    在这里插入图片描述




横向如何变成竖向打印,之前想过纸张整体旋转或者用xml旋转打印,但是效果不好,后面想到一个新的思路:一个字一个字地旋转,先摆好位置,一开始字是歪的,然后再统一旋转就可以完美解决。主要用^FWR文字旋转90度:

在这里插入图片描述
下面这两行一个是打印一行字,一个是一条直线,都是用绝对定位的x和y去控制位置。大概只用到这些了,具体语法查看文档。

^FO80,60^AA,24,24^FD测试^FS ^FO100,50^GB240,4,4^FS 

下面是我的实现,代码用循环去生成指令:

private fun generateOrder(bean:PrintInfoBean?):String{ 
      var str="^XA" + "^MCY^MTD^MD8^LT0^MMT^MNN^MTD^PW880^LL1360^PR4^PMN^PON^JMA^LH0,4^LRN^CWA,Z:MSUNG.FNT^CI28" str += "^FWR" //旋转,假条由横向变竖向 if(bean?.leaveInfoPrinerItems != null && bean.leaveInfoPrinerItems.size>0){ 
      val contentList=bean.leaveInfoPrinerItems2 val otherList=bean.leaveInfoPrinerItems val title=otherList[0].item?:"" val tag=otherList[1].item?:"" val pSignTitle=otherList[2].item?:"" val tSignTitle=otherList[3].item?:"" val tSign=otherList[4].item?:"" val dateTitle=otherList[5].item?:"" val date=otherList[6].item?:"" val notes1=otherList[7].item?:"" val notes2=otherList[8].item?:"" val notes3=otherList[9].item?:"" //宽:1360 高:880 val start=32*2 //宽起点 val end=1360-50-32*2 //宽终点 var h=880-100-48 //高起点,顶部边距100 val space=32+20 //行距 //标题起点 val tStart= if ((1360 / 2 - title.length * 48 / 2)<0) 0 else (1360 / 2 - title.length * 48 / 2) for (i in title.indices){ 
      str+="^FO$h,${ 
       tStart+i*48}^AA,48,48^FD${ 
       title[i]}^FS" } //加标题行距 h=h-48-40 for (i in tag.indices){ 
      str+="^FO$h,${ 
       start+i*32}^AA,32,32^FD${ 
       tag[i]}^FS" } h -= space var cStart=start+32*2 contentList?.forEachIndexed { 
      i, item -> val content= item.item ?:"" for (i in content.indices){ 
      //超过宽度换行 if(cStart>=end){ 
      cStart=start h -= space } str+="^FO$h,$cStart^AA,32,32^FD${ 
       content[i]}^FS" if(item.isValue==true){ 
      str+="^FO$h,$cStart^GB1,${ 
       if(content[i].isDigit()) 16 else 32},1^FS" //下划线 } cStart += if(content[i].isDigit()) 16 else 32 } } h =h-space-30 val len =if(date.isEmpty()||date.length<11) 11 else date.length val signMaxWidth= len*32 - 8*16 //签名最大宽度,日期11的长度 val signStart=end - signMaxWidth//可能有数字,数字只占16 for ((m, i) in (pSignTitle.length-1 downTo 0).withIndex()){ 
      //m从1开始 str+="^FO$h,${ 
       signStart-(m+1)*32}^AA,32,32^FD${ 
       pSignTitle[i]}^FS" } str+="^FO$h,${ 
       signStart}^GB1,${ 
       signMaxWidth},1^FS" //下划线 h -= space for ((m, i) in (tSignTitle.length-1 downTo 0).withIndex()){ 
      str+="^FO$h,${ 
       signStart-(m+1)*32}^AA,32,32^FD${ 
       tSignTitle[i]}^FS" } str+="^FO$h,${ 
       signStart}^GB1,${ 
       signMaxWidth},1^FS" //下划线 var tSignStart= signStart + if ((signMaxWidth/2-tSign.length*32/2)<0) 0 else (signMaxWidth/2-tSign.length*32/2) for (i in tSign.indices){ 
      //超过宽度换行 if(tSignStart>=end){ 
      tSignStart=signStart h -= space str+="^FO$h,${ 
       signStart}^GB1,${ 
       signMaxWidth},1^FS" //下划线 } str+="^FO$h,${ 
       tSignStart}^AA,32,32^FD${ 
       tSign[i]}^FS" tSignStart+=32 } h -= space for ((m, i) in (dateTitle.length-1 downTo 0).withIndex()){ 
      str+="^FO$h,${ 
       signStart-(m+1)*32}^AA,32,32^FD${ 
       dateTitle[i]}^FS" } str+="^FO$h,${ 
       signStart}^GB1,${ 
       signMaxWidth},1^FS" //下划线 var dStart=signStart for (i in date.indices){ 
      str+="^FO$h,${ 
       dStart}^AA,32,32^FD${ 
       date[i]}^FS" //str+="^FO$h,$dStart^GB1,${if(date[i].isDigit()) 16 else 32},1^FS" dStart += if(date[i].isDigit()) 16 else 32 } h -= space for (i in notes1.indices){ 
      str+="^FO$h,${ 
       start+i*32}^AA,32,32^FD${ 
       notes1[i]}^FS" } h -= space for (i in notes2.indices){ 
      str+="^FO$h,${ 
       start+32*2+i*32}^AA,32,32^FD${ 
       notes2[i]}^FS" } h -= space for (i in notes3.indices){ 
      str+="^FO$h,${ 
       start+32*2+i*32}^AA,32,32^FD${ 
       notes3[i]}^FS" } }else{ 
      str+="^FO$0,0^AA,48,48^FD无^FS" } str += "^XZ" return str } @Keep data class PrintInfoBean ( / * 假条信息 分开的固定部分 */ val leaveInfoPrinerItems: List<LeaveInfoPrinterItems>? = null, / * 假条信息 分开的中间假条信息部分 */ val leaveInfoPrinerItems2: List<LeaveInfoPrinterItems>? = null, ){ 
      / * LeaveInfoPrinerItems */ @Keep data class LeaveInfoPrinterItems ( val isValue: Boolean? = null, //是否有签字 val item: String? = null //内容 ) } 
  1. 返回结果处理
    在这里插入图片描述

总结

这是灵活使用第三方接口或者库必经的流程,例如elementUI、低代码平台的使用等等。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/134835.html

(0)
上一篇 2025-07-09 13:33
下一篇 2025-07-09 14:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信