לאפליקציות מסוימות, כמו אפליקציות לשרטוט, אפליקציות לפריסת דפים ואפליקציות אחרות שמתמקדות פלט גרפי, יצירת דפים מודפסים יפהפיים היא תכונה חשובה. במקרה הזה, לא מספיק כדי להדפיס תמונה או מסמך 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()
ה-method לוקחת אובייקט PrintAttributes
. אפשר להשתמש בפרמטר הזה כדי
לספק רמזים למסגרת ההדפסה ואפשרויות מוגדרות מראש בהתאם למחזור ההדפסה הקודם,
וכך לשפר את חוויית המשתמש. תוכלו להשתמש בפרמטר הזה גם כדי להגדיר אפשרויות
התאמה טובה יותר לתוכן המודפס, למשל הגדרת כיוון לרוחב
כשמדפיסים תמונה בכיוון הזה.
יצירת מתאם הדפסה
מתאם הדפסה יוצר אינטראקציה עם מסגרת ההדפסה של Android ומטפל בשלבים תהליך ההדפסה. התהליך הזה מחייב את המשתמשים לבחור מדפסות ואפשרויות הדפסה לפני היצירה מסמך להדפסה. הבחירות האלה יכולות להשפיע על הפלט הסופי במהלך בחירת המשתמש מדפסות עם יכולות פלט שונות, דפים בגדלים שונים או בכיוונים שונים של דפים. כשבוחרים באפשרויות האלה, מסגרת ההדפסה מבקשת מהמתאם לצאת וליצור כהכנה לקבלת הפלט הסופי. כשמשתמש מקיש על לחצן ההדפסה, המסגרת לוקחת את המסמך הסופי המודפס ומעבירה אותו לספק ההדפסה לצורך פלט. במהלך ההדפסה המשתמשים יכולים לבחור לבטל את פעולת ההדפסה, כך שמתאם ההדפסה חייב גם ולהגיב לבקשות ביטול.
המחלקה המופשטת PrintDocumentAdapter
תוכננה לטפל
מחזור החיים של הדפסה, שכולל ארבע שיטות עיקריות של קריאה חוזרת. צריך להטמיע את השיטות האלה
במתאם ההדפסה כדי לקיים אינטראקציה תקינה עם מסגרת ההדפסה:
onStart()
- התקשרת פעם אחת ב תחילתו של תהליך ההדפסה. אם יש באפליקציה משימות הכנה חד-פעמיות, מבצעים, כמו קבלת תמונת מצב של הנתונים שרוצים להדפיס, מפעילים אותם כאן. הטמעה השיטה הזו במתאם לא נדרשת.onLayout()
- התקשרות בכל פעם משתמש משנה הגדרת הדפסה שמשפיעה על הפלט, למשל גודל דף שונה, או בכיוון הדף, באופן שמאפשר לאפליקציה לחשב את הפריסה דפים שיודפסו. לכל הפחות, השיטה הזו חייבת להחזיר את מספר הדפים הצפויים במסמך המודפס.onWrite()
– בוצעה שיחה כדי לעבד את ההדפסה לדפים בקובץ שרוצים להדפיס. ניתן לקרוא לשיטה הזו פעם אחת או יותר אחרי כל שיחתonLayout()
.onFinish()
– שיחה אחת בסוף בתהליך ההדפסה. אם יש באפליקציה משימות ניתוח חד-פעמיות שצריך לבצע, לבצע אותן כאן. אין צורך להטמיע את השיטה הזו במתאם.
בקטעים הבאים מוסבר איך להטמיע את שיטות הפריסה והכתיבה. קריטי לתפקוד של מתאם הדפסה.
הערה: שיטות המתאמים האלה מופעלות ב-thread הראשי של האפליקציה. אם המיקום
אתם מצפים שהביצוע של השיטות האלה בהטמעה שלכם ידרוש
זמן, להטמיע אותן כדי לבצע אותן בשרשור נפרד. לדוגמה, אפשר להקיף את
פריסה או הדפסה של מסמכים באובייקטים נפרדים מסוג AsyncTask
.
פרטי מסמך להדפסת נתונים
בתוך הטמעה של הכיתה PrintDocumentAdapter
,
היישום חייב להיות מסוגל לציין את סוג המסמך שהוא יוצר ולחשב את הסכום הכולל
מספר הדפים למשימת הדפסה, בהינתן מידע על גודל הדף שהודפס.
ההטמעה של השיטה onLayout()
ב
והמתאם מבצע את החישובים האלה ומספק מידע על הפלט הצפוי
משימת הדפסה במחלקה PrintDocumentInfo
, כולל מספר העמודים
סוג התוכן. הקוד לדוגמה הבא מציג הטמעה בסיסית של ה-method 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()
יכולה
יש שלוש תוצאות: השלמה, ביטול או כשל במקרה שבו חישוב של
אי אפשר להשלים את הפריסה. עליך לציין אחת מהתוצאות האלו באמצעות קריאה
של האובייקט 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 מרובה דפים. כשהתהליך הזה יסתיים,
קוראים ל-method onWriteFinished()
של אובייקט הקריאה החוזרת.
הערה: יכול להיות שמסגרת Android להדפסה ב-Android תפעיל את השיטה onWrite()
פעם אחת או יותר
שיחה למספר 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()
יכולות להיות שלוש תוצאות: השלמה, ביטול או כשל במקרה שבו
אי אפשר לכתוב את התוכן. עליך לציין אחת מהתוצאות האלה באמצעות קריאה
של האובייקט PrintDocumentAdapter.WriteResultCallback
המתאים.
הערה: עיבוד מסמך להדפסה יכול להיות פעולה שצורכת משאבים רבים. לחשבון
כדי למנוע את חסימת ה-thread הראשי של ממשק המשתמש של האפליקציה, כדאי לשקול
לבצע פעולות עיבוד וכתיבה בשרשור בשרשור נפרד,
ב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, בהרבה מדפסות לא ניתן להדפיס
פיסת נייר פיזית. חשוב לשים לב לקצוות של הדף שלא ניתנים להדפסה
תבנה מסמך לדפוס באמצעות הכיתה הזו.