Android蓝牙打印机,带你真正了解各种打印格式

注意:本文原创,转载请注明出处。欢迎关注我的 简书

本文主要讲解蓝牙打印机在打印小票的过程中,如何打印各种常见格式。由于之前需要调试打印格式,但是苦于网上没有详细的讲解教程,无奈只能自给自足,自己封装了一个。如果各位盆友正在或者曾经苦恼蓝牙打印机的打印格式,那么恭喜你,本篇博文就是你要寻找的。

什么是蓝牙打印机

是的,你没看错。一开始兔子哥先来介绍一下什么是蓝牙打印机。。。好吧,这个就交给百度了:

蓝牙打印机(Bluetooth printer)就是将蓝牙技术应用在打印机上,摆脱打印机连线所带来的不便,实现无线打印,可以减少桌面上令人不快的电缆,并且可以将打印机远离主机任意搬动,摆放在房间中适合的位置。

常见小票样式

Paste_Image.png

这个小票格式基本就是最常见的了。这里面的各种格式,都可以从蓝牙打印机的API里面找到。蓝牙打印机有好多API,我把常用的给封装了一下:PrintUtils.java

/**
 * 复位打印机
 */
public static final byte[] RESET = {0x1b, 0x40};

/**
 * 左对齐
 */
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};

/**
 * 中间对齐
 */
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};

/**
 * 右对齐
 */
public static final byte[] ALIGN_RIGHT = {0x1b, 0x61, 0x02};

/**
 * 选择加粗模式
 */
public static final byte[] BOLD = {0x1b, 0x45, 0x01};

/**
 * 取消加粗模式
 */
public static final byte[] BOLD_CANCEL = {0x1b, 0x45, 0x00};

/**
 * 宽高加倍
 */
public static final byte[] DOUBLE_HEIGHT_WIDTH = {0x1d, 0x21, 0x11};

/**
 * 宽加倍
 */
public static final byte[] DOUBLE_WIDTH = {0x1d, 0x21, 0x10};

/**
 * 高加倍
 */
public static final byte[] DOUBLE_HEIGHT = {0x1d, 0x21, 0x01};

/**
 * 字体不放大
 */
public static final byte[] NORMAL = {0x1d, 0x21, 0x00};

/**
 * 设置默认行间距
 */
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};

打印实现

打印小票,当然首先需要连接蓝牙打印机。至于如何扫描打印机,如何连接,这个都是标准的蓝牙方式,网上资料也很多。因为本博文主要关注打印格式,所以这个就不再赘述了。连接打印机后,需要从BluetoothSocket中获取OutputStream。然后接下来都是通过OutputStream来给打印机发送打印指令。

  • 设置打印格式
    设置打印格式,就要用到上面封装的那些指令了。
    /**
     * 设置打印格式
     *
     * @param command 格式指令
     */
    public static void selectCommand(byte[] command) {
        try {
            outputStream.write(command);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

用法如下:

  PrintUtils.selectCommand(PrintUtils.RESET);
  PrintUtils.selectCommand(PrintUtils.LINE_SPACING_DEFAULT);
  PrintUtils.selectCommand(PrintUtils.ALIGN_CENTER);
  PrintUtils.selectCommand(PrintUtils.NORMAL);
  • 打印文字
    /**
     * 打印文字
     *
     * @param text 要打印的文字
     */
    public static void printText(String text) {
        try {
            byte[] data = text.getBytes("gbk");
            outputStream.write(data, 0, data.length);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

用法如下:

  PrintUtils.printText("好吃的牛肉面" + "\n");

打印文字的时候,最后都要手动拼接一个 "\n" 用来换行。

完美吗?

根据上面封装的代码,“貌似”是可以实现所有的打印样式了。是的,没毛病。因为上面既有打印格式的设置,又有打印文字的用法。打印小票是没问题了。but……

这种格式如何实现?


Paste_Image.png

这种格式呢?


Paste_Image.png

有的盆友可能会说,这有啥问题的??? 并且给出了他们认为完美的解释:

  PrintUtils.printText("合计                           53.50" + "\n");
  PrintUtils.printText("抹零                            3.50" + "\n");
  PrintUtils.printText("项目            数量            金额" + "\n");

可是,完美吗?
你可能觉得人工加空格是可以“实现”需求。but……中间的空格,你知道应该添加多少吗?添加多了或者少了,打印出来的结果都会一塌糊涂!并且注意小票上都是要求对齐的!合计、抹零左侧对齐。金额右侧对齐。项目、数量、金额这三列都要中心对齐。。看到这里,这个人工加空格的做法,还完美吗?

给我一个完美的解释!

“海参炒面,海参呢?给我一个完美的解释!”
“我叫海参,面是我炒的。完美不?”

是的,我们需要一个完美的解释。到底如何实现上面说的打印两列打印三列的情况。
首先,讲解之前,先设置几个默认值:

    /**
     * 打印纸一行最大的字节
     */
    private static final int LINE_BYTE_SIZE = 32;

    /**
     * 打印三列时,中间一列的中心线距离打印纸左侧的距离
     */
    private static final int LEFT_LENGTH = 16;

    /**
     * 打印三列时,中间一列的中心线距离打印纸右侧的距离
     */
    private static final int RIGHT_LENGTH = 16;

    /**
     * 打印三列时,第一列汉字最多显示几个文字
     */
    private static final int LEFT_TEXT_MAX_LENGTH = 5;

我们知道,通用的打印纸都是有固定宽度的。经过大量测试,得出打印纸一行的最大字节数是32个字节。那么根据上面的注释,我们可以得到以下结论:

LEFT_LENGTH + RIGHT_LENGTH = LINE_BYTE_SIZE

这是毋庸置疑的。左侧宽度 + 右侧宽度 必须要等于打印纸总宽度。
而且因为打印三列的时候,中间一列是要居中显示的,所以LEFT_LENGTHRIGHT_LENGTH都必须是总宽度32的一半,也就是必须是16.

那么如何计算某个文字所占的字节数呢?

    /**
     * 获取数据长度
     *
     * @param msg
     * @return
     */
    @SuppressLint("NewApi")
    private static int getBytesLength(String msg) {
        return msg.getBytes(Charset.forName("GB2312")).length;
    }

OK,准备了这么多,海参终于准备好了。接下来就可以准备炒面了~

打印两列

    /**
     * 打印两列
     *
     * @param leftText  左侧文字
     * @param rightText 右侧文字
     * @return
     */
    @SuppressLint("NewApi")
    public static String printTwoData(String leftText, String rightText) {
        StringBuilder sb = new StringBuilder();
        int leftTextLength = getBytesLength(leftText);
        int rightTextLength = getBytesLength(rightText);
        sb.append(leftText);

        // 计算两侧文字中间的空格
        int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;

        for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
            sb.append(" ");
        }
        sb.append(rightText);
        return sb.toString();
    }

那位说话了:“你这代码明明也是手动拼的空格啊,完美个毛啊!”。大兄弟你消消气,这里是通过逻辑进行拼接的空格,不是无脑的拼接。打印两列的步骤如下:

  • 拼接左侧一列的文字
  • 拼接两侧文字中间的空格
  • 拼接右侧一列的文字

关键步骤是计算两侧文字中间的空格。怎么计算呢?很简单,总宽度 - 左侧文字长度 - 右侧文字长度 就是空格的长度。

打印三列

    /**
     * 打印三列
     *
     * @param leftText   左侧文字
     * @param middleText 中间文字
     * @param rightText  右侧文字
     * @return
     */
    @SuppressLint("NewApi")
    public static String printThreeData(String leftText, String middleText, String rightText) {
        StringBuilder sb = new StringBuilder();
        // 左边最多显示 LEFT_TEXT_MAX_LENGTH 个汉字 + 两个点
        if (leftText.length() > LEFT_TEXT_MAX_LENGTH) {
            leftText = leftText.substring(0, LEFT_TEXT_MAX_LENGTH) + "..";
        }
        int leftTextLength = getBytesLength(leftText);
        int middleTextLength = getBytesLength(middleText);
        int rightTextLength = getBytesLength(rightText);

        sb.append(leftText);
        // 计算左侧文字和中间文字的空格长度
        int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;

        for (int i = 0; i < marginBetweenLeftAndMiddle; i++) {
            sb.append(" ");
        }
        sb.append(middleText);

        // 计算右侧文字和中间文字的空格长度
        int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;

        for (int i = 0; i < marginBetweenMiddleAndRight; i++) {
            sb.append(" ");
        }

        // 打印的时候发现,最右边的文字总是偏右一个字符,所以需要删除一个空格
        sb.delete(sb.length() - 1, sb.length()).append(rightText);
        return sb.toString();
    }

打印三列的步骤如下:

  • 拼接左侧一列的文字
  • 拼接左侧文字和中间文字中间的空格
  • 拼接中间的文字
  • 拼接右侧文字和中间文字中间的空格
  • 拼接右侧一列的文字

在计算空格的时候,为了保证中间一列始终保持中心线对齐,所以在计算中间文字长度时候,都除以2。

完整打印代码

PrintUtils.selectCommand(PrintUtils.RESET);
PrintUtils.selectCommand(PrintUtils.LINE_SPACING_DEFAULT);
PrintUtils.selectCommand(PrintUtils.ALIGN_CENTER);
PrintUtils.printText("美食餐厅\n\n");
PrintUtils.selectCommand(PrintUtils.DOUBLE_HEIGHT_WIDTH);
PrintUtils.printText("桌号:1号桌\n\n");
PrintUtils.selectCommand(PrintUtils.NORMAL);
PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText(PrintUtils.printTwoData("订单编号", "201507161515\n"));
PrintUtils.printText(PrintUtils.printTwoData("点菜时间", "2016-02-16 10:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("上菜时间", "2016-02-16 11:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("人数:2人", "收银员:张三\n"));

PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD);
PrintUtils.printText(PrintUtils.printThreeData("项目", "数量", "金额\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD_CANCEL);
PrintUtils.printText(PrintUtils.printThreeData("面", "1", "0.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("米饭", "1", "6.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("铁板烧", "1", "26.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("一个测试", "1", "226.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("牛肉面啊啊", "1", "2226.00\n"));
PrintUtils.printText(PrintUtils.printThreeData("牛肉面啊啊啊牛肉面啊啊啊", "888", "98886.00\n"));

PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("合计", "53.50\n"));
PrintUtils.printText(PrintUtils.printTwoData("抹零", "3.50\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("应收", "50.00\n"));
PrintUtils.printText("--------------------------------\n");

PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText("备注:不要辣、不要香菜");
PrintUtils.printText("\n\n\n\n\n");

举一反三

Paste_Image.png

学习了上面的打印格式,那么这个小票怎么打印? 区别就是打印三列的时候,中间一列是偏右了。相信大家应该知道答案了。如果有疑问,可以给我留言。

鉴于好多读者给我留言,要PrintUtils工具类代码,所以我把代码发布到github上了,大家可以自行下载。地址是:https://github.com/heroxuetao/PrintUtils

如果有帮助到你,可以顺手来个star 。万分感谢!

本文止。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,585评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,283评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,760评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,461评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,280评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,268评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,656评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,322评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,629评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,691评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,445评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,299评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,694评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,982评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,244评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,642评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,829评论 2 335

推荐阅读更多精彩内容