การพิมพ์เอกสารที่กำหนดเอง

สำหรับบางแอปพลิเคชัน เช่น แอปวาดภาพ แอปเลย์เอาต์หน้า และแอปอื่นๆ ที่มุ่งเน้น การแสดงผลกราฟิก การสร้างหน้าที่พิมพ์ออกมาสวยงามเป็นฟีเจอร์สำคัญ ในกรณีนี้ ยังไม่เพียงพอ เพื่อพิมพ์รูปภาพหรือเอกสาร 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 คุณสามารถใช้พารามิเตอร์นี้เพื่อ ให้คำแนะนำเกี่ยวกับกรอบการพิมพ์และตัวเลือกที่กำหนดค่าไว้ล่วงหน้าตามรอบการพิมพ์ก่อนหน้า จึงทำให้ผู้ใช้ได้รับประสบการณ์ที่ดีขึ้น คุณยังสามารถใช้พารามิเตอร์นี้เพื่อตั้งค่าตัวเลือก เหมาะสมกับเนื้อหาที่พิมพ์มากขึ้น เช่น ตั้งค่าการวางแนวเป็นแนวนอน เมื่อพิมพ์รูปภาพที่อยู่ในแนวนั้น

อะแดปเตอร์การพิมพ์จะโต้ตอบกับเฟรมเวิร์กการพิมพ์ของ Android และจัดการขั้นตอนต่างๆ กระบวนการพิมพ์ กระบวนการนี้กำหนดให้ผู้ใช้เลือกเครื่องพิมพ์และตัวเลือกการพิมพ์ก่อนสร้าง เอกสารสำหรับพิมพ์ การเลือกเหล่านี้สามารถส่งผลต่อผลลัพธ์สุดท้ายได้ตามที่ผู้ใช้เลือก เครื่องพิมพ์ที่มีความสามารถในการเอาต์พุตต่างกัน ขนาดหน้าที่ต่างกัน หรือการวางแนวหน้าที่แตกต่างกัน ระหว่างที่เราดำเนินการเช่นนี้ กรอบการพิมพ์จะขอให้อะแดปเตอร์จัดวางและสร้าง พิมพ์เอกสารเพื่อเตรียมแสดงผลขั้นสุดท้าย เมื่อผู้ใช้แตะปุ่มพิมพ์ เฟรมเวิร์ก นำเอกสารฉบับพิมพ์ขั้นสุดท้ายและส่งไปยังผู้ให้บริการพิมพ์เพื่อส่งออก ระหว่างการพิมพ์ ผู้ใช้สามารถเลือกที่จะยกเลิกการดำเนินการพิมพ์ ดังนั้นอะแดปเตอร์การพิมพ์ของคุณต้องทราบว่าคุณต้องการพิมพ์ และตอบสนองต่อคำขอยกเลิก

Abstract Class ชื่อ PrintDocumentAdapter ออกแบบมาเพื่อจัดการ วงจรการพิมพ์ซึ่งมี 4 วิธีหลักในการติดต่อกลับ คุณต้องทำตามวิธีการเหล่านี้ ในอะแดปเตอร์การพิมพ์เพื่อให้โต้ตอบกับเฟรมเวิร์กการพิมพ์ได้อย่างเหมาะสม

  • onStart() - โทร 1 ครั้งใน ของกระบวนการพิมพ์ หากแอปพลิเคชันของคุณมีงานเตรียมการแบบครั้งเดียวเพื่อ เช่น การรับสแนปชอตของข้อมูลที่จะพิมพ์ และเรียกใช้ที่นี่ การใช้งาน วิธีนี้ไม่จำเป็นต้องใช้ในอะแดปเตอร์ของคุณ
  • onLayout() - โทรออกทุกครั้งที่ ผู้ใช้เปลี่ยนการตั้งค่าการพิมพ์ซึ่งส่งผลต่อเอาต์พุต เช่น หน้ามีขนาดต่างกัน หรือการวางแนวหน้ากระดาษ เพื่อให้แอปพลิเคชันของคุณมีโอกาสคำนวณการจัดวาง หน้าที่จะพิมพ์ อย่างน้อยที่สุด วิธีนี้ต้องแสดงจํานวนหน้าที่คาดไว้ ในเอกสารที่พิมพ์
  • onWrite() - เรียกใช้ให้แสดงผลที่พิมพ์ หน้าลงในไฟล์ที่จะพิมพ์ อาจมีการเรียกเมธอดนี้อย่างน้อย 1 ครั้งหลังจากแต่ละรายการ onLayout() สาย
  • onFinish() - โทร 1 ครั้งในตอนท้าย ของกระบวนการพิมพ์ หากแอปพลิเคชันของคุณมีงานที่ต้องทำซ้ำๆ แบบครั้งเดียว เรียกใช้โค้ดเหล่านั้นที่นี่ คุณไม่จำเป็นต้องนำวิธีการนี้ในอะแดปเตอร์มาใช้

ส่วนต่อไปนี้จะอธิบายวิธีใช้เลย์เอาต์และวิธีการเขียน ที่สำคัญต่อการทำงานของอะแดปเตอร์การพิมพ์

หมายเหตุ: ระบบจะเรียกเมธอดของอะแดปเตอร์เหล่านี้บนเทรดหลักของแอปพลิเคชัน ถ้า คุณคาดหวังว่าระบบจะดำเนินการตามวิธีการเหล่านี้ในการติดตั้งใช้งาน ให้นำไปประมวลผลภายในชุดข้อความที่แยกต่างหาก ตัวอย่างเช่น คุณสามารถสรุป การจัดวางหรือพิมพ์เอกสาร การเขียนงานในออบเจ็กต์ AsyncTask ที่แยกกัน

ข้อมูลเอกสาร Compute Print

ในการใช้งานคลาส PrintDocumentAdapter นั้น แอปพลิเคชันต้องสามารถระบุประเภทของเอกสารที่สร้าง และคำนวณผลรวม จำนวนหน้าสำหรับงานพิมพ์ โดยให้ข้อมูลเกี่ยวกับขนาดของหน้าที่พิมพ์ การใช้งานเมธอด onLayout() ใน อะแดปเตอร์จะทำการคำนวณเหล่านี้ และให้ข้อมูลเกี่ยวกับผลที่คาดว่าจะได้รับของ งานพิมพ์ในชั้นเรียน PrintDocumentInfo ซึ่งรวมถึงจำนวนหน้าและ ประเภทเนื้อหา ตัวอย่างโค้ดต่อไปนี้แสดงการใช้งานพื้นฐานของเมธอด onLayout() สำหรับ PrintDocumentAdapter

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 จะเรียกใช้เมธอด onWrite() ของคลาส PrintDocumentAdapter ของแอปพลิเคชัน พารามิเตอร์ของเมธอดจะระบุว่าหน้าใดควรเป็น และไฟล์เอาต์พุตที่จะใช้ การใช้วิธีการนี้จะต้องแสดงผล หน้าเนื้อหาที่ขอไปยังไฟล์เอกสาร PDF ที่มีหลายหน้า เมื่อขั้นตอนนี้เสร็จสมบูรณ์แล้ว คุณจะ เรียกเมธอด onWriteFinished() ของออบเจ็กต์ Callback

หมายเหตุ: เฟรมเวิร์กการพิมพ์ของ 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 หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับการทำงานกับเทรดการดำเนินการ เช่น งานแบบไม่พร้อมกัน ดูกระบวนการ และ Threads

กำลังวาดเนื้อหาของหน้า PDF

เมื่อแอปพลิเคชันของคุณพิมพ์ แอปพลิเคชันของคุณต้องสร้างเอกสาร PDF และส่งไปยัง กรอบการทำงานการพิมพ์ของ Android สำหรับการพิมพ์ คุณสามารถใช้ไลบรารีการสร้าง 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 จะช่วยให้คุณวางรูปอัดได้ ที่ขอบของเอกสาร PDF เครื่องพิมพ์จำนวนมากไม่สามารถพิมพ์ที่ขอบ กระดาษที่จับต้องได้ ตรวจสอบให้แน่ใจว่าคุณคำนึงถึงขอบที่พิมพ์ไม่ได้เมื่อ คุณสร้างเอกสารสิ่งพิมพ์กับชั้นเรียนนี้