今天來講個"如何在Android"上撰寫PDF
先說好這篇不是教你去下載什麼應用程式喔XD
這篇是真的教你如何寫一個APP讓使用者撰寫文件並輸出成PDF文檔
當然!也教學如何將PDF撰寫為中文(・ω・)b
今天遇到的這個項目,我是不知道其他公司有沒有這個需求啦(´・_・`)
我所知道大部分的APP大多輸出為CSV的比較多
輸出成PDF的我之前是很少聽過..(゚д゚;)
所以當初公司要求在APP輸出成PDF的時候,著實地把我嚇了個跳QQ
當時也是一度懷疑真的有這項功能?(╭ರ_⊙)
廢話..我們偉大的Google所營運的安著系統怎麼可能沒法撰寫!?
不過是用第三方庫寫的...(小聲
這個第三方庫叫做iText,下面會有完整介紹
那麼以下是今天要完成的功能
以及Github
https://github.com/thumbb13555/PDFMakerExample/tree/master
OK,開始吧!
1.功能需求描述以及載入第三方庫
1-1 功能需求描述
首先先來闡述一下今天要完成的功能
- 撰寫PDF檔案
- 讓檔案可輸出為中文
- 可將檔案以Email或其他方式傳送出去
- 在傳送的同時將此檔案存於手機的"內部儲存空間"
1-2 載入第三方庫
這次的範例將載入的第三方庫為iText,這個開源庫本身是由iText這家公司運作的
OS:一個開源庫能寫到變成一家公司(集團公司)....強到不可思議...(゚д゚;)
這邊放上他的官網
目前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內加入我們需要的庫
將庫加入於此
implementation 'com.itextpdf:itextpdf:5.5.13.1'
按下Sync now即完成
1-3 在AndroidManifest.xml加入權限
我們要使用資料儲存,必須要在Manifest中加入儲存權限
<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時,系統就會發起詢問囉!
2.畫介面以及建立使用者功能
2-1 畫介面
介面的部分,樹狀圖長這樣
guideline是分割元件,不用太care
整體的XML在這邊
->https://github.com/thumbb13555/PDFMakerExample/blob/master/app/src/main/res/layout/activity_main.xml
以及介面完整樣式
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的副程式
在我的結構中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模式
接著將目錄來到"main"這一層
在main上點擊右鍵->New->Directory
然後再對話框輸入assets就完成了!
註:要確認有出現黃色符號才是有成功喔!
關於為何會有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
按下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
最後是輸出了!輸出的部分就是藍字部分,我另闢一個副程式處理
這邊副程式內容是這樣
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
感謝你的點閱,下次見!
留言列表