カスタム ドキュメントの印刷

描画アプリやページ レイアウト アプリなど、アプリの場合は 美しい印刷ページを作ることが重要な機能です。この場合、 画像や HTML ドキュメントを印刷できますこの種の用途の印刷出力には、 フォント、テキストの流れ、改ページ、 グラフィック要素を配置できます。

アプリケーションに合わせて完全にカスタマイズされた印刷出力を作成するには、より多くの プログラミングにかける時間を削減できます。アプリケーションに必要な 印刷フレームワークとの通信、プリンタ設定の調整、ページ要素の描画、 複数ページの印刷を管理する

このレッスンでは、印刷マネージャーに接続する方法、プリンタ アダプターを作成する方法、 印刷用のコンテンツを作成します。

アプリケーションが印刷プロセスを直接管理する場合は、メッセージの受信後の最初のステップは、 ユーザーが Android 印刷フレームワークに接続してインスタンスを取得するというものです。 (PrintManager クラスの)このクラスを使用すると、印刷ジョブを初期化できます。 印刷のライフサイクルを開始します。次のコード例は、印刷マネージャーを取得する方法を示しています。 印刷プロセスを開始します。

Kotlin

private fun doPrint() {
    activity?.also { context ->
        // Get a PrintManager instance
        val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager
        // Set job name, which will be displayed in the print queue
        val jobName = "${context.getString(R.string.app_name)} Document"
        // Start a print job, passing in a PrintDocumentAdapter implementation
        // to handle the generation of a print document
        printManager.print(jobName, MyPrintDocumentAdapter(context), null)
    }
}

Java

private void doPrint() {
    // Get a PrintManager instance
    PrintManager printManager = (PrintManager) getActivity()
            .getSystemService(Context.PRINT_SERVICE);

    // Set job name, which will be displayed in the print queue
    String jobName = getActivity().getString(R.string.app_name) + " Document";

    // Start a print job, passing in a PrintDocumentAdapter implementation
    // to handle the generation of a print document
    printManager.print(jobName, new MyPrintDocumentAdapter(getActivity()),
            null); //
}

このコード例は、印刷ジョブに名前を付け、印刷ライフサイクルのステップを処理する PrintDocumentAdapter クラスのインスタンスを設定する方法を示しています。「 プリント アダプター クラスの実装については、次のセクションで説明します。

注: print() の最後のパラメータは、 メソッドは PrintAttributes オブジェクトを受け取ります。このパラメータを使用すると、 印刷フレームワークにヒントを提供し、前の印刷サイクルに基づいて事前設定されたオプションを提供します。 ユーザーエクスペリエンスが向上しますこのパラメータを使用して、Google Chat のメッセージ履歴に (画面の向きを横向きに設定するなど)、印刷するコンテンツに適したオプションです。 指定した向きの写真を印刷すると、その向きの写真が表示されます。

印刷アダプタは、Android 印刷フレームワークとやり取りして、 あります。このプロセスでは、ユーザーは作成する前にプリンタと印刷オプションを選択する必要があります 作成します。これらの選択は、ユーザーの選択に応じた最終出力に影響を与える可能性があります。 出力機能、用紙サイズ、ページの向きなどが異なるプリンタです。 これらの選択を行うと、印刷フレームワークはアダプタに対して、 最終出力に向けて準備します。ユーザーが印刷ボタンをタップすると、フレームワークが は、最終的な印刷ドキュメントを受け取り、出力のために印刷プロバイダに渡します。印刷中 ユーザーが印刷アクションのキャンセルを選択できるため、プリンタ アダプターは キャンセルリクエストに対応します

PrintDocumentAdapter 抽象クラスは、 4 つの主要なコールバック メソッドがあります。これらのメソッドを実装する必要があります。 印刷フレームワークと適切にやり取りするには、次のようにプリンタ アダプタに追加します。

  • onStart() - 次のときに 1 回呼び出されます 始まります。アプリケーションで 1 回限りの準備タスクが 出力するデータのスナップショットの取得などを実行する場合は、ここで実行します。実装 使用する必要はありません。
  • onLayout() - 呼び出されるたびに呼び出されます。 ユーザーが、ページサイズの変更、 これにより、アプリはページのレイアウトを計算できます。 印刷するページを指定します。少なくとも、予想されるページ数を返す必要があります。 をご覧ください。
  • onWrite() - 印刷してレンダリングするために呼び出されます。 変換してファイルとして保存できますこのメソッドは、各エントリの後に 1 回以上呼び出される場合があります。 onLayout() の呼び出し。
  • onFinish() - 最後に 1 回呼び出される 。アプリケーションに 1 回限りの破棄タスクを実行する必要がある場合は、 ここで実行してください。アダプターでこのメソッドを実装する必要はありません。

以降のセクションでは、レイアウト メソッドと write メソッドの実装方法について説明します。これらのメソッドは、 プリント アダプターが機能するうえで非常に重要です。

注: これらのアダプター メソッドは、アプリのメインスレッドで呼び出されます。条件 実装でこれらのメソッドを実行すると、処理にかなりの時間がかかることが 独立したスレッド内で実行されるように実装する必要があります。たとえば、Terraform の状態を レイアウトや印刷ドキュメントの書き込み処理は個別の AsyncTask オブジェクトで行います。

印刷ドキュメント情報の計算

PrintDocumentAdapter クラスの実装内で、 アプリケーションは、作成するドキュメントの種類を指定し、合計サイズを計算できる必要があります。 印刷ジョブのページ数(印刷ページサイズに関する情報)。 onLayout() メソッドの実装は、 アダプタがこれらの計算を行い、出力値に関する期待される出力 PrintDocumentInfo クラス内の印刷ジョブ(ページ数と できます。次のコード例は、PrintDocumentAdapteronLayout() メソッドの基本的な実装を示しています。

Kotlin

override fun onLayout(
        oldAttributes: PrintAttributes?,
        newAttributes: PrintAttributes,
        cancellationSignal: CancellationSignal?,
        callback: LayoutResultCallback,
        extras: Bundle?
) {
    // Create a new PdfDocument with the requested page attributes
    pdfDocument = PrintedPdfDocument(activity, newAttributes)

    // Respond to cancellation request
    if (cancellationSignal?.isCanceled == true) {
        callback.onLayoutCancelled()
        return
    }

    // Compute the expected number of printed pages
    val pages = computePageCount(newAttributes)

    if (pages > 0) {
        // Return print information to print framework
        PrintDocumentInfo.Builder("print_output.pdf")
                .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                .setPageCount(pages)
                .build()
                .also { info ->
                    // Content layout reflow is complete
                    callback.onLayoutFinished(info, true)
                }
    } else {
        // Otherwise report an error to the print framework
        callback.onLayoutFailed("Page count calculation failed.")
    }
}

Java

@Override
public void onLayout(PrintAttributes oldAttributes,
                     PrintAttributes newAttributes,
                     CancellationSignal cancellationSignal,
                     LayoutResultCallback callback,
                     Bundle metadata) {
    // Create a new PdfDocument with the requested page attributes
    pdfDocument = new PrintedPdfDocument(getActivity(), newAttributes);

    // Respond to cancellation request
    if (cancellationSignal.isCanceled() ) {
        callback.onLayoutCancelled();
        return;
    }

    // Compute the expected number of printed pages
    int pages = computePageCount(newAttributes);

    if (pages > 0) {
        // Return print information to print framework
        PrintDocumentInfo info = new PrintDocumentInfo
                .Builder("print_output.pdf")
                .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                .setPageCount(pages)
                .build();
        // Content layout reflow is complete
        callback.onLayoutFinished(info, true);
    } else {
        // Otherwise report an error to the print framework
        callback.onLayoutFailed("Page count calculation failed.");
    }
}

onLayout() メソッドの実行は、 完了、キャンセル、失敗の 3 つになります。 レイアウトを完了できません。適切な関数を呼び出して、これらの結果のいずれかを示す必要があります。 PrintDocumentAdapter.LayoutResultCallback オブジェクトのメソッドを呼び出します。

注: onLayoutFinished() メソッドは、レイアウト コンテンツが実際に変更されたかどうかを示します 前回のリクエスト以降の値を示します。このパラメータを適切に設定すると、印刷フレームワークで onWrite() メソッドを不必要に呼び出す 基本的には、以前に書き込まれた印刷ドキュメントをキャッシュに保存して、パフォーマンスを向上させます。

onLayout() の主な処理は次のとおりです。 指定されたプリンタの属性に基づいて、出力として想定されるページ数を計算する。 この数値を計算する方法は、アプリケーションでページをどのようにレイアウトするかによって大きく異なります。 あります。次のコードサンプルは、ページ数が : 印刷の向きに応じて異なります。

Kotlin

private fun computePageCount(printAttributes: PrintAttributes): Int {
    var itemsPerPage = 4 // default item count for portrait mode

    val pageSize = printAttributes.mediaSize
    if (!pageSize.isPortrait) {
        // Six items per page in landscape orientation
        itemsPerPage = 6
    }

    // Determine number of print items
    val printItemCount: Int = getPrintItemCount()

    return Math.ceil((printItemCount / itemsPerPage.toDouble())).toInt()
}

Java

private int computePageCount(PrintAttributes printAttributes) {
    int itemsPerPage = 4; // default item count for portrait mode

    MediaSize pageSize = printAttributes.getMediaSize();
    if (!pageSize.isPortrait()) {
        // Six items per page in landscape orientation
        itemsPerPage = 6;
    }

    // Determine number of print items
    int printItemCount = getPrintItemCount();

    return (int) Math.ceil(printItemCount / itemsPerPage);
}

印刷ドキュメント ファイルへの書き込み

印刷出力をファイルに書き込むとき、Android 印刷フレームワークによって、アプリの PrintDocumentAdapter クラスの onWrite() メソッドが呼び出されます。このメソッドのパラメータでは、表示するページを 使用される出力ファイルを指定します。次に、このメソッドの実装で、各 Pod に 複数ページの PDF ドキュメント ファイルに変換します。このプロセスが完了すると コールバック オブジェクトの onWriteFinished() メソッドを呼び出します。

注: Android 印刷フレームワークは、セッションごとに onWrite() メソッドを 1 回以上呼び出す onLayout() への呼び出し。このため、 ブール値パラメータを設定することが重要です。 印刷コンテンツのレイアウトが変更されていない場合は、onLayoutFinished() メソッドを false に設定します。 これにより、印刷ドキュメントの不要な書き換えを回避できます。

注: onLayoutFinished() メソッドは、レイアウト コンテンツが実際に変更されたかどうかを示します 前回のリクエスト以降の値を示します。このパラメータを適切に設定すると、印刷フレームワークで onLayout() メソッドを不必要に呼び出す 基本的には、以前に書き込まれた印刷ドキュメントをキャッシュに保存して、パフォーマンスを向上させます。

このプロセスの基本的な仕組みを示す次のサンプルは、PrintedPdfDocument クラスを使用して PDF ファイルを作成しています。

Kotlin

override fun onWrite(
        pageRanges: Array<out PageRange>,
        destination: ParcelFileDescriptor,
        cancellationSignal: CancellationSignal?,
        callback: WriteResultCallback
) {
    // Iterate over each page of the document,
    // check if it's in the output range.
    for (i in 0 until totalPages) {
        // Check to see if this page is in the output range.
        if (containsPage(pageRanges, i)) {
            // If so, add it to writtenPagesArray. writtenPagesArray.size()
            // is used to compute the next output page index.
            writtenPagesArray.append(writtenPagesArray.size(), i)
            pdfDocument?.startPage(i)?.also { page ->

                // check for cancellation
                if (cancellationSignal?.isCanceled == true) {
                    callback.onWriteCancelled()
                    pdfDocument?.close()
                    pdfDocument = null
                    return
                }

                // Draw page content for printing
                drawPage(page)

                // Rendering is complete, so page can be finalized.
                pdfDocument?.finishPage(page)
            }
        }
    }

    // Write PDF document to file
    try {
        pdfDocument?.writeTo(FileOutputStream(destination.fileDescriptor))
    } catch (e: IOException) {
        callback.onWriteFailed(e.toString())
        return
    } finally {
        pdfDocument?.close()
        pdfDocument = null
    }
    val writtenPages = computeWrittenPages()
    // Signal the print framework the document is complete
    callback.onWriteFinished(writtenPages)

    ...
}

Java

@Override
public void onWrite(final PageRange[] pageRanges,
                    final ParcelFileDescriptor destination,
                    final CancellationSignal cancellationSignal,
                    final WriteResultCallback callback) {
    // Iterate over each page of the document,
    // check if it's in the output range.
    for (int i = 0; i < totalPages; i++) {
        // Check to see if this page is in the output range.
        if (containsPage(pageRanges, i)) {
            // If so, add it to writtenPagesArray. writtenPagesArray.size()
            // is used to compute the next output page index.
            writtenPagesArray.append(writtenPagesArray.size(), i);
            PdfDocument.Page page = pdfDocument.startPage(i);

            // check for cancellation
            if (cancellationSignal.isCanceled()) {
                callback.onWriteCancelled();
                pdfDocument.close();
                pdfDocument = null;
                return;
            }

            // Draw page content for printing
            drawPage(page);

            // Rendering is complete, so page can be finalized.
            pdfDocument.finishPage(page);
        }
    }

    // Write PDF document to file
    try {
        pdfDocument.writeTo(new FileOutputStream(
                destination.getFileDescriptor()));
    } catch (IOException e) {
        callback.onWriteFailed(e.toString());
        return;
    } finally {
        pdfDocument.close();
        pdfDocument = null;
    }
    PageRange[] writtenPages = computeWrittenPages();
    // Signal the print framework the document is complete
    callback.onWriteFinished(writtenPages);

    ...
}

このサンプルでは、PDF ページ コンテンツのレンダリングを drawPage() に委任します。 メソッドを使用します。これについては次のセクションで説明します。

レイアウトと同様に、onWrite() の実行 メソッドの結果には、完了、キャンセル、失敗の 3 つがあります。 コンテンツを書き込めません。次の呼び出しによって、これらの結果のいずれかを指定する必要があります。 PrintDocumentAdapter.WriteResultCallback オブジェクトの適切なメソッドを呼び出します。

注: 印刷ドキュメントのレンダリングは、リソースを大量に消費することがあります。イン アプリのメイン ユーザー インターフェース スレッドがブロックされないようにするために、 ページのレンダリングと書き込みの処理を別のスレッドで実行すると、 AsyncTask 内。 非同期タスクなどの実行スレッドの操作について詳しくは、 プロセス とスレッドをご覧ください。

PDF ページ コンテンツの描画

アプリケーションで印刷するときは、アプリケーションで PDF ドキュメントを生成して、 印刷用の Android Print Frameworkこの作業には任意の PDF 生成ライブラリを使用できます。 あります。このレッスンでは、PrintedPdfDocument クラスの使用方法について説明します。 コンテンツから PDF ページを生成できます

PrintedPdfDocument クラスは Canvas を使用する アクティビティ レイアウト上での描画と同様に、PDF ページに要素を描画するオブジェクトです。描画できる Canvas 描画メソッドを使用して、印刷ページ上に要素を配置します。次の サンプルコードは、PDF ドキュメント ページにシンプルな要素を描画する方法を示しています。 メソッド:

Kotlin

private fun drawPage(page: PdfDocument.Page) {
    page.canvas.apply {

        // units are in points (1/72 of an inch)
        val titleBaseLine = 72f
        val leftMargin = 54f

        val paint = Paint()
        paint.color = Color.BLACK
        paint.textSize = 36f
        drawText("Test Title", leftMargin, titleBaseLine, paint)

        paint.textSize = 11f
        drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint)

        paint.color = Color.BLUE
        drawRect(100f, 100f, 172f, 172f, paint)
    }
}

Java

private void drawPage(PdfDocument.Page page) {
    Canvas canvas = page.getCanvas();

    // units are in points (1/72 of an inch)
    int titleBaseLine = 72;
    int leftMargin = 54;

    Paint paint = new Paint();
    paint.setColor(Color.BLACK);
    paint.setTextSize(36);
    canvas.drawText("Test Title", leftMargin, titleBaseLine, paint);

    paint.setTextSize(11);
    canvas.drawText("Test paragraph", leftMargin, titleBaseLine + 25, paint);

    paint.setColor(Color.BLUE);
    canvas.drawRect(100, 100, 172, 172, paint);
}

Canvas を使用して PDF ページに描画する場合、要素は 1/72 インチになります。サイズを指定する際は、この測定単位を必ず使用してください。 最適化されます。描画された要素の配置については、座標系は 0,0 から始まります。 をクリックします。

ヒント: Canvas オブジェクトを使用すると、print ジョブを といった要素が含まれていると、多くのプリンタでは、 1 枚しかありません。印刷する場合は、ページの印刷できない端も考慮してください。 このクラスを使用して印刷ドキュメントを作成します