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閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容