最近做了一个项目使用Android手机打印excel表,现在把自己的心得分享给大家。
一、项目需求:
表格内容固定,不同的用户,每条信息打分不同,生成表格后打印出来盖章。
二、开发背景:
1、android系统自带打印功能,但是只能打印图片或者pdf
2、打印机使用的是 HP OfficeJet 200,打印机具有WI-FI Direct功能,可以在没有网络,没有电源的情况下便携打印
(机器本身就是一个路由器,android设备需要连接wifi至该打印机,android设备需要安装HP打印服务插件服务)
3、android设备中需要提前安装Hp Print Service插件
4、需要在项目assets目录中放入一个通用excel表
三、展示:(下面那个是原表,上面这个是打印出来的)
四、使用框架:
excel表编辑工具jxl.jar
网络rxvolley
五、关键代码:
package com.axin.wifiprint;
import android.Manifest;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.print.PrintAttributes;
import android.print.PrintManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.kymjs.rxvolley.RxVolley;
import com.kymjs.rxvolley.client.HttpCallback;
import com.kymjs.rxvolley.client.ProgressListener;
import java.io.File;
import java.io.InputStream;
import java.util.List;
import jxl.Workbook;
import jxl.write.WritableCell;
import jxl.write.WritableSheet;
public class MainActivity extends AppCompatActivity {
private WifiManager mWifiManager;
private TextView error_tv;
private WifiConnector connector;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
error_tv= (TextView) findViewById(R.id.error_tv);
findViewById(R.id.click1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
InputStream open = getResources().getAssets().open("分数表.xls");
Workbook workbook = Workbook.getWorkbook(open);
// 分数表.xls 得到的对象是只读的,
// 如果要修改Excel,需要创建一个可读的副本,副本指向原Excel文件(即下面的new File())
File file = new File(Environment.getExternalStoragePublicDirectory("Download") + "/分数表.xls");
if (file.exists()) {
file.delete();
}
file.createNewFile();
//创建workbook的副本
jxl.write.WritableWorkbook wbe = Workbook.createWorkbook(file, workbook);
//获取第一个sheet
WritableSheet sheet = wbe.getSheet(0);
//获取第四列、第七行单元格,在我这里就是张三5的成绩
WritableCell cell = sheet.getWritableCell(3, 6);
//获取这个单元格的格式
jxl.format.CellFormat cf = cell.getCellFormat();
// Label(x,y,z) 代表单元格的第x+1列,第y+1行, 内容z
// 在Label对象的子对象中指明单元格的位置和内容
//第四列、第七行,在我这里就是张三5的成绩
jxl.write.Label lbl = new jxl.write.Label(3, 6, "90");
//将修改后的单元格的格式设定成跟原来一样
lbl.setCellFormat(cf);
//将改过的单元格保存到sheet
sheet.addCell(lbl);
//将修改保存到workbook --》一定要保存
wbe.write();
//关闭workbook,释放内存 ---》一定要释放内存
wbe.close();
} catch (Exception e) {
e.printStackTrace();
}
}
});
findViewById(R.id.click2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//让你的后台帮你写一个excel转码成pdf的文件,然后下载下来并保存
/*
* 这里我要特别说明的是:
* 1、安卓系统内,没有任何可以使用的框架,直接将excel转为pdf文件
* 2、wps安卓版可以转码是因为他们与microsoft公司签署了协议,他们能够拿到excel的数据转码方式
* 3、wps没有给我们免费提供转码的框架或接口
* 4、我们可以使用itext5框架 手动绘制pdf文件,说实话很麻烦。而且文档都是外文的。有兴趣的同学可以参考
* 地址:https://developers.itextpdf.com/content/itext-5-examples
* 5、因此最简单的方法就是上传至自己的后台,只要服务器安装了microsoft office,后台代码还是很简单的(不做解释)
*/
//等待加载框
showDialog();
RxVolley.download(Environment.getExternalStoragePublicDirectory("Download") + "/分数表.pdf",
"http://app1.lnbhjg.com/handlers/APK/fenshubiao.pdf",
new ProgressListener() {
@Override
public void onProgress(long transferredBytes, long totalSize) {
if (transferredBytes == totalSize) {
hideDialog();
Toast.makeText(MainActivity.this, "可以打印了", Toast.LENGTH_SHORT).show();
}
}
}, new HttpCallback() {
@Override
public void onSuccess(String t) {
super.onSuccess(t);
}
@Override
public void onFailure(int errorNo, String strMsg) {
super.onFailure(errorNo, strMsg);
hideDialog();
Toast.makeText(MainActivity.this, "服务器请求失败", Toast.LENGTH_SHORT).show();
}
});
}
});
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
findViewById(R.id.click3).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
showDialog();
if (!mWifiManager.isWifiEnabled()) {
mWifiManager.setWifiEnabled(true);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
checkPemision();
}
});
}
private void checkPemision(){
//在checkCallerCanAccessScanResults中检查了ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION
//如果没有这两个权限,就会返回一个empty List
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// 申请一个(或多个)权限,并提供用于回调返回的获取码(用户定义)
Toast.makeText(MainActivity.this, "开启定位权限,才能使用打印功能", Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION}, 102);
} else {
openList();
}
} else {
openList();
}
}
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
hideDialog();
switch (msg.what){
case 90:
error_tv.setVisibility(View.VISIBLE);
break;
case 91:
print();
break;
case 92:
error_tv.setVisibility(View.VISIBLE);
break;
}
}
};
//HP OfficeJet200打印机 拥有WI-FI Direct功能 可以在没有网络 没有电源的情况下便携打印
//特点是:机器本身就是个路由器,用户通过Android通过wifi链接至该打印机 然后便能轻松打印
//wifi
private void openList() {
if(isHasHPNet()){
connector=new WifiConnector(mWifiManager,handler);
connector.connect("DIRECT-98-HP OfficeJet 200","12345678", WifiConnector.WifiCipherType.WIFICIPHER_WPA);
}else {
Toast.makeText(MainActivity.this, "请打开HP OfficeJet 200打印机后重试", Toast.LENGTH_SHORT).show();
}
}
private boolean isHasHPNet(){
List<ScanResult> scanResults = mWifiManager.getScanResults();
for(ScanResult result:scanResults){
if(result.SSID.equals("DIRECT-98-HP OfficeJet 200")){
return true;
}
}
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 102) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openList();
} else {
// 没有获取到权限,做特殊处理
hideDialog();
error_tv.setVisibility(View.VISIBLE);
Toast.makeText(MainActivity.this, "请按照打印说明继续完成打印功能", Toast.LENGTH_SHORT).show();
}
}
}
private void print() {
PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
PrintAttributes.Builder builder = new PrintAttributes.Builder();
builder.setColorMode(PrintAttributes.COLOR_MODE_COLOR);
//第一个参数是任务名称(随便起一个)
printManager.print("print1",
new MyPrintAdapter(this, Environment.getExternalStoragePublicDirectory("Download") + "/分数表.pdf"), builder.build());
}
// 加载progressdialog初始化
private ProgressDialog dialog;
private void showDialog() {
if (dialog == null) {
dialog = new ProgressDialog(this);
dialog.setMessage("请等待...");
dialog.setCancelable(false);
}
dialog.show();
}
private void hideDialog() {
if (dialog != null) {
dialog.dismiss();
}
}
}
package com.axin.wifiprint;
/**
* Created by Administrator on 2018/1/9.
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.pdf.PdfDocument;
import android.graphics.pdf.PdfDocument.PageInfo;
import android.graphics.pdf.PdfRenderer;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.pdf.PrintedPdfDocument;
import android.support.annotation.RequiresApi;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class MyPrintAdapter extends PrintDocumentAdapter {
private Context context;
private int pageHeight;
private int pageWidth;
private PdfDocument mPdfDocument;
private int totalpages = 1;
private String pdfPath;
private List<Bitmap> mlist;
public MyPrintAdapter(Context context,String pdfPath) {
this.context = context;
this.pdfPath = pdfPath;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal,
LayoutResultCallback callback,
Bundle metadata) {
mPdfDocument = new PrintedPdfDocument(context, newAttributes); //创建可打印PDF文档对象
pageHeight = newAttributes.getMediaSize().ISO_A4.getHeightMils() * 72 / 1000; //设置尺寸
pageWidth = newAttributes.getMediaSize().ISO_A4.getWidthMils() * 72 / 1000;
if (cancellationSignal.isCanceled()) {
callback.onLayoutCancelled();
return;
}
ParcelFileDescriptor mFileDescriptor = null;
PdfRenderer pdfRender = null;
PdfRenderer.Page page = null;
try {
mFileDescriptor = ParcelFileDescriptor.open(new File(pdfPath), ParcelFileDescriptor.MODE_READ_ONLY);
if (mFileDescriptor != null)
pdfRender = new PdfRenderer(mFileDescriptor);
mlist = new ArrayList<>();
if (pdfRender.getPageCount() > 0) {
totalpages = pdfRender.getPageCount();
for (int i = 0; i < pdfRender.getPageCount(); i++) {
if(null != page)
page.close();
page = pdfRender.openPage(i);
Bitmap bmp = Bitmap.createBitmap(page.getWidth()*2,page.getHeight()*2, Bitmap.Config.ARGB_8888);
page.render(bmp, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
mlist.add(bmp);
}
}
if(null != page)
page.close();
if(null != mFileDescriptor)
mFileDescriptor.close();
if (null != pdfRender)
pdfRender.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
if (totalpages > 0) {
PrintDocumentInfo.Builder builder = new PrintDocumentInfo
.Builder("分数表.pdf")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(totalpages); //构建文档配置信息
PrintDocumentInfo info = builder.build();
callback.onLayoutFinished(info, true);
} else {
callback.onLayoutFailed("Page count is zero.");
}
}
@Override
public void onWrite(final PageRange[] pageRanges, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal,
final WriteResultCallback callback) {
for (int i = 0; i < totalpages; i++) {
if (pageInRange(pageRanges, i)) //保证页码正确
{
PageInfo newPage = new PageInfo.Builder(pageWidth,
pageHeight, i).create();
PdfDocument.Page page =
mPdfDocument.startPage(newPage); //创建新页面
if (cancellationSignal.isCanceled()) { //取消信号
callback.onWriteCancelled();
mPdfDocument.close();
mPdfDocument = null;
return;
}
drawPage(page, i); //将内容绘制到页面Canvas上
mPdfDocument.finishPage(page);
}
}
try {
mPdfDocument.writeTo(new FileOutputStream(
destination.getFileDescriptor()));
} catch (IOException e) {
callback.onWriteFailed(e.toString());
return;
} finally {
mPdfDocument.close();
mPdfDocument = null;
}
callback.onWriteFinished(pageRanges);
}
private boolean pageInRange(PageRange[] pageRanges, int page) {
for (int i = 0; i < pageRanges.length; i++) {
if ((page >= pageRanges[i].getStart()) &&
(page <= pageRanges[i].getEnd()))
return true;
}
return false;
}
//页面绘制(渲染)
private void drawPage(PdfDocument.Page page,int pagenumber) {
Canvas canvas = page.getCanvas();
if(mlist != null){
Paint paint = new Paint();
Bitmap bitmap = mlist.get(pagenumber);
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
// 计算缩放比例
float scale = (float)pageWidth/(float)bitmapWidth;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
canvas.drawBitmap(bitmap,matrix,paint);
}
}
}
package com.axin.wifiprint;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.AuthAlgorithm;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.text.TextUtils;
import java.util.List;
public class WifiConnector {
Handler handler;
WifiManager wifiManager;
//WIFICIPHER_WEP是WEP ,WIFICIPHER_WPA是WPA,WIFICIPHER_NOPASS没有密码
public enum WifiCipherType {
WIFICIPHER_WEP, WIFICIPHER_WPA, WIFICIPHER_NOPASS, WIFICIPHER_INVALID
}
// 构造函数
public WifiConnector(WifiManager wifiManager,Handler handler) {
this.wifiManager = wifiManager;
this.handler = handler;
}
// 提供一个外部接口,传入要连接的无线网
public void connect(String ssid, String password, WifiCipherType type) {
Thread thread = new Thread(new ConnectRunnable(ssid, password, type));
thread.start();
}
// 查看以前是否也配置过这个网络
private WifiConfiguration isExsits(String SSID) {
List<WifiConfiguration> existingConfigs = wifiManager
.getConfiguredNetworks();
for (WifiConfiguration existingConfig : existingConfigs) {
if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
return existingConfig;
}
}
return null;
}
private WifiConfiguration createWifiInfo(String SSID, String Password,
WifiCipherType Type) {
WifiConfiguration config = new WifiConfiguration();
config.allowedAuthAlgorithms.clear();
config.allowedGroupCiphers.clear();
config.allowedKeyManagement.clear();
config.allowedPairwiseCiphers.clear();
config.allowedProtocols.clear();
config.SSID = "\"" + SSID + "\"";
// nopass
if (Type == WifiCipherType.WIFICIPHER_NOPASS) {
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
}
// wep
if (Type == WifiCipherType.WIFICIPHER_WEP) {
if (!TextUtils.isEmpty(Password)) {
if (isHexWepKey(Password)) {
config.wepKeys[0] = Password;
} else {
config.wepKeys[0] = "\"" + Password + "\"";
}
}
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
config.allowedKeyManagement.set(KeyMgmt.NONE);
config.wepTxKeyIndex = 0;
}
// wpa
if (Type == WifiCipherType.WIFICIPHER_WPA) {
config.preSharedKey = "\"" + Password + "\"";
config.hiddenSSID = true;
config.allowedAuthAlgorithms
.set(WifiConfiguration.AuthAlgorithm.OPEN);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
config.allowedPairwiseCiphers
.set(WifiConfiguration.PairwiseCipher.TKIP);
// 此处需要修改否则不能自动重联
// config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
config.allowedPairwiseCiphers
.set(WifiConfiguration.PairwiseCipher.CCMP);
config.status = WifiConfiguration.Status.ENABLED;
}
return config;
}
class ConnectRunnable implements Runnable {
private String ssid;
private String password;
private WifiCipherType type;
public ConnectRunnable(String ssid, String password, WifiCipherType type) {
this.ssid = ssid;
this.password = password;
this.type = type;
}
@Override
public void run() {
try {
// 开启wifi功能需要一段时间(我在手机上测试一般需要1-3秒左右),所以要等到wifi
// 状态变成WIFI_STATE_ENABLED的时候才能执行下面的语句
// while (wifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLING) {
// try {
// // 为了避免程序一直while循环,让它睡个100毫秒检测……
// Thread.sleep(100);
// } catch (InterruptedException ie) {
// }
// }
WifiConfiguration wifiConfig = createWifiInfo(ssid, password,
type);
//
if (wifiConfig == null) {
handler.sendEmptyMessage(90);
return;
}
WifiConfiguration tempConfig = isExsits(ssid);
if (tempConfig != null) {
wifiManager.removeNetwork(tempConfig.networkId);
}
// int netID = wifiManager.addNetwork(wifiConfig);
// boolean enabled = wifiManager.enableNetwork(netID, true);
// sendMsg("enableNetwork status enable=" + enabled);
// boolean connected = wifiManager.reconnect();
// sendMsg("enableNetwork connected=" + connected);
// sendMsg("连接成功!");
handler.sendEmptyMessage(91);
} catch (Exception e) {
// TODO: handle exception
handler.sendEmptyMessage(92);
e.printStackTrace();
}
}
}
private static boolean isHexWepKey(String wepKey) {
final int len = wepKey.length();
// WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?)
if (len != 10 && len != 26 && len != 58) {
return false;
}
return isHex(wepKey);
}
private static boolean isHex(String key) {
for (int i = key.length() - 1; i >= 0; i--) {
final char c = key.charAt(i);
if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a'
&& c <= 'f')) {
return false;
}
}
return true;
}
}
下载demo:
项目地址(现在下载的项目没法设置免费了,有需要的可以留邮箱)