Androidアプリ開発

PDF作成をPdfDocumentで実装する

この記事は約21分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


 AndroidアプリでPDFを作成する

下記の動画は、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 出力先にあたる FileOutputStreamPdfDocument に引き渡して、書き込みを行います。
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行として出力します。

測定結果( motionList )の内容

◎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を習得するのに最適かと、まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪

スポンサーリンク
msakiをフォローする
スポンサーリンク

コメント欄

タイトルとURLをコピーしました