Android excel表打印

最近做了一個項目使用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:項目地址(現在下載的項目沒法設置免費了,有需要的可以留郵箱)





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章