今天來講個"如何在Android"上撰寫PDF

先說好這篇不是教你去下載什麼應用程式喔XD

這篇是真的教你如何寫一個APP讓使用者撰寫文件並輸出成PDF文檔

當然!也教學如何將PDF撰寫為中文(・ω・)b

 

今天遇到的這個項目,我是不知道其他公司有沒有這個需求啦(´・_・`)

我所知道大部分的APP大多輸出為CSV的比較多

輸出成PDF的我之前是很少聽過..(゚д゚;)

所以當初公司要求在APP輸出成PDF的時候,著實地把我嚇了個跳QQ

當時也是一度懷疑真的有這項功能?(╭ರ_⊙)

廢話..我們偉大的Google所營運的安著系統怎麼可能沒法撰寫!?

不過是用第三方庫寫的...(小聲

這個第三方庫叫做iText,下面會有完整介紹

那麼以下是今天要完成的功能

PDF示範

以及Github

https://github.com/thumbb13555/PDFMakerExample/tree/master

OK,開始吧!


1.功能需求描述以及載入第三方庫

1-1 功能需求描述

首先先來闡述一下今天要完成的功能

  1. 撰寫PDF檔案
  2. 讓檔案可輸出為中文
  3. 可將檔案以Email或其他方式傳送出去
  4. 在傳送的同時將此檔案存於手機的"內部儲存空間"

 

1-2 載入第三方庫

這次的範例將載入的第三方庫為iText,這個開源庫本身是由iText這家公司運作的

OS:一個開源庫能寫到變成一家公司(集團公司)....強到不可思議...(゚д゚;)

這邊放上他的官網

->https://itextpdf.com/en

目前iText系列有iText5跟iText7,我們專案用iText5就好

此外iText官網也有全套的示範集

我個人建議在開發PDF時可先參照一下他們官網的範例進行開發

範例集->https://itextpdf.com/en/resources/examples/itext-5-legacy

OK,來載入吧

首先是我是在這邊取得他的庫

->https://mvnrepository.com/artifact/com.itextpdf/itextpdf

首先在Android的build.gradle的dependencies內加入我們需要的庫

截圖 2020-02-22 下午10.35.54

將庫加入於此

implementation 'com.itextpdf:itextpdf:5.5.13.1'

 

截圖 2020-02-22 下午10.36.35

按下Sync now即完成

1-3 在AndroidManifest.xml加入權限

我們要使用資料儲存,必須要在Manifest中加入儲存權限

截圖 2020-02-22 下午11.05.08

 

截圖 2020-02-22 下午11.05.36

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

然後先來到程式中,詢問使用者是否許可使用

請在onCreate中加入詢問

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000);
        }
       
    }//onCreate

這樣在開啟APP時,系統就會發起詢問囉!

Screenshot_20200222-232231_Package installer

2.畫介面以及建立使用者功能

2-1 畫介面

介面的部分,樹狀圖長這樣

截圖 2020-02-22 下午10.46.40

guideline是分割元件,不用太care

整體的XML在這邊

->https://github.com/thumbb13555/PDFMakerExample/blob/master/app/src/main/res/layout/activity_main.xml

以及介面完整樣式

Screenshot_20200222-225217_PDFMakerExample

2-2連結使用者功能

接著撰寫連接及要用的功能

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000);
        }
        EditText edTitle = findViewById(R.id.editText_title);
        EditText edInput = findViewById(R.id.editText_input);
        edInput.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE);
        edInput.setGravity(Gravity.TOP);
        edInput.setSingleLine(false);
        edInput.setHorizontallyScrolling(false);

        Button buttonEx = findViewById(R.id.button_Export);
        Button buttonClear = findViewById(R.id.buttonClear);

        buttonClear.setOnClickListener((v -> {
            edInput.setText("");
            edTitle.setText("");
        }));

        buttonEx.setOnClickListener((v) -> {
            String title = edTitle.getText().toString();
            String input = edInput.getText().toString();
            PDFExport(title, input);

        });

    }//onCreate

紅色的部分是設置界面連結

粗體字是設定讓EditText處為一個可以多行換行的模式

藍色部分則是我們設置PDF的部分

那個放在下一段講(´・з・)

此外也補充一下,title跟input分別是標題名稱及輸入的內容~

3.開始撰寫PDF!

首先我在整個結構內有兩個副程式

除了PDFExport之外還有一個output的副程式

截圖 2020-02-22 下午11.43.12

在我的結構中PDFExport是用來寫PDF內容的

output的部分則是將文件輸出的部分

 

OK再來聊聊關於這個製作PDF

製作PDF這回事他也是一個"耗時工作"

所謂的"耗時工作"就像是網路連線,或者設備溝通等等都算是"耗時工作"

這種耗時工作不去處理的話,就會造成UI卡頓,影響使用者操作

我的方法是使用new Thread處理

此外就是,整個PDF製作過程是必須寫在try-catch內

於是,我的程式長這樣

private void PDFExport(String title, String input) {
        ProgressDialog dialog = ProgressDialog.show(this, "處理中", "請稍候", true);
        new Thread(() -> {
            try {
                .
                .
                .

                runOnUiThread(() -> {
                    dialog.dismiss();
                    output(fileName);
                });
            } catch (Exception e) {
                Log.d(TAG, "PDFExport: " + e);
            }
        }).start();
    }//PDFExport

 

再來是內容

再開始撰寫之前要先處理中文化的問題

在中文化這個問題上去網路上查的話

基本上第一...不用指望英文資料了,外國人不用寫中文的(;・∀・)

第二,網路上的確有很多資料,但是太過複雜,不好處理

上述的方法就是載入一個叫做iText-asia的庫

iText7的話到2020年初都還有更新,可以去看看

->https://mvnrepository.com/artifact/com.itextpdf/font-asian

註:它一直都還在更新...厲害啊

 

而今天要講的方法我認為應該最簡單了吧(`・ω・´)+

就是在編輯器內加上一個assets的資源資料夾,將檔案放在裡面後再由程式呼叫

具體操作如下

 

新增assets資料夾,首先先將Project內的資料夾由Android模式改為project模式

截圖 2020-02-23 上午12.56.10

截圖 2020-02-23 上午12.56.25

接著將目錄來到"main"這一層

截圖 2020-02-23 上午12.59.19

在main上點擊右鍵->New->Directory

截圖 2020-02-23 上午12.59.39

然後再對話框輸入assets就完成了!

截圖 2020-02-23 上午1.01.15截圖 2020-02-23 上午1.02.03

註:要確認有出現黃色符號才是有成功喔!

關於為何會有assets資料夾可參考這位大大

->https://bravohsiao.pixnet.net/blog/post/244427743

他有寫簡單的詳述!謝謝您!(っ・∀・)っ

再來我們將中文字型的字型檔(.ttf)給他輸入進去

關於ttf的檔案可以在電腦內部找到

我這邊...可以直接去我的git載就好XD

我是使用標楷體喔!

https://github.com/thumbb13555/PDFMakerExample/blob/master/app/src/main/assets/kaiu.ttf

截圖 2020-02-23 上午1.09.44

按下Download就可以載到了

然後在程式中呼叫這行

 BaseFont chinese = BaseFont.createFont("assets/kaiu.ttf"
                        , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);//設置中文字型

紅色部分一定要打對字,不然就算打錯字編輯器也不會幫你除錯喔!

最後程式長這樣

private void PDFExport(String title, String input) {
        ProgressDialog dialog = ProgressDialog.show(this, "處理中", "請稍候", true);
        new Thread(() -> {
            try {
                String fileName = "/" +title + ".pdf";//決定檔案名稱
                String mFilePath = Environment.getExternalStorageDirectory() + fileName;//決定路徑
                Document document = new Document(PageSize.A4, 40, 40, 40, 40);//設置紙張大小
                PdfWriter.getInstance(document, new FileOutputStream(mFilePath));

                //這裏開始寫內容
                document.open();
                LineSeparator line = new LineSeparator(2f, 300, BaseColor.BLACK, Element.ALIGN_CENTER, 20f);//設定一條黑粗橫線
                BaseFont chinese = BaseFont.createFont("assets/kaiu.ttf"
                        , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);//設置字體
                Font titleFont = new Font(chinese, 32);//這是大~標題的
                Font inputFont = new Font(chinese, 16);//這是小~內容的
                document.add(new Paragraph(new Phrase(20f
                        , title, titleFont)));//新增抬頭的字
                document.add(new Paragraph(" "));//空白行
                document.add(new Paragraph(" "));//空白行
                document.add(line);//畫一條線
                document.add(new Paragraph(" "));//空白行
                document.add(new Paragraph(new Phrase(20f
                        , input, inputFont)));//寫內容
                document.close();
                //這裏結束寫內容

                runOnUiThread(() -> {
                    dialog.dismiss();
                    output(fileName);
                });
            } catch (Exception e) {
                Log.d(TAG, "PDFExport: " + e);
            }


        }).start();
    }//PDFExport

最後是輸出了!輸出的部分就是藍字部分,我另闢一個副程式處理

截圖 2020-02-23 上午1.20.13

這邊副程式內容是這樣

private void output(String fileName) {
        //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        builder.detectFileUriExposure();
        //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
        File filelocation = new File(Environment.
                getExternalStorageDirectory(), fileName);
        Uri path = Uri.fromFile(filelocation);

        Intent fileIntent = new Intent(Intent.ACTION_SEND);
        fileIntent.setType("text/plain");
        fileIntent.putExtra(Intent.EXTRA_SUBJECT, "我的資料");
        fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        fileIntent.putExtra(Intent.EXTRA_STREAM, path);
        startActivity(Intent.createChooser(fileIntent, "Send Mail"));
    }

這裡就是輸出的完整配置了,供參考(*゚ー゚)ゞ

 

好拉,講到這裡應該是可以正常執行了

大家來試一下吧( ・ω・)ノ

今天的範例是簡單的,程式不多,幾百行而已

我把全部PO出來吧

public class MainActivity extends AppCompatActivity {
    public static final String TAG = MainActivity.class.getSimpleName() + "My";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1000);
        }
        EditText edTitle = findViewById(R.id.editText_title);
        EditText edInput = findViewById(R.id.editText_input);
        edInput.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE);
        edInput.setGravity(Gravity.TOP);
        edInput.setSingleLine(false);
        edInput.setHorizontallyScrolling(false);

        Button buttonEx = findViewById(R.id.button_Export);
        Button buttonClear = findViewById(R.id.buttonClear);

        buttonClear.setOnClickListener((v -> {
            edInput.setText("");
            edTitle.setText("");
        }));

        buttonEx.setOnClickListener((v) -> {
            String title = edTitle.getText().toString();
            String input = edInput.getText().toString();
            PDFExport(title, input);

        });

    }//onCreate
    /**@param title 設置文件名稱與抬頭
     * @param input 設置文件內容*/

    private void PDFExport(String title, String input) {
        ProgressDialog dialog = ProgressDialog.show(this, "處理中", "請稍候", true);
        new Thread(() -> {
            try {
                String fileName = "/" +title + ".pdf";
                String mFilePath = Environment.getExternalStorageDirectory() + fileName;
                Document document = new Document(PageSize.A4, 40, 40, 40, 40);
                PdfWriter.getInstance(document, new FileOutputStream(mFilePath));

                //這裏開始寫內容
                document.open();
                LineSeparator line = new LineSeparator(2f, 300, BaseColor.BLACK, Element.ALIGN_CENTER, 20f);
                BaseFont chinese = BaseFont.createFont("assets/kaiu.ttf"
                        , BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
                Font titleFont = new Font(chinese, 32);//這是大~標題的
                Font inputFont = new Font(chinese, 16);
                document.add(new Paragraph(new Phrase(20f
                        , title, titleFont)));
                document.add(new Paragraph(" "));
                document.add(new Paragraph(" "));
                document.add(line);
                document.add(new Paragraph(" "));
                document.add(new Paragraph(new Phrase(20f
                        , input, inputFont)));
                document.close();
                //這裏結束寫內容

                runOnUiThread(() -> {
                    dialog.dismiss();
                    output(fileName);
                });
            } catch (Exception e) {
                Log.d(TAG, "PDFExport: " + e);
            }


        }).start();
    }//PDFExport

    private void output(String fileName) {
        //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        builder.detectFileUriExposure();
        //->遇上exposed beyond app through ClipData.Item.getUri() 錯誤時在onCreate加上這行
        File filelocation = new File(Environment.
                getExternalStorageDirectory(), fileName);
        Uri path = Uri.fromFile(filelocation);

        Intent fileIntent = new Intent(Intent.ACTION_SEND);
        fileIntent.setType("text/plain");
        fileIntent.putExtra(Intent.EXTRA_SUBJECT, "我的資料");
        fileIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        fileIntent.putExtra(Intent.EXTRA_STREAM, path);
        startActivity(Intent.createChooser(fileIntent, "Send Mail"));
    }//output
}

 


這樣今天的教學文就到這邊了

PDF功能如我一開始說的,大概又是沒什麼人要看的功能吧(笑)

不過我的網誌就是秉承著"寫別人不太寫的東西"的精神,去撰寫這一篇文

日後我還打算出畫表格的,不過那個程式複雜太多了,可能要緩緩吧..

最後..喜歡我的網誌的話請將我的網誌加入書籤喔~

能幫上你是我最大的快樂(•ө•)♡

我的網誌每週更新,每週都有不一樣的介紹,歡迎各位每週來刷刷看,亦可以留言許願認我知道你想看什麼XD

感謝你的點閱,下次見!

59349318

arrow
arrow

    碼農日常 發表在 痞客邦 留言(2) 人氣()