Imprimer des documents personnalisés

Pour certaines applications, telles que les applications de dessin, les applications de mise en page et d'autres applications axées sur graphique, la création de belles pages imprimées est une caractéristique clé. Dans ce cas, il ne suffit pas pour imprimer une image ou un document HTML. Pour ces types d'applications, la sortie à imprimer un contrôle précis de tous les éléments d'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.

Créer une sortie d'impression entièrement personnalisée pour votre application nécessite davantage que les approches mentionnées précédemment. Vous devez créer des composants communiquer avec le cadre d'impression, s'adapter aux paramètres de l'imprimante, dessiner les éléments de la 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 créer du contenu pour l'impression.

Lorsque votre application gère directement le processus d'impression, la première étape suivant la réception d'une d'impression de votre 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 commencer le cycle de vie de l'impression. L'exemple de code suivant montre comment récupérer 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 de vie de l'impression. La l'implémentation de la classe de l'adaptateur d'impression est abordée dans la section suivante.

Remarque:Le dernier paramètre de print() utilise un objet PrintAttributes. Vous pouvez utiliser ce paramètre pour Fournir des conseils sur le cadre d'impression et les options prédéfinies en fonction du cycle d'impression précédent et ainsi améliorer l'expérience utilisateur. Vous pouvez également utiliser ce paramètre pour définir des options sont plus adaptées au contenu imprimé (par exemple, en optant pour le mode paysage) ; lorsque vous imprimez une photo dans cette orientation.

Un adaptateur d'impression interagit avec le framework d'impression Android et gère les étapes de la du processus d'impression. Ce processus oblige les utilisateurs à sélectionner les imprimantes et les options d'impression avant de créer un document à imprimer. Ces sélections peuvent influencer le résultat final choisi par l'utilisateur des imprimantes avec 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 imprimer le document, en préparation de la sortie finale. Une fois qu'un utilisateur appuie sur le bouton d'impression, le cadre prend le document final imprimé et le transmet à un prestataire d'impression pour sortie. Pendant l'impression les utilisateurs peuvent choisir d'annuler l'action d'impression. Par conséquent, votre adaptateur d'impression doit également écouter et réagir aux demandes d'annulation.

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 ces méthodes dans votre adaptateur d'impression afin d'interagir correctement avec l'environnement d'impression:

  • onStart() : appelé une fois au au début du processus d'impression. Si votre candidature comporte des tâches de préparation ponctuelles comme l'obtention d'un instantané des données à imprimer, exécutez-les ici. Implémentation cette méthode dans votre adaptateur n'est pas nécessaire.
  • onLayout() : appelé chaque fois que l'utilisateur modifie un paramètre d'impression qui a un impact sur le résultat, comme une taille de page différente, ou l'orientation de la page, ce qui permet à votre application de calculer la mise en page pages à imprimer. Au minimum, cette méthode doit renvoyer le nombre de pages attendu dans le document imprimé.
  • onWrite() : appelé pour afficher l'impression pages en un fichier à imprimer. Cette méthode peut être appelée une ou plusieurs fois après chaque onLayout() appel.
  • onFinish() : appelé une fois à la fin du processus d'impression. Si votre application a des tâches de suppression ponctuelles à effectuer, les exécuter 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 essentiel 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 vous attendez à ce que l'exécution de ces méthodes dans votre implémentation demande beaucoup implémentez-les pour qu'elles s'exécutent dans un thread distinct. Par exemple, vous pouvez encapsuler le mettre en page ou imprimer des documents dans des objets AsyncTask distincts.

Informations sur les documents d'impression Compute

Dans une implémentation de la classe PrintDocumentAdapter, votre application doit être en mesure de spécifier le type de document qu'elle crée et de calculer le total nombre de pages pour la tâche d'impression, en fonction du format de la page imprimée. L'implémentation de la méthode onLayout() dans l'adaptateur effectue ces calculs et fournit des informations sur la sortie attendue du d'une tâche d'impression dans une classe PrintDocumentInfo, y compris le nombre de pages et type de contenu. L'exemple de code suivant montre 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 ont trois résultats: l'achèvement, l'annulation ou l'échec dans le cas où le calcul du Impossible de finaliser la mise en page. Vous devez indiquer l'un de ces résultats en appelant la méthode de l'objet PrintDocumentAdapter.LayoutResultCallback.

Remarque:Le paramètre booléen de la méthode La méthode onLayoutFinished() indique si le contenu de la mise en page a été modifié ou non. depuis la dernière demande. La définition de ce paramètre correctement permet au framework d'impression d'éviter appeler inutilement la méthode onWrite(), essentiellement mettre en cache le document imprimé précédemment écrit et améliorer les performances.

L'action principale de onLayout() est calculer le nombre de pages attendues comme sortie en fonction des attributs de l'imprimante. La façon dont vous calculez ce nombre dépend fortement de la façon dont votre application présente les pages pour impression. L'exemple de code suivant illustre une implémentation dans laquelle le nombre de pages est déterminée par l'orientation d'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 de document 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 écrit et le fichier de sortie à utiliser. Votre implémentation de cette méthode doit ensuite afficher chaque la page de contenu demandée en fichier PDF de plusieurs pages. Une fois ce processus terminé, 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 à onLayout(). Pour cette raison, il est il est important de définir le paramètre booléen Méthode onLayoutFinished() sur false lorsque la mise en page du contenu imprimé n'a pas changé pour éviter les réécritures inutiles du document imprimé.

Remarque:Le paramètre booléen de la méthode La méthode onLayoutFinished() indique si le contenu de la mise en page a été modifié ou non. depuis la dernière demande. La définition de ce paramètre correctement permet au framework d'impression d'éviter appeler inutilement la méthode onLayout(), essentiellement mettre en cache le document imprimé précédemment écrit et améliorer les performances.

L'exemple suivant illustre les mécanismes de base de ce processus à l'aide de 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 des pages PDF à drawPage() qui est abordée dans la section suivante.

Comme pour la mise en page, l'exécution de onWrite() peut avoir trois résultats: l'achèvement, l'annulation ou l'échec dans le cas où le le contenu ne peut pas être écrit. Vous devez indiquer l'un de ces résultats en appelant la méthode la méthode appropriée de l'objet PrintDocumentAdapter.WriteResultCallback.

Remarque:Le rendu d'un document pour impression peut nécessiter beaucoup de ressources. Dans pour éviter de bloquer le thread principal de l'interface utilisateur de votre application, vous devez envisager les opérations de rendu et d'écriture de la page sur un thread distinct, par exemple dans un AsyncTask. Pour en savoir plus sur l'utilisation des threads d'exécution comme les tâches asynchrones, consultez la section Processus et les threads.

Dessiner le contenu d'une page PDF

Lors de l'impression, votre candidature doit générer un document PDF et le transmettre à le framework d'impression Android pour l'impression. Vous pouvez utiliser n'importe quelle bibliothèque de génération de PDF l'objectif. 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 élément Canvas. permettant de dessiner des éléments sur une page PDF, de la même manière que dans une mise en page d'activité. Vous pouvez dessiner sur la page imprimée à l'aide des méthodes de dessin Canvas. Les éléments suivants : exemple de code montre comment dessiner des éléments simples sur une page de document PDF en utilisant ces méthodes:

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 dans soit 1/72 de pouce. Veillez à utiliser cette unité de mesure pour indiquer la taille. d'éléments sur la page. Pour positionner les é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 une impression sur le bord d'un document PDF, de nombreuses imprimantes ne parviennent pas à imprimer jusqu'au bord d'un une feuille de papier physique. Assurez-vous de tenir compte des bords non imprimables de la page lorsque vous créerez un document imprimé avec cette classe.