Pour certaines applications, telles que les applications de dessin, de mise en page et d'autres applications axées sur la sortie graphique, la création de superbes pages imprimées est une fonctionnalité essentielle. Dans ce cas, il ne suffit pas d'imprimer une image ou un document HTML. Pour ces types d'applications, la sortie d'impression nécessite un contrôle précis de tout ce qui compose une page, y compris les polices, le flux de texte, les sauts de page, les en-têtes, les pieds de page et les éléments graphiques.
La création d'une sortie d'impression entièrement personnalisée pour votre application nécessite plus d'investissement en programmation que les approches décrites précédemment. Vous devez créer des composants qui communiquent avec le framework d'impression, ajuster les paramètres de l'imprimante, dessiner des éléments de page et gérer l'impression sur plusieurs pages.
Cette leçon vous explique comment vous connecter au gestionnaire d'impression, créer un adaptateur d'impression et créer du contenu pour l'impression.
Se connecter au gestionnaire d'impression
Lorsque votre application gère directement le processus d'impression, la première étape après avoir reçu une requête d'impression de la part de l'utilisateur consiste à se connecter au framework d'impression Android et à obtenir une instance de la classe PrintManager
. Cette classe vous permet d'initialiser une tâche d'impression et de commencer le cycle de vie d'une impression. L'exemple de code suivant montre comment obtenir le gestionnaire d'impression et lancer le processus d'impression.
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); // }
L'exemple de code ci-dessus montre comment nommer une tâche d'impression et définir une instance de la classe PrintDocumentAdapter
qui gère les étapes du cycle d'impression. L'implémentation de la classe d'adaptateur d'impression est abordée dans la section suivante.
Remarque:Le dernier paramètre de la méthode print()
utilise un objet PrintAttributes
. Vous pouvez utiliser ce paramètre pour fournir des indications sur le framework d'impression et les options prédéfinies en fonction du cycle d'impression précédent, améliorant ainsi l'expérience utilisateur. Vous pouvez également utiliser ce paramètre pour définir des options plus adaptées au contenu à imprimer, telles que la définition de l'orientation en mode paysage lors de l'impression d'une photo dans cette orientation.
Créer un adaptateur d'impression
Un adaptateur d'impression interagit avec le framework d'impression Android et gère les étapes du processus d'impression. Ce processus oblige les utilisateurs à sélectionner des imprimantes et des options d'impression avant de créer un document pour impression. Ces sélections peuvent influencer la sortie finale, car l'utilisateur choisit des imprimantes offrant différentes capacités de sortie, différentes tailles de page ou différentes orientations de page. Au fur et à mesure que ces sélections sont effectuées, le framework d'impression demande à votre adaptateur de mettre en page et de générer un document imprimé en vue de la sortie finale. Lorsqu'un utilisateur appuie sur le bouton d'impression, le framework prend le document d'impression final et le transmet à un fournisseur de service d'impression. Au cours du processus d'impression, les utilisateurs peuvent choisir d'annuler l'action d'impression. Votre adaptateur d'impression doit donc également écouter les demandes d'annulation et y réagir.
La classe abstraite PrintDocumentAdapter
est conçue pour gérer le cycle de vie d'impression, qui comporte quatre méthodes de rappel principales. Vous devez implémenter les méthodes suivantes dans votre adaptateur d'impression pour interagir correctement avec le framework d'impression:
onStart()
: appelé une fois au début du processus d'impression. Si votre application doit effectuer des tâches de préparation ponctuelles, telles que l'obtention d'un instantané des données à imprimer, exécutez-les ici. Il n'est pas nécessaire d'implémenter cette méthode dans votre adaptateur.onLayout()
: Appelé chaque fois qu'un utilisateur modifie un paramètre d'impression qui affecte la sortie, par exemple une taille ou une orientation de page différente, ce qui permet à votre application de calculer la mise en page des pages à imprimer. Cette méthode doit au minimum renvoyer le nombre de pages attendues dans le document imprimé.onWrite()
: appelé pour afficher les pages imprimées dans un fichier à imprimer. Cette méthode peut être appelée une ou plusieurs fois après chaque appelonLayout()
.onFinish()
: appelé une fois à la fin du processus d'impression. Si votre application doit effectuer des tâches de suppression ponctuelles, exécutez-les ici. Il n'est pas nécessaire d'implémenter cette méthode dans votre adaptateur.
Les sections suivantes décrivent comment implémenter les méthodes de mise en page et d'écriture, qui sont essentielles au fonctionnement d'un adaptateur d'impression.
Remarque:Ces méthodes d'adaptateur sont appelées sur le thread principal de votre application. Si vous pensez que l'exécution de ces méthodes dans votre implémentation prendra beaucoup de temps, implémentez-les pour qu'elles s'exécutent dans un thread distinct. Par exemple, vous pouvez encapsuler la mise en page ou imprimer le travail d'écriture d'un document dans des objets AsyncTask
distincts.
Calculer les informations sur l'impression du document
Dans une implémentation de la classe PrintDocumentAdapter
, votre application doit pouvoir spécifier le type de document qu'elle crée et calculer le nombre total de pages pour la tâche d'impression, en fonction des informations sur le format des pages imprimées.
L'implémentation de la méthode onLayout()
dans l'adaptateur effectue ces calculs et fournit des informations sur le résultat attendu de la tâche d'impression dans une classe PrintDocumentInfo
, y compris le nombre de pages et le type de contenu. L'exemple de code suivant illustre une implémentation de base de la méthode onLayout()
pour un 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."); } }
L'exécution de la méthode onLayout()
peut avoir trois résultats: achèvement, annulation ou échec lorsque le calcul de la mise en page ne peut pas être effectué. Vous devez indiquer l'un de ces résultats en appelant la méthode appropriée de l'objet PrintDocumentAdapter.LayoutResultCallback
.
Remarque:Le paramètre booléen de la méthode onLayoutFinished()
indique si le contenu de la mise en page a effectivement été modifié depuis la dernière requête. La définition correcte de ce paramètre permet au framework d'impression d'éviter d'appeler inutilement la méthode onWrite()
, ce qui met en cache le document imprimé précédemment et améliore les performances.
La tâche principale de onLayout()
consiste à calculer le nombre de pages attendues en tant que sortie en fonction des attributs de l'imprimante.
La manière dont vous calculez ce nombre dépend fortement de la mise en page de votre application pour l'impression. L'exemple de code suivant montre une implémentation dans laquelle le nombre de pages est déterminé par l'orientation de l'impression:
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); }
Écrire un fichier d'impression
Au moment d'écrire la sortie d'impression dans un fichier, le framework d'impression Android appelle la méthode onWrite()
de la classe PrintDocumentAdapter
de votre application. Les paramètres de la méthode spécifient les pages à écrire et le fichier de sortie à utiliser. La mise en œuvre de cette méthode doit ensuite afficher chaque page de contenu demandée dans un fichier PDF de plusieurs pages. Une fois ce processus terminé, vous appelez la méthode onWriteFinished()
de l'objet de rappel.
Remarque:Le framework d'impression Android peut appeler la méthode onWrite()
une ou plusieurs fois pour chaque appel à onLayout()
. Pour cette raison, il est important de définir le paramètre booléen de la méthode onLayoutFinished()
sur false
lorsque la mise en page du contenu imprimé n'a pas changé, afin d'éviter toute réécriture inutile du document imprimé.
Remarque:Le paramètre booléen de la méthode onLayoutFinished()
indique si le contenu de la mise en page a effectivement été modifié depuis la dernière requête. La définition correcte de ce paramètre permet au framework d'impression d'éviter d'appeler inutilement la méthode onLayout()
, ce qui met en cache le document imprimé précédemment et améliore les performances.
L'exemple suivant illustre le mécanisme de base de ce processus en utilisant la classe PrintedPdfDocument
pour créer un fichier 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); ... }
Cet exemple délègue le rendu du contenu de la page PDF à la méthode drawPage()
, qui est abordée dans la section suivante.
Comme pour la mise en page, l'exécution de la méthode onWrite()
peut avoir trois résultats: achèvement, annulation ou échec lorsque le contenu ne peut pas être écrit. Vous devez indiquer l'un de ces résultats en appelant la méthode appropriée de l'objet PrintDocumentAdapter.WriteResultCallback
.
Remarque:L'affichage d'un document pour impression peut prendre beaucoup de ressources. Pour éviter de bloquer le thread d'interface utilisateur principal de votre application, vous devez envisager d'effectuer les opérations d'affichage et d'écriture de la page sur un thread distinct, par exemple dans un AsyncTask
.
Pour en savoir plus sur l'utilisation de threads d'exécution tels que les tâches asynchrones, consultez la section Processus et threads.
Dessiner le contenu de la page d'un PDF
Lors de l'impression de votre application, elle doit générer un document PDF et le transmettre à Android Print Framework pour l'impression. Pour ce faire, vous pouvez utiliser n'importe quelle bibliothèque de génération de PDF. Cette leçon explique comment utiliser la classe PrintedPdfDocument
pour générer des pages PDF à partir de votre contenu.
La classe PrintedPdfDocument
utilise un objet Canvas
pour dessiner des éléments sur une page PDF, comme pour le dessin sur la mise en page d'une activité. Vous pouvez dessiner des éléments sur la page imprimée à l'aide des méthodes de dessin Canvas
. L'exemple de code suivant montre comment dessiner des éléments simples sur une page de document PDF à l'aide des méthodes suivantes:
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); }
Lorsque vous utilisez Canvas
pour dessiner sur une page PDF, les éléments sont spécifiés en points, soit 1/72 de pouce. Veillez à utiliser cette unité de mesure pour spécifier la taille des éléments sur la page. Pour le positionnement des éléments dessinés, le système de coordonnées commence à 0,0 pour l'angle supérieur gauche de la page.
Conseil:Bien que l'objet Canvas
vous permette de placer des éléments d'impression sur le bord d'un document PDF, de nombreuses imprimantes ne sont pas en mesure d'imprimer sur le bord d'une feuille de papier physique. Veillez à tenir compte des bords non imprimables de la page lorsque vous créez un document imprimé avec cette classe.