Para alguns aplicativos, como aplicativos de desenho, aplicativos de layout de página e outros aplicativos que se concentram em saída gráfica, criar belas páginas impressas é um recurso fundamental. Nesse caso, não basta para imprimir uma imagem ou um documento HTML. O resultado de impressão desses tipos de aplicativos exige controle preciso de tudo o que há 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 seu aplicativo requer mais de programação do que as abordagens discutidas anteriormente. Você precisa criar componentes que comunicar-se com a estrutura 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 se conectar com o gerenciador de impressão, criar um adaptador de impressão e criar conteúdo para impressão.
Conectar-se ao gerenciador de impressão
Quando o aplicativo 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 obter uma instância
da classe PrintManager
. Esta classe permite inicializar um trabalho de impressão
e iniciar o ciclo de impressão. O exemplo de código a seguir mostra como acessar 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
A implementação da classe do adaptador de impressão será discutida na próxima seção.
Observação:o último parâmetro na print()
usa um objeto PrintAttributes
. É possível usar esse parâmetro para
fornecer dicas sobre 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 que sejam
mais apropriado para o conteúdo sendo impresso, por exemplo, definindo a orientação como paisagem
ao imprimir uma foto 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 da processo de impressão. Esse processo exige que os usuários selecionem as impressoras e opções de impressão antes de criar um documento para impressão. Essas seleções podem influenciar o resultado final à medida que o usuário escolhe impressoras com diferentes recursos de saída, tamanhos de página ou orientações de página diferentes. À medida que essas seleções são feitas, o framework de impressão pede ao adaptador que disponha e gere uma imprimir documento, em preparação para o resultado final. Quando um usuário toca no botão de impressão, o framework transmite o documento de impressão final para um provedor de impressão para fins de saída. Durante a impressão os usuários podem optar por cancelar a ação de impressão, de modo que o adaptador de impressão também deve ouvir e reagir a solicitações de cancelamento.
A classe abstrata PrintDocumentAdapter
foi projetada para processar o
de impressão, que tem quatro métodos principais de callback. Você precisa implementar esses métodos
no adaptador de impressão para interagir corretamente com o framework de impressão:
onStart()
(ligação feita uma vez no início do processo de impressão. Caso sua inscrição tenha tarefas de preparação únicas para executar, como conseguir um snapshot dos dados a serem impressos, execute-os aqui. Implementação esse método no adaptador não é necessário.onLayout()
: chamado sempre que um o usuário altera uma configuração de impressão que afeta o resultado, como outro tamanho de página, ou orientação de página, permitindo que seu aplicativo calcule o layout da páginas a serem impressas. No mínimo, esse método precisa retornar quantas páginas são esperadas no documento impresso.onWrite()
: chamado para renderizar na impressão páginas em um arquivo a ser impresso. Esse método pode ser chamado uma ou mais vezes após cadaonLayout()
.onFinish()
(chamada uma vez no final) do processo de impressão. Se seu aplicativo tiver alguma tarefa única de eliminação para executar, executá-las 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 do 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 em sua implementação tome uma quantidade significativa de
implemente-os para que sejam executados em uma linha de execução separada. Por exemplo, é possível encapsular
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
, a classe
aplicativo deve ser capaz de especificar o tipo de documento que está criando e calcular o total
número de páginas para o trabalho de impressão, com base nas informações sobre o tamanho da página impressa.
A implementação do método onLayout()
em
o 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
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
têm três resultados: conclusão, cancelamento ou falha caso o cálculo do
não pode ser concluído. Você deve indicar um desses resultados chamando o método
do objeto PrintDocumentAdapter.LayoutResultCallback
.
Observação: o parâmetro booleano da
O método onLayoutFinished()
indica se o conteúdo do layout foi realmente alterado 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()
,
armazenando em cache o documento impresso previamente gravado, o que melhora o desempenho.
O principal trabalho do onLayout()
é
calcular o número de páginas esperadas como saída, considerando os atributos da impressora.
A forma como você calcula esse número depende muito de como seu aplicativo organiza 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 é
determinada 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 devem ser
gravado e o arquivo de saída a ser usado. A implementação desse método deve então renderizar cada
página solicitada de conteúdo em um arquivo de documento PDF com várias páginas. Quando esse processo for 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 para cada
para onLayout()
. Por isso,
é importante definir o parâmetro booleano
Método onLayoutFinished()
para false
quando o layout do conteúdo de impressão não muda.
para evitar regravações desnecessárias nos documentos impressos.
Observação: o parâmetro booleano da
O método onLayoutFinished()
indica se o conteúdo do layout foi realmente alterado 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()
,
armazenando em cache o documento impresso previamente gravado, o que melhora 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); ... }
Este exemplo delega a renderização do conteúdo da página PDF à drawPage()
, que será discutido na próxima seção.
Assim como no layout, a execução de onWrite()
pode ter três resultados: conclusão, cancelamento ou falha caso o
não é possível gravar o conteúdo. Você deve 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. Em
para evitar o bloqueio da linha de execução principal da interface do usuário do aplicativo, considere
executando as operações de renderização e gravação da página em uma linha de execução separada, por exemplo
em um AsyncTask
.
Para mais informações sobre como trabalhar com linhas de execução como tarefas assíncronas,
consulte Processos
e Threads.
Desenhar conteúdo de página PDF
Quando seu aplicativo for impresso, ele deverá gerar um documento PDF e passá-lo para
o framework de impressão do Android para impressão. Você pode usar qualquer biblioteca de geração de PDF para
do propósito da tecnologia. 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 Canvas
.
para desenhar elementos em uma página PDF, semelhante ao desenho em um layout de atividade. Você pode desenhar
elementos na página impressa usando os métodos de desenho Canvas
. O seguinte
exemplo de código demonstra como desenhar alguns elementos simples em uma página de documento PDF usando estes
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 Canvas
para desenhar em uma página PDF, os elementos são especificados em
de ponta, que é 1/72 de polegada. Use essa unidade de medida para especificar o tamanho
de elementos da 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 colocar a impressão
elementos na borda de um documento PDF, muitas impressoras não conseguem
uma folha de papel física. Considere as bordas da página que não podem ser impressas ao
a criar um documento de impressão com esta classe.