Android 蓝牙打印小票与WiFi打印小票两种打印方式的实现(带有图片和二维码)

来源:互联网 发布:jira 数据库表结构 编辑:程序博客网 时间:2024/05/22 11:35

最近在做小票打印这块,项目需求是蓝牙和WiFi两种都要实现,开始做的时候也是一脸懵,然后网上找了一些代码,看了好多博主的文章,最后自己把这些都综合到一起0.0,结果还好成功了

先看看图吧,蓝牙和WiFi打印出的都是一样的,毕竟都是同一个工具类(打印机型号  爱宝POS-80C 、 资江 ZJ-80)

先来看看WiFi打印(旁边的色块是打印纸自带的,我也很无奈)

首先在你的activity里触发点击事件

right = (TextView) findViewById(R.id.right);//点击WiFi打印right.setOnClickListener(new View.OnClickListener() {    @Override    public void onClick(View v) {        pos();    }}); //调用打印的方法
  private void pos() {        // 开启一个子线程        new Thread() {            public void run() {                try {              //打印机网口IP    打印机端口号     编码方式                    pos = new Pos(Preference.instance(PayTypeActivity.this).getIPAddress(), Integer.parseInt(Preference.instance(PayTypeActivity.this).getPost_IDs()), "gbk");                    //初始化打印机                    pos.initPos();                    if (!TextUtils.isEmpty(objBean.getHead_img())){                        pos.printLocation(1);                        bitmap = pos.compressPic(returnBitMap(objBean.getHead_img()));                        pos.draw2PxPoint(bitmap);                    }                    pos.printLocation(1);                    pos.bold(true);                    pos.printTabSpace(2);                    pos.printWordSpace(1);                    pos.printTextNewLine(objBean.getShop_name());                    pos.printLocation(0);                    pos.printTextNewLine("----------------------------------------------");                    pos.bold(false);                    pos.printTextNewLine("订 单 号:"+objBean.getSn());                    pos.printTextNewLine("用 户 名:"+objBean.getMember());                    pos.printTextNewLine("订单日期:"+objBean.getCreate_time());                    pos.printTextNewLine("支付方式:"+objBean.getPay_way());                    pos.printTextNewLine("订单备注:"+objBean.getRemarks());                    pos.printLine(2);                    pos.printText("货号          尺码    颜色    数量   小计");                    pos.printLocation(20, 1);                    pos.printTextNewLine("----------------------------------------------");                    for (ParentBean foods : foodsBean) {                        pos.printTextNewLine(foods.getGoods_name()+"            "+foods.getCode_number()+"     "+foods.getColour()+"      "+foods.getNum()+"      "+foods.getPay_back());                        pos.printLocation(20, 1);                    }                    pos.printTextNewLine("----------------------------------------------");                    pos.printLocation(0);                    pos.printLine(1);                    pos.printTextNewLine("总数:"+objBean.getNums());                    pos.printTextNewLine("合计:"+objBean.getPay());                    pos.printLine(2);//                    //打印二维码  -- 如果提供了二维码的地址则用该方法//                    pos.qrCode(objBean.getQr_code());                    //打印二维码的图片 -- 如果提供了二维码的截图则用该方法                    if (!TextUtils.isEmpty(objBean.getQr_code())){                        pos.printLocation(1);                        Qbitmap = pos.compressPic(returnBitMap(objBean.getQr_code()));//url 转换为bitmap                        pos.draw2PxPoint(Qbitmap);                    }                    //切纸                    pos.feedAndCut();                    pos.closeIOAndSocket();                    pos = null;                } catch (UnknownHostException e) {                    e.printStackTrace();                    Log.d("tag", "错误信息1:" + e.toString());                } catch (IOException e) {                    e.printStackTrace();                    Log.d("tag", "错误信息2:" + e.toString());                }            }        }.start();    }
//将图片url转换为bitmap
public Bitmap returnBitMap(String url) { URL myFileUrl = null; Bitmap bitmap = null; try { myFileUrl = new URL(url); } catch (MalformedURLException e) { e.printStackTrace(); } try { HttpURLConnection conn = (HttpURLConnection) myFileUrl.openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); bitmap = BitmapFactory.decodeStream(is); is.close(); } catch (IOException e) { e.printStackTrace(); } return bitmap; }



再来看看调用的工具类(蓝牙和WiFi共用一个,只是初始化方式不同)
import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Rect;import java.io.DataOutputStream;import java.io.IOException;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.net.Socket;public class Pos {    //定义编码方式    private static String encoding = null;    private Socket sock = null;    // 通过socket流进行读写    private OutputStream socketOut = null;    private OutputStreamWriter writer = null;    /**     * 初始化Pos实例     *     * @param ip 打印机IP     * @param port 打印机端口号     * @param encoding 编码     * @throws IOException     */    public Pos(String ip, int port, String encoding) throws IOException {        sock = new Socket(ip, port);        socketOut = new DataOutputStream(sock.getOutputStream());        this.encoding = encoding;        writer = new OutputStreamWriter(socketOut, encoding);    }    //蓝牙初始化    public Pos(OutputStream outputStream, String encoding) throws IOException {        writer = new OutputStreamWriter(outputStream, encoding);        socketOut = outputStream;        initPrinter();    }    public void print(byte[] bs) throws IOException {        socketOut.write(bs);    }    public void printRawBytes(byte[] bytes) throws IOException {        socketOut.write(bytes);        socketOut.flush();    }    /**     * 初始化打印机     *     * @throws IOException     */    public void initPrinter() throws IOException {        socketOut.write(0x1B);        socketOut.write(0x40);        socketOut.flush();    }    /**     * 关闭IO流和Socket     *     * @throws IOException     */    public void closeIOAndSocket() throws IOException {        writer.close();        socketOut.close();        sock.close();    }    /**     * 打印二维码     *     * @param qrData 二维码的内容     * @throws IOException     */    public void qrCode(String qrData) throws IOException {        int moduleSize = 8;        int length = qrData.getBytes(encoding).length;        //打印二维码矩阵        writer.write(0x1D);// init        writer.write("(k");// adjust height of barcode        writer.write(length + 3); // pl        writer.write(0); // ph        writer.write(49); // cn        writer.write(80); // fn        writer.write(48); //        writer.write(qrData);        writer.write(0x1D);        writer.write("(k");        writer.write(3);        writer.write(0);        writer.write(49);        writer.write(69);        writer.write(48);        writer.write(0x1D);        writer.write("(k");        writer.write(3);        writer.write(0);        writer.write(49);        writer.write(67);        writer.write(moduleSize);        writer.write(0x1D);        writer.write("(k");        writer.write(3); // pl        writer.write(0); // ph        writer.write(49); // cn        writer.write(81); // fn        writer.write(48); // m        writer.flush();    }    /**     * 进纸并全部切割     *     * @return     * @throws IOException     */    public void feedAndCut() throws IOException {        writer.write(0x1D);        writer.write(86);        writer.write(65);        //    writer.write(0);        //切纸前走纸多少        writer.write(100);        writer.flush();        //另外一种切纸的方式        //    byte[] bytes = {29, 86, 0};        //    socketOut.write(bytes);    }    /**     * 打印换行     *     * @return length 需要打印的空行数     * @throws IOException     */    public void printLine(int lineNum) throws IOException {        for (int i = 0; i < lineNum; i++) {            writer.write("\n");        }        writer.flush();    }    /**     * 打印换行(只换一行)     *     * @throws IOException     */    protected void printLine() throws IOException {        writer.write("\n");        writer.flush();    }    /**     * 打印空白(一个Tab的位置,约4个汉字)     *     * @param length 需要打印空白的长度,     * @throws IOException     */    public void printTabSpace(int length) throws IOException {        for (int i = 0; i < length; i++) {            writer.write("\t");        }        writer.flush();    }    /**     * 打印空白(一个汉字的位置)     *     * @param length 需要打印空白的长度,     * @throws IOException     */    public void printWordSpace(int length) throws IOException {        for (int i = 0; i < length; i++) {            writer.write(" ");        }        writer.flush();    }    /**     * 打印位置调整     *     * @param position 打印位置 0:居左(默认) 1:居中 2:居右     * @throws IOException     */    public void printLocation(int position) throws IOException {        writer.write(0x1B);        writer.write(97);        writer.write(position);        writer.flush();    }    /**     * 绝对打印位置     *     * @throws IOException     */    public void printLocation(int light, int weight) throws IOException {        writer.write(0x1B);        writer.write(0x24);        writer.write(light);        writer.write(weight);        writer.flush();    }    /**     * 打印文字     *     * @param text     * @throws IOException     */    public void printText(String text) throws IOException {        String s = text;        byte[] content = s.getBytes("gbk");        socketOut.write(content);        socketOut.flush();    }    /**     * 新起一行,打印文字     *     * @param text     * @throws IOException     */    public void printTextNewLine(String text) throws IOException {        //换行        writer.write("\n");        writer.flush();        String s = text;        byte[] content = s.getBytes("gbk");        socketOut.write(content);        socketOut.flush();    }    /**     * 初始化打印机     *     * @throws IOException     */    public void initPos() throws IOException {        writer.write(0x1B);        writer.write(0x40);        writer.flush();    }    /**     * 加粗     *     * @param flag false为不加粗     * @return     * @throws IOException     */    public void bold(boolean flag) throws IOException {        if (flag) {            //常规粗细            writer.write(0x1B);            writer.write(69);            writer.write(0xF);            writer.flush();        } else {            //加粗            writer.write(0x1B);            writer.write(69);            writer.write(0);            writer.flush();        }    }    /**     * 对图片进行压缩(去除透明度)     *     * @param     */    public static Bitmap compressPic(Bitmap bitmap) {        // 获取这个图片的宽和高        int width = bitmap.getWidth();        int height = bitmap.getHeight();        // 指定调整后的宽度和高度        int newWidth = 200;        int newHeight = 200;        Bitmap targetBmp = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);        Canvas targetCanvas = new Canvas(targetBmp);        targetCanvas.drawColor(0xffffffff);        targetCanvas.drawBitmap(bitmap, new Rect(0, 0, width, height), new Rect(0, 0, newWidth, newHeight), null);        return targetBmp;    }    /**     * 灰度图片黑白化,黑色是1,白色是0     *     * @param x   横坐标     * @param y   纵坐标     * @param bit 位图     * @return     */    public static 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 static int RGB2Gray(int r, int g, int b) {        int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b);  //灰度转化公式        return gray;    }    /*************************************************************************     * 假设一个240*240的图片,分辨率设为24, 共分10行打印     * 每一行,是一个 240*24 的点阵, 每一列有24个点,存储在3个byte里面。     * 每个byte存储8个像素点信息。因为只有黑白两色,所以对应为1的位是黑色,对应为0的位是白色     **************************************************************************/    /**     * 把一张Bitmap图片转化为打印机可以打印的字节流     *     * @param bmp     * @return     */    public void draw2PxPoint(Bitmap bmp) throws IOException {        //用来存储转换后的 bitmap 数据。为什么要再加1000,这是为了应对当图片高度无法        //整除24时的情况。比如bitmap 分辨率为 240 * 250,占用 7500 byte,        //但是实际上要存储11行数据,每一行需要 24 * 240 / 8 =720byte 的空间。再加上一些指令存储的开销,        //所以多申请 1000byte 的空间是稳妥的,不然运行时会抛出数组访问越界的异常。        int size = bmp.getWidth() * bmp.getHeight() / 8 + 1000;        byte[] data = new byte[size];        int k = 0;        //设置行距为0的指令        data[k++] = 0x1B;        data[k++] = 0x33;        data[k++] = 0x00;        // 逐行打印        for (int j = 0; j < bmp.getHeight() / 24f; j++) {            //打印图片的指令            data[k++] = 0x1B;            data[k++] = 0x2A;            data[k++] = 33;            data[k++] = (byte) (bmp.getWidth() % 256); //nL            data[k++] = (byte) (bmp.getWidth() / 256); //nH            //对于每一行,逐列打印            for (int i = 0; i < bmp.getWidth(); i++) {                //每一列24个像素点,分为3个字节存储                for (int m = 0; m < 3; m++) {                    //每个字节表示8个像素点,0表示白色,1表示黑色                    for (int n = 0; n < 8; n++) {                        byte b = px2Byte(i, j * 24 + m * 8 + n, bmp);                        data[k] += data[k] + b;                    }                    k++;                }            }            data[k++] = 10;//换行        }        socketOut.write(data);        socketOut.flush();    }}WiFi打印小票到此基本上就可以了下面开始我们的蓝牙打印蓝牙打印的话首先就要有权限喽
//蓝牙权限<uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />再来看看蓝牙相关的布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <include layout="@layout/top_bar"/>    <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" />    <LinearLayout        android:layout_marginBottom="15dp"        android:layout_width="match_parent"        android:layout_height="50dp"        android:orientation="horizontal">        <Button            android:background="@drawable/submit_select"            android:textColor="@color/white"            android:id="@+id/btn_goto_setting"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:layout_marginLeft="@dimen/common_margin"            android:layout_marginRight="@dimen/common_margin"            android:text="还未配对打印机,去设置" />        <Button            android:background="@drawable/submit_select"            android:textColor="@color/white"            android:id="@+id/btn_test_conntect"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:layout_marginLeft="@dimen/common_margin"            android:layout_marginRight="@dimen/common_margin"            android:text="测试打印机连接" />        <Button            android:background="@drawable/submit_select"            android:id="@+id/btn_print"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="match_parent"            android:layout_marginLeft="@dimen/common_margin"            android:layout_marginRight="@dimen/common_margin"            android:text="打印"            android:textColor="@color/white"            />    </LinearLayout></LinearLayout>接着具体看看代码里面怎么实现的,包括蓝牙的一些检索和设置
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;private String id;private Pos pos;private OutputStream mOutputStream = null;private Bitmap head_bitmap;private Bitmap code_bitmap;private LinearLayout back;

@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_printer_setting);    id = getIntent().getStringExtra("id");    setTitle("蓝牙打印");    initViews();    print();//获取小票数据}

@Overrideprotected void onResume() {    super.onResume();    fillAdapter();}private void initViews() {    back = (LinearLayout) findViewById(R.id.back);    back.setOnClickListener(this);    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("还未配对打印机,去设置");    }}@Overridepublic 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;        case R.id.back:            finish();            break;        default:            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;    }}
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();
}}

@Overridepublic void onConnected(BluetoothSocket socket, int taskType) {
switch (taskType){
case TASK_TYPE_PRINT:
pos(socket);
break;
}}

R.layout.item_bluetooth_device的布局如下:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="48dp"    android:paddingLeft="@dimen/common_margin"    android:paddingRight="@dimen/common_margin">    <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" /></RelativeLayout>



最后还是打印小票的方法 pos(socket);这个方法和之前WiFi方法基本一致,只是初始化不一样
 private void pos(final BluetoothSocket socket) {        // 开启一个子线程        new Thread() {            public void run() {                try {                    //初始化打印机                    pos = new Pos(socket.getOutputStream(),"GBK");                    if (!TextUtils.isEmpty(objBean.getHead_img())){                        pos.printLocation(1);                        head_bitmap = pos.compressPic(returnBitMap(objBean.getHead_img()));                        pos.draw2PxPoint(head_bitmap);                    }                    pos.printLocation(1);                    pos.bold(true);                    pos.printTabSpace(2);                    pos.printWordSpace(1);                    pos.printTextNewLine(objBean.getShop_name());                    pos.printLocation(0);                    pos.printTextNewLine("----------------------------------------------");                    pos.bold(false);                    pos.printTextNewLine("订 单 号:"+objBean.getSn());                    pos.printTextNewLine("用 户 名:"+objBean.getMember());                    pos.printTextNewLine("订单日期:"+objBean.getCreate_time());                    pos.printTextNewLine("支付方式:"+objBean.getPay_way());                    pos.printTextNewLine("订单备注:"+objBean.getRemarks());                    pos.printLine(2);                    pos.printText("货号          尺码    颜色    数量   小计");                    pos.printLocation(20, 1);                    pos.printTextNewLine("----------------------------------------------");                    for (ParentBean foods : foodsBean) {                        pos.printTextNewLine(foods.getGoods_name()+"            "+foods.getCode_number()+"     "+foods.getColour()+"      "+foods.getNum()+"      "+foods.getPay_back());                        pos.printLocation(20, 1);                    }                    pos.printTextNewLine("----------------------------------------------");                    pos.printLocation(0);                    pos.printLine(1);                    pos.printTextNewLine("总数:"+objBean.getNums());                    pos.printTextNewLine("合计:"+objBean.getPay());                    pos.printLine(2);                    //打印二维码  -- 如果提供了二维码的地址则用该方法//                  pos.qrCode(objBean.getQr_code());                    //打印二维码的图片 -- 如果提供了二维码的截图则用该方法                    if (!TextUtils.isEmpty(objBean.getQr_code())){                        pos.printLocation(1);                        code_bitmap = pos.compressPic(returnBitMap(objBean.getQr_code()));//returnBitmap方法和上面WiFi的一样                        pos.draw2PxPoint(code_bitmap);                    }                    pos.printLine(4);                    //切纸                    pos.feedAndCut();//                  pos.closeIOAndSocket();                    pos = null;                } catch (UnknownHostException e) {                    Log.d("tag", "错误信息1:" + e.toString());                } catch (IOException e) {                    Log.d("tag", "错误信息2:" + e.toString());                }            }        }.start();    }到此的话基本两种方式都可以了,我用两台打印机试过都没问题









原创粉丝点击