第一步:首先需要一個藍牙打印工具類
import android.bluetooth.BluetoothSocket;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import com.github.promeg.pinyinhelper.Pinyin;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Calendar;
/**
-
藍牙打印工具類
*/
public class PrintUtil {private OutputStreamWriter mWriter = null;
private OutputStream mOutputStream = null;public final static int WIDTH_PIXEL = 384;
public final static int IMAGE_SIZE = 320;
private static String myTime;/**
- 初始化Pos實例
- @param encoding 編碼
- @throws IOException
*/
public PrintUtil(OutputStream outputStream, String encoding) throws IOException {
mWriter = new OutputStreamWriter(outputStream, encoding);
mOutputStream = outputStream;
initPrinter();
}
public void print(byte[] bs) throws IOException {
mOutputStream.write(bs);
}public void printRawBytes(byte[] bytes) throws IOException {
mOutputStream.write(bytes);
mOutputStream.flush();
}/**
- 初始化打印機
- @throws IOException
*/
public void initPrinter() throws IOException {
mWriter.write(0x1B);
mWriter.write(0x40);
mWriter.flush();
//獲取當前時間
getTime();
}
/**
- 打印換行
- @return length 需要打印的空行數
- @throws IOException
*/
public void printLine(int lineNum) throws IOException {
for (int i = 0; i < lineNum; i++) {
mWriter.write("\n");
}
mWriter.flush();
}
/**
- 打印換行(只換一行)
- @throws IOException
*/
public void printLine() throws IOException {
printLine(1);
}
/**
- 打印空白(一個Tab的位置,約4個漢字)
- @param length 需要打印空白的長度,
- @throws IOException
*/
public void printTabSpace(int length) throws IOException {
for (int i = 0; i < length; i++) {
mWriter.write("\t");
}
mWriter.flush();
}
/**
- 絕對打印位置
- @return
- @throws IOException
*/
public byte[] setLocation(int offset) throws IOException {
byte[] bs = new byte[4];
bs[0] = 0x1B;
bs[1] = 0x24;
bs[2] = (byte) (offset % 256);
bs[3] = (byte) (offset / 256);
return bs;
}
public byte[] getGbk(String stText) throws IOException {
byte[] returnText = stText.getBytes(“GBK”); // 必須放在try內纔可以
return returnText;
}private int getStringPixLength(String str) {
int pixLength = 0;
char c;
for (int i = 0; i < str.length(); i++) {
c = str.charAt(i);
if (Pinyin.isChinese©) {
pixLength += 24;
} else {
pixLength += 12;
}
}
return pixLength;
}public int getOffset(String str) {
return WIDTH_PIXEL - getStringPixLength(str);
}/**
- 打印文字
- @param text
- @throws IOException
*/
public void printText(String text) throws IOException {
mWriter.write(text);
mWriter.flush();
}
/**
- 對齊0:左對齊,1:居中,2:右對齊
*/
public void printAlignment(int alignment) throws IOException {
mWriter.write(0x1b);
mWriter.write(0x61);
mWriter.write(alignment);
}
public void printLargeText(String text) throws IOException {
mWriter.write(0x1b); mWriter.write(0x21); mWriter.write(48); mWriter.write(text); mWriter.write(0x1b); mWriter.write(0x21); mWriter.write(0); mWriter.flush();
}
public void printTwoColumn(String title, String content) throws IOException {
int iNum = 0;
byte[] byteBuffer = new byte[100];
byte[] tmp;tmp = getGbk(title); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; tmp = setLocation(getOffset(content)); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; tmp = getGbk(content); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); print(byteBuffer);
}
public void printThreeColumn(String left, String middle, String right) throws IOException {
int iNum = 0;
byte[] byteBuffer = new byte[200];
byte[] tmp = new byte[0];System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; tmp = getGbk(left); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; int pixLength = getStringPixLength(left) % WIDTH_PIXEL; if (pixLength > WIDTH_PIXEL / 2 || pixLength == 0) { middle = "\n\t\t" + middle; } tmp = setLocation(192); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; tmp = getGbk(middle); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; tmp = setLocation(getOffset(right)); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); iNum += tmp.length; tmp = getGbk(right); System.arraycopy(tmp, 0, byteBuffer, iNum, tmp.length); print(byteBuffer);
}
public void printDashLine() throws IOException {
printText("--------------------------------");
}public void printBitmap(Bitmap bmp) throws IOException {
bmp = compressPic(bmp);
byte[] bmpByteArray = draw2PxPoint(bmp);
printRawBytes(bmpByteArray);
}/*************************************************************************
-
假設一個360*360的圖片,分辨率設爲24, 共分15行打印 每一行,是一個 360 * 24 的點陣,y軸有24個點,存儲在3個byte裏面。
-
即每個byte存儲8個像素點信息。因爲只有黑白兩色,所以對應爲1的位是黑色,對應爲0的位是白色
**************************************************************************/
private byte[] draw2PxPoint(Bitmap bmp) {
//先設置一個足夠大的size,最後在用數組拷貝複製到一個精確大小的byte數組中
int size = bmp.getWidth() * bmp.getHeight() / 8 + 1000;
byte[] tmp = new byte[size];
int k = 0;
// 設置行距爲0
tmp[k++] = 0x1B;
tmp[k++] = 0x33;
tmp[k++] = 0x00;
// 居中打印
tmp[k++] = 0x1B;
tmp[k++] = 0x61;
tmp[k++] = 1;
for (int j = 0; j < bmp.getHeight() / 24f; j++) {
tmp[k++] = 0x1B;
tmp[k++] = 0x2A;// 0x1B 2A 表示圖片打印指令
tmp[k++] = 33; // m=33時,選擇24點密度打印
tmp[k++] = (byte) (bmp.getWidth() % 256); // nL
tmp[k++] = (byte) (bmp.getWidth() / 256); // nH
for (int i = 0; i < bmp.getWidth(); i++) {
for (int m = 0; m < 3; m++) {
for (int n = 0; n < 8; n++) {
byte b = px2Byte(i, j * 24 + m * 8 + n, bmp);
tmp[k] += tmp[k] + b;
}
k++;
}
}
tmp[k++] = 10;// 換行
}
// 恢復默認行距
tmp[k++] = 0x1B;
tmp[k++] = 0x32;byte[] result = new byte[k];
System.arraycopy(tmp, 0, result, 0, k);
return result;
}
/**
- 圖片二值化,黑色是1,白色是0
- @param x 橫座標
- @param y 縱座標
- @param bit 位圖
- @return
*/
private byte px2Byte(int x, int y, Bitmap bit) {
if (x < bit.getWidth() && y < bit.getHeight()) {
byte b;
int pixel = bit.getPixel(x, y);
int red = (pixel & 0x00ff0000) >> 16; // 取高兩位
int green = (pixel & 0x0000ff00) >> 8; // 取中兩位
int blue = pixel & 0x000000ff; // 取低兩位
int gray = RGB2Gray(red, green, blue);
if (gray < 128) {
b = 1;
} else {
b = 0;
}
return b;
}
return 0;
}
/**
- 圖片灰度的轉化
*/
private int RGB2Gray(int r, int g, int b) {
int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); // 灰度轉化公式
return gray;
}
/**
- 對圖片進行壓縮(去除透明度)
- @param bitmapOrg
*/
private Bitmap compressPic(Bitmap bitmapOrg) {
// 獲取這個圖片的寬和高
int width = bitmapOrg.getWidth();
int height = bitmapOrg.getHeight();
// 定義預轉換成的圖片的寬度和高度
int newWidth = IMAGE_SIZE;
int newHeight = IMAGE_SIZE;
Bitmap targetBmp = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
Canvas targetCanvas = new Canvas(targetBmp);
targetCanvas.drawColor(0xffffffff);
targetCanvas.drawBitmap(bitmapOrg, new Rect(0, 0, width, height), new Rect(0, 0, newWidth, newHeight), null);
return targetBmp;
}
public static void printTest(BluetoothSocket bluetoothSocket, Bitmap bitmap) {
try { PrintUtil pUtil = new PrintUtil(bluetoothSocket.getOutputStream(), "GBK"); // 店鋪名 居中 放大 pUtil.printAlignment(1); pUtil.printLargeText("廣州德勝"); pUtil.printLine(); pUtil.printAlignment(0); pUtil.printLine(); pUtil.printTwoColumn("時間:", myTime); pUtil.printLine(); pUtil.printTwoColumn("訂單號:", System.currentTimeMillis() + ""); pUtil.printLine(); pUtil.printTwoColumn("付款人:", "VitaminChen"); pUtil.printLine(); // 分隔線 pUtil.printDashLine(); pUtil.printLine(); //打印商品列表 pUtil.printText("商品"); pUtil.printTabSpace(2); pUtil.printText("數量"); pUtil.printTabSpace(1); pUtil.printText(" 單價"); pUtil.printLine(); pUtil.printThreeColumn("iphone6", "1", "4999.00"); pUtil.printThreeColumn("測試測試", "1", "4999.00"); pUtil.printDashLine(); pUtil.printLine(); pUtil.printTwoColumn("訂單金額:", "9998.00"); pUtil.printLine(); pUtil.printTwoColumn("實收金額:", "10000.00"); pUtil.printLine(); pUtil.printTwoColumn("找零:", "2.00"); pUtil.printLine(); pUtil.printDashLine(); //打印圖片 // pUtil.printBitmap(bitmap); pUtil.printLine(4); } catch (IOException e) { }
}
private void getTime() {
final Calendar c = Calendar.getInstance();
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH) + 1;
int day = c.get(Calendar.DATE);
int hour = c.get(Calendar.HOUR);
int minute = c.get(Calendar.MINUTE);
if (month > 9) {
myTime = year + “-” + month + “-” + day;
} else {
if (day > 9) {
myTime = year + “-” + “0” + month + “-” + day;
} else {
myTime = year + “-” + “0” + month + “-” + “0” + day;
}}
}
}
第二步 :還有一個檢測藍牙是否連接的工具類
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public class BluetoothUtil {
/**
* 藍牙是否打開
*/
public static boolean isBluetoothOn() {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter != null)
// 藍牙已打開
if (mBluetoothAdapter.isEnabled())
return true;
return false;
}
/**
* 獲取所有已配對的設備
*/
public static List<BluetoothDevice> getPairedDevices() {
List deviceList = new ArrayList<>();
Set<BluetoothDevice> pairedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
deviceList.add(device);
}
}
return deviceList;
}
/**
* 獲取所有已配對的打印類設備
*/
public static List<BluetoothDevice> getPairedPrinterDevices() {
return getSpecificDevice(BluetoothClass.Device.Major.IMAGING);
}
/**
* 從已配對設配中,刪選出某一特定類型的設備展示
* @param deviceClass
* @return
*/
public static List<BluetoothDevice> getSpecificDevice(int deviceClass){
List<BluetoothDevice> devices = BluetoothUtil.getPairedDevices();
List<BluetoothDevice> printerDevices = new ArrayList<>();
for (BluetoothDevice device : devices) {
BluetoothClass klass = device.getBluetoothClass();
// 關於藍牙設備分類參考 http://stackoverflow.com/q/23273355/4242112
if (klass.getMajorDeviceClass() == deviceClass)
printerDevices.add(device);
}
return printerDevices;
}
/**
* 彈出系統對話框,請求打開藍牙
*/
public static void openBluetooth(Activity activity) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(enableBtIntent, 666);
}
public static BluetoothSocket connectDevice(BluetoothDevice device) {
BluetoothSocket socket = null;
try {
socket = device.createRfcommSocketToServiceRecord(
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
socket.connect();
} catch (IOException e) {
try {
socket.close();
} catch (IOException closeException) {
return null;
}
return null;
}
return socket;
}
}
第三步:創建一個基類
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
import com.will.bluetoothprinterdemo.utils.BluetoothUtil;
import java.io.IOException;
public abstract class BasePrintActivity extends AppCompatActivity {
String tag = getClass().getSimpleName();
private BluetoothSocket mSocket;
private BluetoothStateReceiver mBluetoothStateReceiver;
private AsyncTask mConnectTask;
private ProgressDialog mProgressDialog;
/**
* 藍牙連接成功後回調,該方法在子線程執行,可執行耗時操作
*/
public abstract void onConnected(BluetoothSocket socket, int taskType);
/**
* 藍牙狀態發生變化時回調
*/
public void onBluetoothStateChanged(Intent intent) {
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initReceiver();
}
@Override
protected void onStop() {
cancelConnectTask();
closeSocket();
super.onStop();
}
protected void closeSocket() {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
mSocket = null;
e.printStackTrace();
}
}
}
protected void cancelConnectTask() {
if (mConnectTask != null) {
mConnectTask.cancel(true);
mConnectTask = null;
}
}
@Override
protected void onDestroy() {
unregisterReceiver(mBluetoothStateReceiver);
super.onDestroy();
}
private void initReceiver() {
mBluetoothStateReceiver = new BluetoothStateReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mBluetoothStateReceiver, filter);
}
/**
* 檢查藍牙狀態,如果已打開,則查找已綁定設備
*
* @return
*/
public boolean checkBluetoothState() {
if (BluetoothUtil.isBluetoothOn()) {
return true;
} else {
BluetoothUtil.openBluetooth(this);
return false;
}
}
public void connectDevice(BluetoothDevice device, int taskType) {
if (checkBluetoothState() && device != null) {
mConnectTask = new ConnectBluetoothTask(taskType).execute(device);
}
}
class ConnectBluetoothTask extends AsyncTask<BluetoothDevice, Integer, BluetoothSocket> {
int mTaskType;
public ConnectBluetoothTask(int taskType) {
this.mTaskType = taskType;
}
@Override
protected void onPreExecute() {
showProgressDialog("請稍候...");
super.onPreExecute();
}
@Override
protected BluetoothSocket doInBackground(BluetoothDevice... params) {
if(mSocket != null){
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
mSocket = BluetoothUtil.connectDevice(params[0]);;
onConnected(mSocket, mTaskType);
return mSocket;
}
@Override
protected void onPostExecute(BluetoothSocket socket) {
mProgressDialog.dismiss();
if (socket == null || !socket.isConnected()) {
toast("連接打印機失敗");
} else {
toast("成功!");
}
super.onPostExecute(socket);
}
}
protected void showProgressDialog(String message) {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setCancelable(false);
}
mProgressDialog.setMessage(message);
if (!mProgressDialog.isShowing()) {
mProgressDialog.show();
}
}
protected void toast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
/**
* 監聽藍牙狀態變化的系統廣播
*/
class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
switch (state) {
case BluetoothAdapter.STATE_TURNING_ON:
toast("藍牙已開啓");
break;
case BluetoothAdapter.STATE_TURNING_OFF:
toast("藍牙已關閉");
break;
}
onBluetoothStateChanged(intent);
}
}
}
第四步:主界面代碼
package com.will.bluetoothprinterdemo.ui;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.will.bluetoothprinterdemo.R;
import com.will.bluetoothprinterdemo.utils.BluetoothUtil;
import com.will.bluetoothprinterdemo.utils.PrintUtil;
import java.util.List;
public class PrinterSettingActivity extends BasePrintActivity implements View.OnClickListener{
ListView mLvPairedDevices;
Button mBtnSetting;
Button mBtnTest;
Button mBtnPrint;
DeviceListAdapter mAdapter;
int mSelectedPosition = -1;
final static int TASK_TYPE_CONNECT = 1;
final static int TASK_TYPE_PRINT = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_printer_setting);
initViews();
}
@Override
protected void onResume() {
super.onResume();
fillAdapter();
}
private void initViews() {
mLvPairedDevices = (ListView) findViewById(R.id.lv_paired_devices);
mBtnSetting = (Button) findViewById(R.id.btn_goto_setting);
mBtnTest = (Button) findViewById(R.id.btn_test_conntect);
mBtnPrint = (Button) findViewById(R.id.btn_print);
mLvPairedDevices.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mSelectedPosition = position;
mAdapter.notifyDataSetChanged();
}
});
mBtnSetting.setOnClickListener(this);
mBtnTest.setOnClickListener(this);
mBtnPrint.setOnClickListener(this);
mAdapter = new DeviceListAdapter(this);
mLvPairedDevices.setAdapter(mAdapter);
}
/**
* 從所有已配對設備中找出打印設備並顯示
*/
private void fillAdapter() {
//推薦使用 BluetoothUtil.getPairedPrinterDevices()
List<BluetoothDevice> printerDevices = BluetoothUtil.getPairedDevices();
mAdapter.clear();
mAdapter.addAll(printerDevices);
refreshButtonText(printerDevices);
}
private void refreshButtonText(List<BluetoothDevice> printerDevices) {
if (printerDevices.size() > 0) {
mBtnSetting.setText("配對更多設備");
} else {
mBtnSetting.setText("還未配對打印機,去設置");
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_goto_setting:
startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
break;
case R.id.btn_test_conntect:
connectDevice(TASK_TYPE_CONNECT);
break;
case R.id.btn_print:
connectDevice(TASK_TYPE_PRINT);
break;
}
}
private void connectDevice(int taskType){
if(mSelectedPosition >= 0){
BluetoothDevice device = mAdapter.getItem(mSelectedPosition);
if(device!= null)
super.connectDevice(device, taskType);
}else{
Toast.makeText(this, "還未選擇打印設備", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onConnected(BluetoothSocket socket, int taskType) {
switch (taskType){
case TASK_TYPE_PRINT:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test_image);
PrintUtil.printTest(socket, bitmap);
break;
}
}
class DeviceListAdapter extends ArrayAdapter<BluetoothDevice> {
public DeviceListAdapter(Context context) {
super(context, 0);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
BluetoothDevice device = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_bluetooth_device, parent, false);
}
TextView tvDeviceName = (TextView) convertView.findViewById(R.id.tv_device_name);
CheckBox cbDevice = (CheckBox) convertView.findViewById(R.id.cb_device);
tvDeviceName.setText(device.getName());
cbDevice.setChecked(position == mSelectedPosition);
return convertView;
}
}
}
第五步:主界面佈局
activity_printer_setting.xml
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/colorGrayTertiary"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:text="選擇一個已配對的打印設備"
android:textColor="@color/textColorSecondary"
android:textSize="14sp" />
<ListView
android:id="@+id/lv_paired_devices"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="@color/colorWhite"
android:choiceMode="singleChoice"
android:divider="@android:drawable/divider_horizontal_bright" />
<Button
android:id="@+id/btn_goto_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/common_margin"
android:layout_marginRight="@dimen/common_margin"
android:layout_marginTop="@dimen/common_margin"
android:text="還未配對打印機,去設置" />
<Button
android:id="@+id/btn_test_conntect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/common_margin"
android:layout_marginRight="@dimen/common_margin"
android:layout_marginTop="@dimen/common_margin"
android:text="測試打印機連接" />
<Button
android:id="@+id/btn_print"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/common_margin"
android:layout_marginRight="@dimen/common_margin"
android:layout_marginTop="@dimen/common_margin"
android:text="打印"
android:layout_marginBottom="@dimen/common_margin"/>
佈局二:
<?xml version="1.0" encoding="utf-8"?><TextView
android:id="@+id/tv_device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="藍牙打印機"
android:textColor="@color/textColorPrimary"
android:textSize="14sp" />
<android.support.v7.widget.AppCompatCheckBox
android:id="@+id/cb_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:textSize="14sp" />