Android 实现蓝牙打印的功能

第一步:首先需要一个蓝牙打印工具类
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 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





布局二:




你可能感兴趣的:(Android)