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