Stampa di documenti personalizzati

Per alcune applicazioni, come le app di disegno, le app di impaginazione e altre app incentrate su l'output grafico, la creazione di stupende pagine stampate è una caratteristica fondamentale. In questo caso, non è sufficiente per stampare un'immagine o un documento HTML. L'output di stampa per questi tipi di applicazioni richiede controllo preciso di tutto ciò che compare in una pagina, inclusi caratteri, flusso del testo, interruzioni di pagina intestazioni, piè di pagina ed elementi grafici.

La creazione di output di stampa completamente personalizzati per la tua applicazione richiede investimenti nella programmazione rispetto agli approcci discussi in precedenza. Devi creare componenti che comunicare con il framework di stampa, adattare le impostazioni della stampante, disegnare elementi di pagina e gestire la stampa su più pagine.

Questa lezione mostra come collegare il gestore di stampa, creare un adattatore per la stampa e creare contenuti per la stampa.

Quando la tua applicazione gestisce direttamente il processo di stampa, il primo passaggio dopo aver ricevuto un una richiesta di stampa da parte dell'utente è quella di connettersi al framework di stampa Android per ottenere un'istanza della classe PrintManager. Questa classe consente di inizializzare un processo di stampa e iniziare il ciclo di vita della stampa. Il seguente esempio di codice mostra come recuperare il gestore di stampa e avviare il processo di stampa.

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); //
}

Il codice di esempio riportato sopra mostra come assegnare un nome a un processo di stampa e impostare un'istanza della classe PrintDocumentAdapter che gestisce i passaggi del ciclo di vita di stampa. La Nella sezione successiva verrà trattata l'implementazione della classe dell'adattatore di stampa.

Nota: l'ultimo parametro della colonna print() prende un oggetto PrintAttributes. Puoi utilizzare questo parametro per forniscono suggerimenti sul framework di stampa e sulle opzioni preimpostate in base al ciclo di stampa precedente, migliorando così l'esperienza utente. Puoi utilizzare questo parametro anche per impostare opzioni che più appropriato per i contenuti da stampare, ad esempio impostando l'orientamento su orizzontale quando stampi una foto con quell'orientamento.

Un adattatore di stampa interagisce con il framework di stampa di Android e gestisce i passaggi del processo di stampa. Questo processo richiede che gli utenti selezionino stampanti e opzioni di stampa prima della creazione un documento da stampare. Queste selezioni possono influenzare l'output finale quando l'utente sceglie stampanti con capacità di output, dimensioni di pagina o orientamenti diversi. Man mano che vengono effettuate queste selezioni, il framework di stampa chiede all'adattatore di creare un layout e generare e stamparlo, in preparazione all'output finale. Quando un utente tocca il pulsante Stampa, il framework prende il documento finale stampato e lo passa a un fornitore di stampa per l'output. Durante la stampa di stampa, gli utenti possono scegliere di annullare l'azione di stampa, quindi l'adattatore di stampa deve rilevare e reagire a una richiesta di annullamento.

La classe astratta PrintDocumentAdapter è progettata per gestire ciclo di vita della stampa, che ha quattro metodi principali di callback. Devi implementare questi metodi nell'adattatore di stampa per interagire correttamente con il framework di stampa:

  • onStart() - Ricevuto una sola chiamata al l'inizio del processo di stampa. Se la tua applicazione prevede attività di preparazione una tantum per eseguire, ad esempio ottenere un'istantanea dei dati da stampare, eseguirle qui. Implementazione questo metodo nell'adattatore non è obbligatorio.
  • onLayout() - Richiamato ogni volta l'utente cambia un'impostazione di stampa che influisce sull'output, ad esempio dimensioni della pagina diverse, o l'orientamento della pagina, offrendo all'applicazione l'opportunità di calcolare il layout del pagine da stampare. Questo metodo deve restituire come minimo il numero di pagine previste nel documento stampato.
  • onWrite() - Chiamata per eseguire il rendering stampato pagine in un file da stampare. Questo metodo può essere chiamato una o più volte dopo ogni Chiamata onLayout().
  • onFinish() - Chiamata una volta alla fine del processo di stampa. Se l'applicazione deve eseguire attività di rimozione una tantum, eseguili qui. Non è necessario implementare questo metodo nell'adattatore.

Le seguenti sezioni descrivono come implementare i metodi di layout e scrittura, che sono fondamentale per il funzionamento di un adattatore di stampa.

Nota:questi metodi dell'adattatore vengono richiamati sul thread principale della tua applicazione. Se prevedi che l'esecuzione di questi metodi nella tua implementazione richieda una quantità significativa di nel tempo, implementarle in modo che vengano eseguite in un thread separato. Ad esempio, puoi incapsulare creare layout o stampare documenti di scrittura in oggetti AsyncTask separati.

Informazioni su documenti stampati Compute

Nell'ambito di un'implementazione della classe PrintDocumentAdapter, l'applicazione deve essere in grado di specificare il tipo di documento che sta creando e calcolare il totale numero di pagine per il processo di stampa, fornite informazioni sulle dimensioni della pagina stampata. L'implementazione del metodo onLayout() in l'adattatore esegue questi calcoli e fornisce informazioni sull'output previsto un processo di stampa in una classe PrintDocumentInfo, che include il numero di pagine e tipo di contenuti. Il seguente esempio di codice mostra un'implementazione di base del metodo onLayout() per 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'esecuzione del metodo onLayout() può avere tre risultati: completamento, annullamento o fallimento, nel caso in cui il calcolo Impossibile completare il layout. Devi indicare uno di questi risultati richiamando il dell'oggetto PrintDocumentAdapter.LayoutResultCallback.

Nota: il parametro booleano del onLayoutFinished() indica se i contenuti del layout sono stati effettivamente modificati o meno dall'ultima richiesta. L'impostazione corretta di questo parametro consente al framework di stampa di evitare chiamando inutilmente il metodo onWrite(), memorizzando nella cache il documento di stampa scritto in precedenza e migliorando le prestazioni.

Il lavoro principale di onLayout() è calcolare il numero di pagine previste come output in base agli attributi della stampante. Il modo in cui viene calcolato questo numero dipende molto da come la tua applicazione presenta le pagine per: stampa. Il codice di esempio riportato di seguito mostra un'implementazione in cui il numero di pagine è determinato dall'orientamento di stampa:

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);
}

Scrivi un file di documento di stampa

Quando è il momento di scrivere l'output di stampa in un file, il framework di stampa di Android chiama il metodo onWrite() della classe PrintDocumentAdapter dell'applicazione. I parametri del metodo specificano le pagine da e il file di output da utilizzare. L'implementazione di questo metodo deve quindi visualizzare pagina di contenuti richiesta in un file di documento PDF di più pagine. Al termine della procedura, chiama il metodo onWriteFinished() dell'oggetto callback.

Nota: il framework di stampa di Android potrebbe chiamare il metodo onWrite() una o più volte per ogni chiamata a onLayout(). Per questo motivo, è importante impostare il parametro booleano onLayoutFinished() al metodo false quando il layout dei contenuti di stampa non è cambiato, per evitare inutili riscritture del documento cartaceo.

Nota: il parametro booleano del onLayoutFinished() indica se i contenuti del layout sono stati effettivamente modificati o meno dall'ultima richiesta. L'impostazione corretta di questo parametro consente al framework di stampa di evitare chiamando inutilmente il metodo onLayout(), memorizzando nella cache il documento di stampa scritto in precedenza e migliorando le prestazioni.

L'esempio seguente illustra i meccanismi di base di questo processo utilizzando la classe PrintedPdfDocument per creare un file 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);

    ...
}

Questo esempio delega il rendering dei contenuti delle pagine PDF a drawPage() di cui parleremo nella prossima sezione.

Come per il layout, l'esecuzione di onWrite() può avere tre risultati: completamento, annullamento o fallimento nel caso in cui non possono essere scritti. Devi indicare uno di questi risultati richiamando il metodo metodo appropriato dell'oggetto PrintDocumentAdapter.WriteResultCallback.

Nota:il rendering di un documento per la stampa può essere un'operazione che richiede molte risorse. Nella per evitare di bloccare il thread dell'interfaccia utente principale della tua applicazione, dovresti prendere in considerazione eseguire le operazioni di rendering e scrittura della pagina su un thread separato, ad esempio in un AsyncTask. Per ulteriori informazioni sull'utilizzo di thread di esecuzione come le attività asincrone, consulta Processi e Thread.

Disegno dei contenuti della pagina PDF

Quando l'applicazione viene stampata, devi generare un documento PDF e passarlo a il framework di stampa Android. Per questo, puoi utilizzare qualsiasi libreria di generazione PDF che non ha uno scopo specifico. Questa lezione mostra come utilizzare il corso PrintedPdfDocument per generare pagine PDF dai tuoi contenuti.

Il corso PrintedPdfDocument utilizza un Canvas per disegnare elementi su una pagina PDF, come un disegno su un layout di attività. Puoi disegnare elementi sulla pagina stampata utilizzando i metodi di disegno Canvas. Le seguenti il codice di esempio mostra come disegnare alcuni semplici elementi sulla pagina di un documento PDF utilizzando questi metodo:

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);
}

Quando utilizzi Canvas per disegnare su una pagina PDF, gli elementi vengono specificati in che è 1/72 di pollice. Assicurati di utilizzare questa unità di misura per specificare la dimensione di elementi della pagina. Per il posizionamento degli elementi disegnati, il sistema di coordinate inizia da 0,0 per l'angolo in alto a sinistra della pagina.

Suggerimento: l'oggetto Canvas ti consente di posizionare la stampa elementi sul bordo di un documento PDF, molte stampanti non sono in grado di stampare sul bordo di un foglio di carta fisica. Assicurati di tenere conto dei bordi non stampabili della pagina quando puoi creare un documento cartaceo con questo corso.