Para algunas aplicaciones, como las aplicaciones de dibujo, las aplicaciones de diseño de páginas y otras aplicaciones que se centran en salida gráfica, la creación de hermosas páginas impresas es una función clave. En este caso, no es suficiente para imprimir una imagen o un documento HTML. La salida de impresión para estos tipos de aplicaciones requiere control preciso de todo el contenido de una página, incluidas las fuentes, el flujo de texto, los saltos de página, encabezados, pies de página y elementos gráficos.
Crear una salida de impresión completamente personalizada para tu aplicación requiere más más inversión en programación que los enfoques antes analizados. Debes compilar componentes que comunicarse con el marco de impresión, ajustar la configuración de la impresora, dibujar los elementos de la página y administrar la impresión en varias páginas.
Esta lección te muestra cómo conectarte con el administrador de impresión, crear un adaptador de impresión y crear contenido para imprimir.
Cómo conectarte al administrador de impresión
Cuando tu aplicación administra directamente el proceso de impresión, el primer paso después de recibir un
de impresión de tu usuario es conectarte al framework de impresión de Android y obtener una instancia
de la clase PrintManager
Esta clase te permite inicializar un trabajo de impresión.
y comenzar el ciclo de vida de impresión. En el siguiente ejemplo de código, se muestra cómo obtener el administrador de impresión
e iniciar el proceso de impresión.
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); // }
En el código de ejemplo anterior, se muestra cómo nombrar un trabajo de impresión y configurar una instancia de la clase PrintDocumentAdapter
que controle los pasos del ciclo de vida de impresión. El
implementación de la clase de adaptador de impresión se analiza en la siguiente sección.
Nota: El último parámetro de print()
toma un objeto PrintAttributes
. Puedes usar este parámetro para
proporcionar sugerencias para el marco de trabajo de impresión y las opciones preestablecidas basadas en el ciclo de impresión anterior,
lo que mejora la experiencia del usuario. También puedes usar este parámetro para configurar las opciones que son
sea más apropiada para el contenido que se está imprimiendo, como establecer la orientación en horizontal.
al imprimir una foto con esa orientación.
Cómo crear un adaptador de impresión
Un adaptador de impresión interactúa con el framework de impresión de Android y controla los pasos de la proceso de impresión. Este proceso requiere que los usuarios seleccionen impresoras y opciones de impresión antes de crear un documento para imprimirlo. Estas selecciones pueden influir en el resultado final a medida que el usuario elige. con diferentes funciones de salida, diferentes tamaños de página o diferentes orientaciones de página. A medida que se realizan estas selecciones, el framework de impresión le pide a tu adaptador que diseñe y genere una imprimir el documento para preparar el resultado final. Una vez que el usuario presiona el botón de impresión, el marco de trabajo toma el documento de impresión final y lo pasa a un proveedor de impresión para su salida. Durante la impresión de impresión, los usuarios pueden elegir cancelar la acción de impresión, por lo que el adaptador de impresión también debe detectar y reaccionar a las solicitudes de cancelación.
La clase abstracta PrintDocumentAdapter
está diseñada para controlar las
de impresión, que tiene cuatro métodos principales de devolución de llamada. Debes implementar estos métodos
en tu adaptador de impresión para interactuar correctamente con el marco de trabajo de impresión:
onStart()
: Se llama una vez a las inicio del proceso de impresión. Si tu aplicación tiene tareas de preparación únicas realizar, como obtener una instantánea de los datos que se imprimirán, ejecútalos aquí. Implementación este método en el adaptador no es obligatorio.onLayout()
: Se llama cada vez que un el usuario cambia una configuración de impresión que afecta el resultado, como un tamaño de página diferente o la orientación de la página, lo que le da a tu aplicación la oportunidad de procesar el diseño de la las páginas que se imprimirán. Como mínimo, este método debe mostrar la cantidad de páginas esperadas. del documento impreso.onWrite()
: Se lo llama para renderizar el contenido impreso. las páginas en un archivo para imprimir. Se puede llamar a este método una o más veces después de cadaonLayout()
llamada.onFinish()
: Se llama una vez al final del proceso de impresión. Si tu aplicación tiene que realizar alguna tarea de eliminación única, ejecutarlas aquí. No es obligatorio implementar este método en tu adaptador.
En las siguientes secciones, se describe cómo implementar los métodos de diseño y escritura, que son que son esenciales para el funcionamiento de un adaptador de impresión.
Nota: Se llama a estos métodos del adaptador en el subproceso principal de tu aplicación. Si
esperas que la ejecución de estos métodos en tu implementación
demore significativamente
impleméntalas para que se ejecuten en un subproceso independiente. Por ejemplo, puedes encapsular el
trabajo de escritura de documentos de diseño o impresión en objetos AsyncTask
separados
Cómo calcular la información del documento para imprimir
En una implementación de la clase PrintDocumentAdapter
, tu archivo
la aplicación debe poder especificar el tipo de documento que está creando y calcular el total
la cantidad de páginas del trabajo de impresión, según la información del tamaño de la página impresa.
La implementación del método onLayout()
en
el adaptador realiza estos cálculos y proporciona información sobre el resultado esperado de la
de impresión en una clase PrintDocumentInfo
, incluida la cantidad de páginas y
el tipo de contenido. En el siguiente ejemplo de código, se muestra una implementación básica del método onLayout()
para 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."); } }
La ejecución del método onLayout()
puede
tienen tres resultados: finalización, cancelación o falla en caso de que el cálculo del
no se puede completar el diseño. Debes indicar uno de estos resultados llamando al
del objeto PrintDocumentAdapter.LayoutResultCallback
.
Nota: El parámetro booleano del
El método onLayoutFinished()
indica si el contenido del diseño cambió o no.
desde la última solicitud. Configurar correctamente este parámetro permite que el marco de trabajo de impresión evite
llamar innecesariamente al método onWrite()
,
para almacenar en caché el documento de impresión
que se escribió anteriormente y mejorar el rendimiento.
El trabajo principal de onLayout()
es
Calcular la cantidad de páginas que se esperan como resultado según los atributos de la impresora.
La forma en que calculas este número depende en gran medida de cómo tu aplicación diseña las páginas para
imprimir. En el siguiente ejemplo de código, se muestra una implementación en la que la cantidad de páginas es de
determinado por la orientación de impresión:
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); }
Cómo escribir un archivo de documento impreso
Cuando llega el momento de escribir la salida de impresión en un archivo, el marco de trabajo de impresión de Android llama al método onWrite()
de la clase PrintDocumentAdapter
de tu aplicación. Los parámetros del método especifican qué páginas deben
y el archivo de salida que se usará. Tu implementación de este método debe renderizar cada
la página de contenido solicitada a un archivo de documento PDF de varias páginas. Cuando se completa este proceso,
Llama al método onWriteFinished()
del objeto de devolución de llamada.
Nota: El framework de impresión de Android puede llamar al método onWrite()
una o más veces por cada
llamada a onLayout()
. Por este motivo, es
importante establecer el parámetro booleano de
Usa el método onLayoutFinished()
en false
cuando no se haya cambiado el diseño del contenido de impresión.
para evitar reescrituras innecesarias del documento impreso.
Nota: El parámetro booleano del
El método onLayoutFinished()
indica si el contenido del diseño cambió o no.
desde la última solicitud. Configurar correctamente este parámetro permite que el marco de trabajo de impresión evite
llamar innecesariamente al método onLayout()
,
para almacenar en caché el documento de impresión
que se escribió anteriormente y mejorar el rendimiento.
En el siguiente ejemplo, se muestra la mecánica básica de este proceso con la clase PrintedPdfDocument
para crear un archivo 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); ... }
En este ejemplo, se delega el procesamiento del contenido de la página PDF a drawPage()
que se analiza en la siguiente sección.
Al igual que con el diseño, la ejecución de onWrite()
puede tener tres resultados: finalización, cancelación o falla en caso de que el
no se puede escribir el contenido. Debes indicar uno de estos resultados llamando al
método apropiado del objeto PrintDocumentAdapter.WriteResultCallback
Nota: La operación de procesamiento de un documento para imprimir puede requerir muchos recursos. En
Para evitar bloquear el subproceso principal de la interfaz de usuario de tu aplicación, debes considerar
realizar las operaciones de procesamiento y escritura de la página en un subproceso separado, por ejemplo,
en un objeto AsyncTask
.
Para obtener más información sobre cómo trabajar con subprocesos de ejecución como tareas asíncronas,
consulta Procesos
y subprocesos.
Cómo dibujar contenido de la página PDF
Cuando tu aplicación imprima, deberá generar un documento PDF y pasarlo a
el framework de impresión de Android para imprimir. Puedes usar cualquier biblioteca de generación de PDF para esto
que no tiene ningún propósito específico. En esta lección, se muestra cómo usar la clase PrintedPdfDocument
para generar páginas PDF a partir de tu contenido.
La clase PrintedPdfDocument
usa un Canvas
.
para dibujar elementos en una página PDF, de manera similar a dibujar en un diseño de actividad. Puedes dibujar
elementos de la página impresa con los métodos de dibujo Canvas
Lo siguiente
código de ejemplo que muestra cómo dibujar algunos elementos simples en la página de un documento PDF usando estos
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); }
Cuando se usa Canvas
para dibujar en una página PDF, los elementos se especifican en
puntos, que es 1/72 de pulgada. Asegúrate de utilizar esta unidad de medida para especificar el tamaño
de elementos de la página. Para el posicionamiento de elementos dibujados, el sistema de coordenadas comienza en 0,0
en la esquina superior izquierda de la página.
Nota: Si bien el objeto Canvas
te permite colocar elementos de impresión,
al borde de un documento PDF, muchas impresoras no pueden imprimir en el borde de un
hoja de papel física. Asegúrate de tener en cuenta los bordes no imprimibles de la página cuando
crearás un documento impreso con esta clase.