Android藍(lán)牙打印機(jī),帶你真正了解各種打印格式

注意:本文原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處。歡迎關(guān)注我的 簡書 。

本文主要講解藍(lán)牙打印機(jī)在打印小票的過程中,如何打印各種常見格式。由于之前需要調(diào)試打印格式,但是苦于網(wǎng)上沒有詳細(xì)的講解教程,無奈只能自給自足,自己封裝了一個(gè)。如果各位盆友正在或者曾經(jīng)苦惱藍(lán)牙打印機(jī)的打印格式,那么恭喜你,本篇博文就是你要尋找的。

什么是藍(lán)牙打印機(jī)

是的,你沒看錯(cuò)。一開始兔子哥先來介紹一下什么是藍(lán)牙打印機(jī)。。。好吧,這個(gè)就交給百度了:

藍(lán)牙打印機(jī)(Bluetooth printer)就是將藍(lán)牙技術(shù)應(yīng)用在打印機(jī)上,擺脫打印機(jī)連線所帶來的不便,實(shí)現(xiàn)無線打印,可以減少桌面上令人不快的電纜,并且可以將打印機(jī)遠(yuǎn)離主機(jī)任意搬動(dòng),擺放在房間中適合的位置。

常見小票樣式

Paste_Image.png

這個(gè)小票格式基本就是最常見的了。這里面的各種格式,都可以從藍(lán)牙打印機(jī)的API里面找到。藍(lán)牙打印機(jī)有好多API,我把常用的給封裝了一下:PrintUtils.java

/**
 * 復(fù)位打印機(jī)
 */
public static final byte[] RESET = {0x1b, 0x40};

/**
 * 左對(duì)齊
 */
public static final byte[] ALIGN_LEFT = {0x1b, 0x61, 0x00};

/**
 * 中間對(duì)齊
 */
public static final byte[] ALIGN_CENTER = {0x1b, 0x61, 0x01};

/**
 * 右對(duì)齊
 */
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};

/**
 * 設(shè)置默認(rèn)行間距
 */
public static final byte[] LINE_SPACING_DEFAULT = {0x1b, 0x32};

打印實(shí)現(xiàn)

打印小票,當(dāng)然首先需要連接藍(lán)牙打印機(jī)。至于如何掃描打印機(jī),如何連接,這個(gè)都是標(biāo)準(zhǔn)的藍(lán)牙方式,網(wǎng)上資料也很多。因?yàn)楸静┪闹饕P(guān)注打印格式,所以這個(gè)就不再贅述了。連接打印機(jī)后,需要從BluetoothSocket中獲取OutputStream。然后接下來都是通過OutputStream來給打印機(jī)發(fā)送打印指令。

  • 設(shè)置打印格式
    設(shè)置打印格式,就要用到上面封裝的那些指令了。
    /**
     * 設(shè)置打印格式
     *
     * @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");

打印文字的時(shí)候,最后都要手動(dòng)拼接一個(gè) "\n" 用來換行。

完美嗎?

根據(jù)上面封裝的代碼,“貌似”是可以實(shí)現(xiàn)所有的打印樣式了。是的,沒毛病。因?yàn)樯厦婕扔写蛴「袷降脑O(shè)置,又有打印文字的用法。打印小票是沒問題了。but……

這種格式如何實(shí)現(xiàn)?


Paste_Image.png

這種格式呢?


Paste_Image.png

有的盆友可能會(huì)說,這有啥問題的??? 并且給出了他們認(rèn)為完美的解釋:

  PrintUtils.printText("合計(jì)                           53.50" + "\n");
  PrintUtils.printText("抹零                            3.50" + "\n");
  PrintUtils.printText("項(xiàng)目            數(shù)量            金額" + "\n");

可是,完美嗎?
你可能覺得人工加空格是可以“實(shí)現(xiàn)”需求。but……中間的空格,你知道應(yīng)該添加多少嗎?添加多了或者少了,打印出來的結(jié)果都會(huì)一塌糊涂!并且注意小票上都是要求對(duì)齊的!合計(jì)、抹零左側(cè)對(duì)齊。金額右側(cè)對(duì)齊。項(xiàng)目、數(shù)量、金額這三列都要中心對(duì)齊。??吹竭@里,這個(gè)人工加空格的做法,還完美嗎?

給我一個(gè)完美的解釋!

“海參炒面,海參呢?給我一個(gè)完美的解釋!”
“我叫海參,面是我炒的。完美不?”

是的,我們需要一個(gè)完美的解釋。到底如何實(shí)現(xiàn)上面說的打印兩列、打印三列的情況。
首先,講解之前,先設(shè)置幾個(gè)默認(rèn)值:

    /**
     * 打印紙一行最大的字節(jié)
     */
    private static final int LINE_BYTE_SIZE = 32;

    /**
     * 打印三列時(shí),中間一列的中心線距離打印紙左側(cè)的距離
     */
    private static final int LEFT_LENGTH = 16;

    /**
     * 打印三列時(shí),中間一列的中心線距離打印紙右側(cè)的距離
     */
    private static final int RIGHT_LENGTH = 16;

    /**
     * 打印三列時(shí),第一列漢字最多顯示幾個(gè)文字
     */
    private static final int LEFT_TEXT_MAX_LENGTH = 5;

我們知道,通用的打印紙都是有固定寬度的。經(jīng)過大量測試,得出打印紙一行的最大字節(jié)數(shù)是32個(gè)字節(jié)。那么根據(jù)上面的注釋,我們可以得到以下結(jié)論:

LEFT_LENGTH + RIGHT_LENGTH = LINE_BYTE_SIZE

這是毋庸置疑的。左側(cè)寬度 + 右側(cè)寬度 必須要等于打印紙總寬度。
而且因?yàn)榇蛴∪械臅r(shí)候,中間一列是要居中顯示的,所以LEFT_LENGTHRIGHT_LENGTH都必須是總寬度32的一半,也就是必須是16.

那么如何計(jì)算某個(gè)文字所占的字節(jié)數(shù)呢?

    /**
     * 獲取數(shù)據(jù)長度
     *
     * @param msg
     * @return
     */
    @SuppressLint("NewApi")
    private static int getBytesLength(String msg) {
        return msg.getBytes(Charset.forName("GB2312")).length;
    }

OK,準(zhǔn)備了這么多,海參終于準(zhǔn)備好了。接下來就可以準(zhǔn)備炒面了~

打印兩列

    /**
     * 打印兩列
     *
     * @param leftText  左側(cè)文字
     * @param rightText 右側(cè)文字
     * @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);

        // 計(jì)算兩側(cè)文字中間的空格
        int marginBetweenMiddleAndRight = LINE_BYTE_SIZE - leftTextLength - rightTextLength;

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

那位說話了:“你這代碼明明也是手動(dòng)拼的空格啊,完美個(gè)毛啊!”。大兄弟你消消氣,這里是通過邏輯進(jìn)行拼接的空格,不是無腦的拼接。打印兩列的步驟如下:

  • 拼接左側(cè)一列的文字
  • 拼接兩側(cè)文字中間的空格
  • 拼接右側(cè)一列的文字

關(guān)鍵步驟是計(jì)算兩側(cè)文字中間的空格。怎么計(jì)算呢?很簡單,總寬度 - 左側(cè)文字長度 - 右側(cè)文字長度 就是空格的長度。

打印三列

    /**
     * 打印三列
     *
     * @param leftText   左側(cè)文字
     * @param middleText 中間文字
     * @param rightText  右側(cè)文字
     * @return
     */
    @SuppressLint("NewApi")
    public static String printThreeData(String leftText, String middleText, String rightText) {
        StringBuilder sb = new StringBuilder();
        // 左邊最多顯示 LEFT_TEXT_MAX_LENGTH 個(gè)漢字 + 兩個(gè)點(diǎn)
        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);
        // 計(jì)算左側(cè)文字和中間文字的空格長度
        int marginBetweenLeftAndMiddle = LEFT_LENGTH - leftTextLength - middleTextLength / 2;

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

        // 計(jì)算右側(cè)文字和中間文字的空格長度
        int marginBetweenMiddleAndRight = RIGHT_LENGTH - middleTextLength / 2 - rightTextLength;

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

        // 打印的時(shí)候發(fā)現(xiàn),最右邊的文字總是偏右一個(gè)字符,所以需要?jiǎng)h除一個(gè)空格
        sb.delete(sb.length() - 1, sb.length()).append(rightText);
        return sb.toString();
    }

打印三列的步驟如下:

  • 拼接左側(cè)一列的文字
  • 拼接左側(cè)文字和中間文字中間的空格
  • 拼接中間的文字
  • 拼接右側(cè)文字和中間文字中間的空格
  • 拼接右側(cè)一列的文字

在計(jì)算空格的時(shí)候,為了保證中間一列始終保持中心線對(duì)齊,所以在計(jì)算中間文字長度時(shí)候,都除以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("桌號(hào):1號(hào)桌\n\n");
PrintUtils.selectCommand(PrintUtils.NORMAL);
PrintUtils.selectCommand(PrintUtils.ALIGN_LEFT);
PrintUtils.printText(PrintUtils.printTwoData("訂單編號(hào)", "201507161515\n"));
PrintUtils.printText(PrintUtils.printTwoData("點(diǎn)菜時(shí)間", "2016-02-16 10:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("上菜時(shí)間", "2016-02-16 11:46\n"));
PrintUtils.printText(PrintUtils.printTwoData("人數(shù):2人", "收銀員:張三\n"));

PrintUtils.printText("--------------------------------\n");
PrintUtils.selectCommand(PrintUtils.BOLD);
PrintUtils.printText(PrintUtils.printThreeData("項(xiàng)目", "數(shù)量", "金額\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("一個(gè)測試", "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("合計(jì)", "53.50\n"));
PrintUtils.printText(PrintUtils.printTwoData("抹零", "3.50\n"));
PrintUtils.printText("--------------------------------\n");
PrintUtils.printText(PrintUtils.printTwoData("應(yīng)收", "50.00\n"));
PrintUtils.printText("--------------------------------\n");

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

舉一反三

Paste_Image.png

學(xué)習(xí)了上面的打印格式,那么這個(gè)小票怎么打?。?區(qū)別就是打印三列的時(shí)候,中間一列是偏右了。相信大家應(yīng)該知道答案了。如果有疑問,可以給我留言。

鑒于好多讀者給我留言,要PrintUtils工具類代碼,所以我把代碼發(fā)布到github上了,大家可以自行下載。地址是:https://github.com/heroxuetao/PrintUtils

如果有幫助到你,可以順手來個(gè)star 。萬分感謝!

本文止。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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