Para alguns aplicativos, como apps de desenho, de layout de página e outros com foco em resultados gráficos, criar belas páginas impressas é um recurso fundamental. Nesse caso, não basta imprimir uma imagem ou um documento HTML. A saída de impressão para esses tipos de aplicativos requer um controle preciso de tudo o que entra em uma página, incluindo fontes, fluxo de texto, quebras de página, cabeçalhos, rodapés e elementos gráficos.
Criar uma saída de impressão totalmente personalizada para o aplicativo requer mais investimento em programação do que as abordagens discutidas anteriormente. Você precisa criar componentes que se comuniquem com o framework de impressão, ajustar as configurações da impressora, desenhar elementos da página e gerenciar a impressão em várias páginas.
Esta lição mostra como você se conecta ao gerenciador de impressão, cria um adaptador de impressão e cria conteúdo para impressão.
Conectar-se ao gerenciador de impressão
Quando o app gerencia o processo de impressão diretamente, a primeira etapa após receber uma solicitação de impressão do usuário é se conectar ao framework de impressão do Android e conseguir uma instância da classe PrintManager
. Essa classe permite inicializar um trabalho de impressão e iniciar o ciclo de impressão. O exemplo de código a seguir mostra como conseguir o gerenciador de impressão e iniciar o processo de impressão.
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); // }
O exemplo acima demonstra como nomear um trabalho de impressão e definir uma instância da classe PrintDocumentAdapter
, que processa as etapas do ciclo de impressão. A implementação da classe do adaptador de impressão é discutida na próxima seção.
Observação:o último parâmetro no método print()
usa um objeto PrintAttributes
. Você pode usar esse parâmetro para dar dicas para o framework de impressão e opções predefinidas com base no ciclo de impressão anterior, melhorando a experiência do usuário. Você também pode usar esse parâmetro para definir opções mais adequadas ao conteúdo que está sendo impresso, como definir a orientação como paisagem ao imprimir uma foto que esteja nessa orientação.
Criar um adaptador de impressão
Um adaptador de impressão interage com o framework de impressão do Android e processa as etapas do processo de impressão. Esse processo exige que os usuários selecionem impressoras e opções de impressão antes de criar um documento para impressão. Essas seleções podem influenciar a saída final, já que o usuário escolhe impressoras com diferentes recursos de saída, tamanhos de página ou orientações de página diferentes. Quando essas seleções são feitas, o framework de impressão pede que o adaptador faça o layout e gere um documento de impressão, em preparação para a saída final. Depois que um usuário toca no botão de impressão, o framework recebe o documento de impressão final e o transmite a um provedor de impressão para saída. Durante o processo de impressão, os usuários podem optar por cancelar a ação de impressão. Portanto, o adaptador de impressão também precisa detectar e reagir a solicitações de cancelamento.
A classe abstrata PrintDocumentAdapter
foi projetada para processar o
ciclo de impressão, que tem quatro métodos principais de callback. Implemente estes métodos no adaptador de impressão para interagir corretamente com o framework de impressão:
onStart()
: chamado uma vez no início do processo de impressão. Se o aplicativo tiver tarefas de preparação únicas para executar, como receber um snapshot dos dados a serem impressos, execute-as aqui. Não é necessário implementar esse método no seu adaptador.onLayout()
: chamado sempre que um usuário altera uma configuração de impressão que afeta a saída, como um tamanho ou orientação de página diferente, oferecendo ao seu aplicativo a oportunidade de calcular o layout das páginas a serem impressas. Esse método precisa retornar, no mínimo, quantas páginas são esperadas no documento impresso.onWrite()
: chamado para renderizar as páginas impressas em um arquivo a ser impresso. Esse método pode ser chamado uma ou mais vezes após cada chamada deonLayout()
.onFinish()
: chamado uma vez no final do processo de impressão. Se o aplicativo tiver tarefas de desmontagem única para realizar, execute-as aqui. A implementação deste método no adaptador não é necessária.
As seções a seguir descrevem como implementar os métodos de layout e gravação, que são essenciais para o funcionamento de um adaptador de impressão.
Observação: esses métodos do adaptador são chamados na linha de execução principal do app. Se você espera que a execução desses métodos na sua implementação leve um tempo significativo, implemente-os em uma linha de execução separada. Por exemplo, você pode encapsular o trabalho de layout ou impressão de documentos em objetos AsyncTask
separados.
Calcular informações de documentos de impressão
Em uma implementação da classe PrintDocumentAdapter
, o aplicativo precisa ser capaz de especificar o tipo de documento que está criando e calcular o número total de páginas para o trabalho de impressão, de acordo com as informações sobre o tamanho da página impressa.
A implementação do método onLayout()
no adaptador faz esses cálculos e fornece informações sobre a saída esperada do trabalho de impressão em uma classe PrintDocumentInfo
, incluindo o número de páginas e o tipo de conteúdo. O exemplo a seguir mostra uma implementação básica do método onLayout()
para um 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."); } }
A execução do método onLayout()
pode
ter três resultados: conclusão, cancelamento ou falha caso o cálculo do
layout não possa ser concluído. É preciso indicar um desses resultados chamando o método apropriado do objeto PrintDocumentAdapter.LayoutResultCallback
.
Observação:o parâmetro booleano do método
onLayoutFinished()
indica se o conteúdo do layout mudou ou não
desde a última solicitação. Definir esse parâmetro corretamente permite que o framework de impressão evite chamar desnecessariamente o método onWrite()
, essencialmente armazenando em cache o documento de impressão gravado anteriormente, melhorando o desempenho.
O principal trabalho do onLayout()
é
calcular o número de páginas que são esperadas como saída, considerando os atributos da impressora.
A forma como esse número é calculado depende muito de como o aplicativo dispõe as páginas para impressão. O exemplo de código a seguir mostra uma implementação em que o número de páginas é determinado pela orientação da impressão:
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); }
Gravar um arquivo de documento de impressão
Quando for necessário gravar uma saída de impressão em um arquivo, o framework de impressão do Android chamará o método onWrite()
da classe PrintDocumentAdapter
do seu aplicativo. Os parâmetros do método especificam quais páginas precisam ser
gravadas e o arquivo de saída a ser usado. Sua implementação desse método precisa renderizar cada página de conteúdo solicitada em um arquivo de documento PDF de várias páginas. Quando esse processo estiver concluído, chame o método onWriteFinished()
do objeto de callback.
Observação:o framework de impressão do Android pode chamar o método onWrite()
uma ou mais vezes a cada
chamada para onLayout()
. Por esse motivo, é importante definir o parâmetro booleano do método onLayoutFinished()
como false
quando o layout do conteúdo de impressão não foi modificado para evitar regravações desnecessárias do documento de impressão.
Observação:o parâmetro booleano do método
onLayoutFinished()
indica se o conteúdo do layout mudou ou não
desde a última solicitação. Definir esse parâmetro corretamente permite que o framework de impressão evite chamar desnecessariamente o método onLayout()
, essencialmente armazenando em cache o documento de impressão gravado anteriormente, melhorando o desempenho.
A amostra a seguir demonstra a mecânica básica desse processo usando a classe PrintedPdfDocument
para criar um arquivo 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); ... }
Esse exemplo delega a renderização do conteúdo da página PDF ao método drawPage()
, que será discutido na próxima seção.
Assim como no layout, a execução do método onWrite()
pode ter três resultados: conclusão, cancelamento ou falha caso o
conteúdo não possa ser gravado. É preciso indicar um desses resultados chamando o
método apropriado do objeto PrintDocumentAdapter.WriteResultCallback
.
Observação: a renderização de um documento para impressão pode ser uma operação que consome muitos recursos. Para
evitar o bloqueio da linha de execução principal da interface do usuário do seu aplicativo, considere
executar as operações de renderização e gravação da página em uma linha de execução separada, por exemplo,
em uma AsyncTask
.
Para mais informações sobre como trabalhar com linhas de execução como tarefas assíncronas,
consulte Processos
e linhas de execução.
Desenhar conteúdo de página PDF
Quando o aplicativo é impresso, ele precisa gerar um documento PDF e transmiti-lo ao
framework de impressão do Android para impressão. É possível usar qualquer biblioteca de geração de PDF para essa
finalidade. Esta lição mostra como usar a classe PrintedPdfDocument
para gerar páginas PDF a partir do seu conteúdo.
A classe PrintedPdfDocument
usa um objeto Canvas
para desenhar elementos em uma página PDF, de forma semelhante ao desenho em um layout de atividade. Você pode desenhar
elementos na página impressa usando os métodos de desenho Canvas
. O código de exemplo a seguir demonstra como desenhar alguns elementos simples em uma página de documentos PDF usando esses métodos:
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); }
Ao usar o Canvas
para desenhar em uma página PDF, os elementos são especificados em pontos, o que equivale a 1/72 de polegada. Use essa unidade de medida para especificar o tamanho
dos elementos na página. Para o posicionamento de elementos desenhados, o sistema de coordenadas começa em 0,0 no canto superior esquerdo da página.
Dica:embora o objeto Canvas
permita posicionar elementos de impressão na borda de um documento PDF, não é possível, para muitas impressoras, imprimir na borda de uma folha de papel física. Considere as bordas não imprimíveis da página ao
criar um documento de impressão com essa classe.