第一步:首先需要一个蓝牙打印工具类
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;
/**
public void print(byte[] bs) throws IOException {
mOutputStream.write(bs);
}
public void printRawBytes(byte[] bytes) throws IOException {
mOutputStream.write(bytes);
mOutputStream.flush();
}
/**
/**
/**
/**
/**
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);
}
/**
/**
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;
}
/**
/**
/**
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 getPairedDevices() {
List deviceList = new ArrayList<>();
Set pairedDevices = BluetoothAdapter.getDefaultAdapter().getBondedDevices();
if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
deviceList.add(device);
}
}
return deviceList;
}
/**
* 获取所有已配对的打印类设备
*/
public static List getPairedPrinterDevices() {
return getSpecificDevice(BluetoothClass.Device.Major.IMAGING);
}
/**
* 从已配对设配中,删选出某一特定类型的设备展示
* @param deviceClass
* @return
*/
public static List getSpecificDevice(int deviceClass){
List devices = BluetoothUtil.getPairedDevices();
List 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 {
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 printerDevices = BluetoothUtil.getPairedDevices();
mAdapter.clear();
mAdapter.addAll(printerDevices);
refreshButtonText(printerDevices);
}
private void refreshButtonText(List 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 {
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
布局二: