Bei einigen Anwendungen, z. B. Zeichen-Apps, Seitenlayout-Apps und anderen Apps, die sich hauptsächlich auf die grafische Ausgabe konzentrieren, ist das Erstellen ansprechender gedruckter Seiten eine wichtige Funktion. In diesem Fall reicht es nicht aus, ein Bild oder ein HTML-Dokument zu drucken. Bei der Druckausgabe für diese Arten von Anwendungen muss alles, was auf einer Seite angezeigt wird, genau gesteuert werden, einschließlich Schriftarten, Textfluss, Seitenumbrüchen, Kopfzeilen, Fußzeilen und Grafikelementen.
Das Erstellen einer vollständig an Ihre Anwendung angepassten Druckausgabe erfordert mehr Programmierinvestitionen als die zuvor erläuterten Ansätze. Sie müssen Komponenten erstellen, die mit dem Druck-Framework kommunizieren, die Druckereinstellungen anpassen, Seitenelemente zeichnen und den Druck auf mehreren Seiten verwalten.
In dieser Lektion erfahren Sie, wie Sie eine Verbindung zum Druckmanager herstellen, einen Druckadapter erstellen und Inhalte für den Druck erstellen.
Mit dem Druckmanager verbinden
Wenn Ihre Anwendung den Druckprozess direkt verwaltet, besteht der erste Schritt nach dem Empfang einer Druckanfrage des Nutzers darin, eine Verbindung zum Android-Druck-Framework herzustellen und eine Instanz der PrintManager
-Klasse abzurufen. Mit dieser Klasse können Sie einen Druckauftrag initialisieren und den Drucklebenszyklus starten. Das folgende Codebeispiel zeigt, wie Sie den Druckmanager abrufen und den Druckvorgang starten.
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); // }
Der Beispielcode oben zeigt, wie Sie einen Druckauftrag benennen und eine Instanz der PrintDocumentAdapter
-Klasse festlegen, die die Schritte des Drucklebenszyklus verarbeitet. Die Implementierung der Druckerklasse wird im nächsten Abschnitt beschrieben.
Hinweis:Der letzte Parameter in der Methode print()
verwendet ein PrintAttributes
-Objekt. Mit diesem Parameter können Sie dem Druck-Framework und vordefinierte Optionen basierend auf dem vorherigen Druckzyklus Hinweise geben und so die Nutzerfreundlichkeit verbessern. Du kannst diesen Parameter auch verwenden, um Optionen festzulegen, die sich besser für den gerade gedruckten Inhalt eignen. So lässt sich z. B. die Ausrichtung auf Querformat festlegen, wenn ein Foto mit dieser Ausrichtung gedruckt wird.
Druckeradapter erstellen
Ein Druckeradapter interagiert mit dem Android-Druck-Framework und führt die Schritte des Druckvorgangs aus. Bei diesem Vorgang müssen Nutzer Drucker und Druckoptionen auswählen, bevor sie ein Dokument zum Drucken erstellen. Diese Auswahl kann die endgültige Ausgabe beeinflussen, wenn der Nutzer Drucker mit unterschiedlichen Ausgabefunktionen, Seitengrößen oder Seitenausrichtungen auswählt. Während Sie diese Auswahl treffen, fordert das Druck-Framework Ihren Adapter auf, das Layout zu erstellen und ein Druckdokument zur Vorbereitung der endgültigen Ausgabe zu generieren. Sobald ein Nutzer auf die Druckschaltfläche tippt, übergibt das Framework das endgültige Druckdokument zur Ausgabe an einen Druckanbieter. Während des Druckvorgangs können Nutzer die Druckaktion abbrechen. Daher muss auch der Druckadapter auf Abbruchanfragen warten und darauf reagieren.
Die abstrakte Klasse PrintDocumentAdapter
wurde für den Drucklebenszyklus entwickelt. Es gibt vier Haupt-Callback-Methoden. Sie müssen diese Methoden in Ihrem Druckeradapter implementieren, damit eine ordnungsgemäße Interaktion mit dem Druck-Framework möglich ist:
onStart()
: Wird zu Beginn des Druckvorgangs einmal aufgerufen. Wenn für Ihre Anwendung einmalige Vorbereitungsaufgaben erforderlich sind, z. B. das Abrufen eines Snapshots der zu druckenden Daten, führen Sie diese hier aus. Die Implementierung dieser Methode in Ihrem Adapter ist nicht erforderlich.onLayout()
– Wird jedes Mal aufgerufen, wenn ein Nutzer eine Druckeinstellung ändert, die sich auf die Ausgabe auswirkt, z. B. bei einer anderen Seitengröße oder Seitenausrichtung. So kann deine Anwendung das Layout der zu druckenden Seiten berechnen. Diese Methode muss mindestens zurückgeben, wie viele Seiten im gedruckten Dokument erwartet werden.onWrite()
– Zum Rendern gedruckter Seiten in eine zu druckende Datei wird aufgerufen. Diese Methode kann nach jedemonLayout()
-Aufruf einmal oder mehrmals aufgerufen werden.onFinish()
: Wird einmal am Ende des Druckvorgangs aufgerufen. Wenn für Ihre Anwendung einmalige Teardown-Aufgaben auszuführen sind, führen Sie diese hier aus. Die Implementierung dieser Methode in Ihrem Adapter ist nicht erforderlich.
In den folgenden Abschnitten wird beschrieben, wie die Layout- und Schreibmethoden implementiert werden, die für das Funktionieren eines Druckadapters entscheidend sind.
Hinweis:Diese Adaptermethoden werden im Hauptthread Ihrer Anwendung aufgerufen. Wenn Sie davon ausgehen, dass die Ausführung dieser Methoden in Ihrer Implementierung viel Zeit in Anspruch nimmt, implementieren Sie sie so, dass sie in einem separaten Thread ausgeführt werden. Du kannst beispielsweise das Layout oder die Schreibarbeit für Druckdokumente in separate AsyncTask
-Objekte kapseln.
Druckdokumentinformationen berechnen
Innerhalb einer Implementierung der Klasse PrintDocumentAdapter
muss deine Anwendung den Dokumenttyp angeben und die Gesamtzahl der Seiten für den Druckauftrag anhand der Informationen zur gedruckten Seitengröße berechnen können.
Durch die Implementierung der Methode onLayout()
im Adapter werden diese Berechnungen durchgeführt und Informationen zur erwarteten Ausgabe des Druckauftrags in einer PrintDocumentInfo
-Klasse bereitgestellt, einschließlich der Anzahl der Seiten und des Inhaltstyps. Das folgende Codebeispiel zeigt eine grundlegende Implementierung der Methode onLayout()
für eine 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."); } }
Die Ausführung der Methode onLayout()
kann drei Ergebnisse haben: Abschluss, Abbruch oder Fehler, falls die Berechnung des Layouts nicht abgeschlossen werden kann. Sie müssen eines dieser Ergebnisse angeben, indem Sie die entsprechende Methode des PrintDocumentAdapter.LayoutResultCallback
-Objekts aufrufen.
Hinweis:Der boolesche Parameter der onLayoutFinished()
-Methode gibt an, ob sich der Layoutinhalt seit der letzten Anfrage geändert hat. Wenn dieser Parameter richtig konfiguriert ist, kann das Druck-Framework das unnötige Aufrufen der onWrite()
-Methode vermeiden, wodurch das zuvor geschriebene Druckdokument im Cache gespeichert und die Leistung verbessert wird.
Die Hauptfunktion von onLayout()
besteht darin, die Anzahl der Seiten zu berechnen, die basierend auf den Attributen des Druckers als Ausgabe erwartet werden.
Wie du diese Zahl berechnest, hängt stark davon ab, wie das Layout der zu druckenden Seiten in deiner Anwendung erfolgt. Das folgende Codebeispiel zeigt eine Implementierung, bei der die Anzahl der Seiten durch die Druckausrichtung bestimmt wird:
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); }
Eine Druckdokumentdatei schreiben
Wenn es an der Zeit ist, die Druckausgabe in eine Datei zu schreiben, ruft das Android-Druck-Framework die Methode onWrite()
der Klasse PrintDocumentAdapter
Ihrer Anwendung auf. Die Parameter der Methode geben an, welche Seiten geschrieben und welche Ausgabedatei verwendet werden soll. Ihre Implementierung dieser Methode muss dann jede angeforderte Seite mit Inhalten in eine mehrseitige PDF-Dokumentdatei rendern. Nach Abschluss dieses Vorgangs rufen Sie die Methode onWriteFinished()
des Callback-Objekts auf.
Hinweis:Das Android Print-Framework kann die Methode onWrite()
für jeden Aufruf von onLayout()
einmal oder mehrmals aufrufen. Aus diesem Grund ist es wichtig, den booleschen Parameter der Methode onLayoutFinished()
auf false
zu setzen, wenn sich das Layout der Druckinhalte nicht geändert hat, um unnötiges Neuschreiben des Druckdokuments zu vermeiden.
Hinweis:Der boolesche Parameter der onLayoutFinished()
-Methode gibt an, ob sich der Layoutinhalt seit der letzten Anfrage geändert hat. Wenn dieser Parameter richtig konfiguriert ist, kann das Druck-Framework das unnötige Aufrufen der onLayout()
-Methode vermeiden, wodurch das zuvor geschriebene Druckdokument im Cache gespeichert und die Leistung verbessert wird.
Im folgenden Beispiel werden die grundlegenden Mechanismen dieses Prozesses unter Verwendung der PrintedPdfDocument
-Klasse zum Erstellen einer PDF-Datei veranschaulicht:
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); ... }
In diesem Beispiel wird das Rendern von PDF-Seiteninhalten an die Methode drawPage()
delegiert, die im nächsten Abschnitt erläutert wird.
Wie beim Layout kann die Ausführung der Methode onWrite()
drei Ergebnisse haben: Abschluss, Abbruch oder Fehler, falls der Inhalt nicht geschrieben werden kann. Sie müssen eines dieser Ergebnisse angeben, indem Sie die entsprechende Methode des PrintDocumentAdapter.WriteResultCallback
-Objekts aufrufen.
Hinweis: Das Rendern eines Dokuments für den Druck kann ein ressourcenintensiver Vorgang sein. Damit der Hauptthread der Benutzeroberfläche Ihrer Anwendung nicht blockiert wird, sollten Sie die Seiten-Rendering- und Schreibvorgänge in einem separaten Thread ausführen, z. B. in einem AsyncTask
.
Weitere Informationen zum Arbeiten mit Ausführungsthreads wie asynchrone Aufgaben finden Sie unter Prozesse und Threads.
Zeichnen von PDF-Seiteninhalt
Wenn deine App gedruckt wird, muss deine App ein PDF-Dokument generieren und zum Drucken an das Android Print-Framework übergeben. Für diesen Zweck können Sie eine beliebige PDF-Generierungsbibliothek verwenden. In dieser Lektion erfahren Sie, wie Sie mit der Klasse PrintedPdfDocument
aus Ihren Inhalten PDF-Seiten generieren können.
Die Klasse PrintedPdfDocument
verwendet ein Canvas
-Objekt, um Elemente auf einer PDF-Seite zu zeichnen, ähnlich wie beim Zeichnen in einem Aktivitätslayout. Mit den Zeichenmethoden Canvas
können Sie Elemente auf der gedruckten Seite zeichnen. Der folgende Beispielcode zeigt, wie Sie mit diesen Methoden einige einfache Elemente auf einer PDF-Dokumentseite zeichnen:
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); }
Wenn Sie mit Canvas
auf einer PDF-Seite zeichnen, werden die Elemente in Punkten angegeben. Das entspricht 1/72 Zoll. Achte darauf, diese Maßeinheit zu verwenden, um die Größe der Elemente auf der Seite anzugeben. Zur Positionierung gezeichneter Elemente beginnt das Koordinatensystem für die obere linke Ecke der Seite bei 0,0.
Tipp:Mit dem Canvas
-Objekt können Sie Druckelemente am Rand eines PDF-Dokuments platzieren, bei vielen Druckern ist das jedoch nicht möglich. Achte darauf, dass du die nicht druckbaren Ränder der Seite berücksichtigst, wenn du mit dieser Klasse ein Druckdokument erstellst.