この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、記事の最後で紹介します)
下記の動画は、GPSタイム計測アプリ(Laps)でタイム計測結果のPDFを作成して、作成したPDFを PDFビューワ(Acrobat Reader)で表示している動画です。
PDF作成を PdfDocument で実装する
Android API の android.graphics.pdf というパッケージには、PDFファイルを作成する PdfDocument クラスが用意されています。今回は、PdfDocument クラスを使用した PDF作成について、解説したいと思います。
◎PDF作成クラス
PDF を作成する基本的な流れとして、インスタンス化した PdfDocument から Page 作成を開始、作成した Page から、Canvas を取得します。
Canvas に PDF に出力する内容を描画して、Page 作成を終了します。
注意点としては、改ページの単位で、Page 作成の開始と終了する必要があります。
全ページ分の Canvas への描画が完了した時点で、PDF 出力先にあたる FileOutputStream を PdfDocument に引き渡して、書き込みを行います。
PDF 出力後に PdfDocument を閉じます。
public class LapsPdfDocument {
private Context context;
private final Bitmap bitmap;
private final int[] bitmapSize = { 0, 0 };
private final String DATE;
private final float Y3 = 100;
private final float PITCH = 12;
// コンストラクタ //
public LapsPdfDocument(Context newContext, int image) {
if (context == null) {
context = newContext;
} else if (!context.equals(newContext)) {
context = newContext;
}
bitmap = BitmapFactory.decodeResource(context.getResources(), image);
bitmapSize[0] = bitmap.getWidth();
bitmapSize[1] = bitmap.getHeight();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault());
DATE = String.format("DATE : %s", simpleDateFormat.format(System.currentTimeMillis()));
}
// PDF作成 //
public void createPdfDocument(List<String> motionList, Uri uri) {
PdfDocument pdfDocument = new PdfDocument();
PdfDocument.Page page;
Canvas canvas;
RectF rectF;
Matrix matrix = new Matrix();
Paint paint1 = new Paint();
Paint paint2 = new Paint();
Paint paint3 = new Paint();
Paint paint4 = new Paint();
int pos = 0;
int pages = 0;
// [1] 00:00:00.000
paint1.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC));
paint1.setColor(context.getColor(R.color.theme500));
paint1.setTextSize(20);
paint1.setTextAlign(Paint.Align.LEFT);
// [2] Laps: HH:MM 距離: 000.0m
// [3] 最低: 0.0Km/h 最高: 0.0Km/h 平均: 0.0Km/h
paint2.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
paint2.setColor(context.getColor(R.color.grey800));
paint2.setTextSize(10);
paint2.setTextAlign(Paint.Align.LEFT);
// DATE : yyyy/MM/dd
paint3.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
paint3.setColor(context.getColor(R.color.grey800));
paint3.setTextSize(10);
paint3.setTextAlign(Paint.Align.RIGHT);
// PANEL
float LX = 15;
float RX = 580;
LinearGradient linearGradient = new LinearGradient(LX, 0, RX, 0,Color.argb(0,0,0,0), context.getColor(R.color.grey002), Shader.TileMode.CLAMP);
paint4.setShader(linearGradient);
// A4横(72dpi)
int a4X = 595;
// A4縦(72dpi)
int a4Y = 842;
page = pdfDocument.startPage(new PdfDocument.PageInfo.Builder(a4X, a4Y, pages).create());
canvas = page.getCanvas();
// 背景
matrix.postScale(0.381f,0.381f);
canvas.drawBitmap(Bitmap.createBitmap(bitmap, 0, 0, bitmapSize[0], bitmapSize[1], matrix, true), 0, 0, null);
// DATE : yyyy/MM/dd
float y2 = 20;
canvas.drawText(DATE, RX, y2, paint3);
// 計測結果
List<String> dataList = new ArrayList<>(motionList);
Collections.reverse(dataList);
for (String data : dataList) {
String[] items = data.split("\n");
if (items.length > 1) {
int detail = items.length > 2 ? (items.length - 1) / 2 + 2: 2;
String[] heads = new String[] {
items[0].substring(0, Math.max(items[0].indexOf("Laps"), 0)), // [0] 00:00:00.000
items[0].substring(Math.max(items[0].indexOf("Laps"), 0)) // [1] Laps: HH:MM 距離: 000.0m
};
// 行数
int LIMIT = 60;
if (pos + detail > LIMIT) {
pdfDocument.finishPage(page);
// 改ページ
pages++;
page = pdfDocument.startPage(new PdfDocument.PageInfo.Builder(a4X, a4Y, pages).create());
canvas = page.getCanvas();
canvas.drawBitmap(Bitmap.createBitmap(bitmap, 0, 0, bitmapSize[0], bitmapSize[1], matrix, true), 1 , 1, null);
canvas.drawText(DATE, RX, y2, paint3);
pos = 0;
}
// PANEL
rectF = new RectF(LX, Y3 + (pos * PITCH) + 6, RX, Y3 + ((pos + detail) * PITCH) + 3);
canvas.drawRoundRect(rectF, 2, 2, paint4);
// [0] 00:00:00.000
drawText(heads[0], LX + 5, pos, paint1, canvas, 2);
pos++;
// [1] Laps: HH:MM 距離: 0.0m
float CX = 120;
drawText(heads[1], CX, pos, paint2, canvas, 1);
float DX = 330;
if (items.length > 2) {
pos++;
for (int i = 1; i < items.length; i = i + 2) {
// [1] [ 区間1 ] タイム: 00:00.000 距離: 0.0m
drawText(items[i], CX, pos, paint2, canvas, 1);
// [2] 最低: 0.0Km/h 最高: 0.0Km/h 平均: 0.0Km/h
drawText(items[i + 1], DX, pos, paint2, canvas, 1);
pos++;
}
} else {
// [2] 最低: 0.0Km/h 最高: 0.0Km/h 平均: 0.0Km/h
drawText(items[1], DX, pos, paint2, canvas, 1);
pos++;
}
}
}
pdfDocument.finishPage(page);
try {
FileOutputStream fileOutputStream = (FileOutputStream) context.getContentResolver().openOutputStream(uri, "wt");
pdfDocument.writeTo(fileOutputStream);
Toast toast = Toast.makeText(context, context.getString(R.string.export_pdf), Toast.LENGTH_SHORT);
toast.show();
} catch (IOException e) {
e.printStackTrace();
}
pdfDocument.close();
}
// テキスト出力 //
private void drawText(String text, float x, int pos, Paint paint, Canvas canvas, float size) {
canvas.drawText(text, x, Y3 + ((pos + size) * PITCH), paint);
}
}
今回、出力する内容(測定結果)を String のリストを引数として、背景付きの A4サイズの PDF を作成します。
アプリで作成する PDF が1種類のため、コンストラクタで、背景と作成日付を設定、String のリストと PDF の出力先 Uri を引数としたPDF作成( createPdfDocument )を 1つのクラスとして実装します。
◎出力する内容( String型のリスト)
測定結果( motionList )は改行コードで区切りられたマルチレコードの構成で、PDF作成クラスでは、1レコード目のヘッダを2つに分解、2レコード目以降の明細は、2レコードを1行として出力します。
明細が 1レコードしかない場合は、ヘッダと合わせて 1行として出力します。
◎PDF作成クラスを使用する
メニューの「PDF出力」をタップで、ストレージアクセスフレームワーク( SAF )を使用して、PDFファイルの出力先( Uri )を取得します。
Context とリソースに登録した背景( R.drawable.background )を引数として、インスタンス化した PDF作成クラスに、
取得した Uri と測定結果( motionList )を使って、PDF作成( createPdfDocument )を実行します。
:
private final SimpleDateFormat simpleDateFormat2 = new SimpleDateFormat("yyMMddHHmm", Locale.getDefault());
private Handler handler;
private Runnable runnable;
private Context context;
private ArrayList<String> motionList = new ArrayList<>();
private ActionMenuView actionMenuView;
private int itemId;
:
// ActivityResultLauncher //
private final ActivityResultLauncher<Intent> activityResultLauncher= registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
if (result.getData() != null) {
switch (itemId) {
:
case 11:
// PDF出力
LapsPdfDocument lapsPdfDocument = new LapsPdfDocument(context, R.drawable.background);
new Thread(() -> handler.post(() -> lapsPdfDocument.createPdfDocument(motionList, result.getData().getData()))).start();
break;
default:
}
}
}
});
// onCreate //
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = getApplicationContext();
:
// メニュー
actionMenuView = findViewById(R.id.menu);
:
actionMenuView.getMenu().add(Menu.NONE, 11, Menu.NONE,context.getString(R.string.menu_pdf));
:
}
// onResume //
@Override
protected void onResume() {
super.onResume();
:
// メニュー
actionMenuView.setOnMenuItemClickListener(menuItem -> {
itemId = menuItem.getItemId();
Intent intentMenu;
switch (itemId) {
:
case 11: // PDF出力
if (motionList.size() > 0) {
intentMenu = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intentMenu.addCategory(Intent.CATEGORY_OPENABLE);
intentMenu.setType("application/pdf");
intentMenu.putExtra(Intent.EXTRA_TITLE, String.format("%s%s.pdf", context.getString(R.string.app_name), simpleDateFormat2.format(System.currentTimeMillis())));
intentMenu.putExtra(DocumentsContract.EXTRA_INITIAL_URI, String.format("%s.pdf", context.getString(R.string.app_name)));
activityResultLauncher.launch(intentMenu);
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
} else
toastMessage(R.string.pdf_error);
break;
:
}
return false;
});
:
ストレージアクセスフレームワーク( SAF )について
使用する string や color は、string.xml または、color.xml に指定します。
<resources>
<string name="app_name">Laps</string>
<string name="pdf_error">PDF出力するデータがありません</string>
<string name="menu_pdf">PDF出力</string>
<string name="export_pdf">PDFファイルを出力しました</string>
:
</resources>
<resources>
<color name="theme500">#FF5722</color>
<color name="grey800">#404040</color>
<color name="grey002">#20FEFEFE</color>
:
</resources>
PDF作成をPdfDocumentで実装している Androidアプリです。
今回は、ここまでです。
参考 : Android Developers > Docs > Reference
誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にあるコメントか、
こちらから、お願いいたします♪
サイズが大きいPDFファイルの中身はイメージです。
OCRでテキスト化しても、どうしても誤認識があるので、最終的に人のチェックが必要です。
手書きのホワイトボードを撮影した画像からのテキスト化(文字起こし)も可能な代行サービスを使えば、一発解決ですね♪
ポチッとして頂けると、
次のコンテンツを作成する励みになります♪
これからAndroidのアプリ開発やJavaでの開発を始めたい方へ
アプリケーション開発経験がない方や、アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、Android のアプリ開発ができるようになるには、かなりの時間がかかります。
オンラインスクールでの習得を、強くおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、まずは無料相談から♪
未経験者からプログラマーを目指すのに最適かと、まずは無料カウンセリングから♪
カリキュラムとサポートがしっかりしています、お得なキャンペーンとかいろいろやっています♪
ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、まずは無料オンライン相談から♪
参考になったら、💛をポッチとしてね♪
コメント欄