W przypadku niektórych aplikacji, takich jak aplikacje do rysowania, układy stron i inne aplikacje, których główną funkcją jest grafika, tworzenie atrakcyjnych stron drukowanych jest kluczową funkcją. W takim przypadku nie wystarczy wydrukować obraz lub dokument HTML. Drukowanie w takich aplikacjach wymaga dokładnej kontroli nad wszystkim, co znajduje się na stronie, w tym nad czcionkami, przepływem tekstu, podziałami stron, nagłówkami, stopkami i elementami graficznymi.
Tworzenie materiałów drukowanych, które są w pełni dostosowane do danej aplikacji, wymaga więcej inwestycji w programowanie niż omówione wcześniej metody. Musisz utworzyć komponenty, które komunikują się z platformą drukowania, dostosowują się do ustawień drukarki, rysują elementy stron i zarządzają drukowaniem na wielu stronach.
Z tej lekcji dowiesz się, jak nawiązać połączenie z menedżerem drukowania, utworzyć adapter wydruku i tworzyć treści do drukowania.
Połącz się z menedżerem drukowania
Gdy Twoja aplikacja bezpośrednio zarządza procesem drukowania, pierwszym krokiem po otrzymaniu żądania wydruku od użytkownika jest połączenie z platformą Android Print i uzyskanie instancji klasy PrintManager
. Ta klasa umożliwia zainicjowanie zadania drukowania i rozpoczęcie cyklu życia drukowania. Poniższy przykładowy kod pokazuje, jak pobrać menedżera drukowania i rozpocząć proces drukowania.
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); // }
Przykładowy kod powyżej pokazuje, jak nazwać zadanie drukowania i ustawić wystąpienie klasy PrintDocumentAdapter
, która obsługuje kroki cyklu życia drukowania. Implementacja klasy adaptera wydruku została omówiona w następnej sekcji.
Uwaga: ostatni parametr w metodzie print()
pobiera obiekt PrintAttributes
. Możesz używać tego parametru, aby podawać wskazówki dotyczące platformy drukowania i gotowych opcji na podstawie poprzedniego cyklu drukowania, aby poprawić wygodę użytkowników. Za pomocą tego parametru możesz też ustawić opcje bardziej odpowiednie do drukowanych treści, np. ustawić orientację poziomą w przypadku drukowania zdjęcia w tej orientacji.
Tworzenie adaptera wydruku
Adapter wydruku współpracuje z platformą drukowania w Androidzie i wykonuje kroki procesu drukowania. Ten proces wymaga, aby przed utworzeniem dokumentu do wydrukowania użytkownicy wybrali drukarki i opcje drukowania. Wybory te mogą mieć wpływ na wynik końcowy, gdy użytkownik wybierze drukarki o różnych możliwościach pracy, różnych rozmiarach stron lub orientacjach stron. Gdy dokonujemy wyboru, platforma drukowania prosi adapter o ułożenie i wygenerowanie dokumentu do wydruku, aby przygotować się do ostatecznego wyniku. Gdy użytkownik kliknie przycisk drukowania, platforma przekazuje ostateczną wersję dokumentu do dostawcy wydruku, aby ją otrzymać. Podczas procesu drukowania użytkownicy mogą anulować działanie drukowania, więc adapter wydruku również musi nasłuchiwać próśb o anulowanie drukowania i na nie zareagować.
Klasa abstrakcyjna PrintDocumentAdapter
jest przeznaczona do obsługi cyklu życia drukowania, który ma 4 główne metody wywołania zwrotnego. Aby prawidłowo współdziałać z platformą drukowania, musisz zastosować w adapterze wydruku te metody:
onStart()
– wywoływane raz na początku procesu drukowania. Jeśli Twoja aplikacja zawiera jednorazowe zadania przygotowawcze, takie jak uzyskanie zrzutu danych do wydrukowania, wykonaj je tutaj. Implementacja tej metody w adapterze nie jest wymagana.onLayout()
– wywoływane za każdym razem, gdy użytkownik zmieni ustawienie drukowania, które ma wpływ na dane wyjściowe, na przykład inny rozmiar lub orientacja strony, umożliwiając aplikacji obliczenie układu stron do wydrukowania. Ta metoda musi zwracać co najmniej liczbę oczekiwanych stron w wydrukowanym dokumencie.onWrite()
– wywoływane do renderowania drukowanych stron w pliku do wydrukowania. Ta metoda może być wywoływana co najmniej raz po każdym wywołaniuonLayout()
.onFinish()
– wywoływane raz na końcu procesu drukowania. Jeśli Twoja aplikacja ma do wykonania jednorazowe zadania odbudowy, wykonaj je tutaj. Wdrażanie tej metody w adapterze nie jest wymagane.
W kolejnych sekcjach opisano, jak wdrożyć metody układu i zapisu, które mają kluczowe znaczenie dla działania adaptera wydruku.
Uwaga: te metody adaptera są wywoływane w wątku głównym aplikacji. Jeśli spodziewasz się, że wykonanie tych metod w Twojej implementacji zajmie dużo czasu, zaimplementuj je tak, aby zostały wykonane w oddzielnym wątku. Możesz na przykład umieścić układ lub wydrukować dokument w osobnych obiektach AsyncTask
.
Informacje o dokumencie Compute Print
W ramach implementacji klasy PrintDocumentAdapter
aplikacja musi mieć możliwość określenia typu tworzonego dokumentu oraz obliczenia łącznej liczby stron na potrzeby zadania drukowania z uwzględnieniem informacji o rozmiarze drukowanej strony.
Implementacja metody onLayout()
w adapterze przeprowadza te obliczenia i dostarcza informacji o oczekiwanych wynikach zadania drukowania w klasie PrintDocumentInfo
, w tym o liczbie stron i typie treści. Poniższy przykładowy kod przedstawia podstawową implementację metody onLayout()
w obrębie typu 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."); } }
Jeśli nie można ukończyć obliczenia układu, wykonanie metody onLayout()
może mieć 3 wyniki: zakończenie, anulowanie lub niepowodzenie. Musisz wskazać jeden z tych wyników, wywołując odpowiednią metodę obiektu PrintDocumentAdapter.LayoutResultCallback
.
Uwaga: parametr logiczny metody onLayoutFinished()
wskazuje, czy treść układu rzeczywiście się zmieniła od ostatniego żądania. Prawidłowe ustawienie tego parametru pozwala platformie drukowania uniknąć niepotrzebnego wywoływania metody onWrite()
, zasadniczo buforowania wcześniej napisanego dokumentu wydruku i zwiększenia wydajności.
Podstawowym zajęciem w onLayout()
jest obliczanie liczby stron, których oczekiwany wygląd zależy od atrybutów drukarki.
Sposób obliczania tej liczby zależy w dużym stopniu od tego, jak aplikacja rozplanuje strony do drukowania. Poniższy przykładowy kod przedstawia implementację, w której liczba stron jest określana na podstawie orientacji wydruku:
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); }
Zapisywanie pliku drukowanego dokumentu
Gdy nadejdzie czas na zapisanie danych wyjściowych wydruku do pliku, platforma drukowania Androida wywołuje metodę onWrite()
klasy PrintDocumentAdapter
aplikacji. Parametry metody określają, które strony powinny zostać zapisane, i który ma zostać użyty w pliku wyjściowym. Implementacja tej metody musi następnie wyrenderować każdą żądaną stronę treści w postaci wielostronicowego pliku PDF. Po zakończeniu tego procesu będziesz wywoływać metodę onWriteFinished()
obiektu wywołania zwrotnego.
Uwaga: platforma Android Print może wywoływać metodę onWrite()
co najmniej raz przy każdym wywołaniu onLayout()
. Dlatego ważne jest, aby ustawić parametr logiczny metody onLayoutFinished()
na false
, gdy układ treści drukowanej nie uległ zmianie, aby uniknąć niepotrzebnego przepisywania dokumentu w wersji drukowanej.
Uwaga: parametr logiczny metody onLayoutFinished()
wskazuje, czy treść układu rzeczywiście się zmieniła od ostatniego żądania. Prawidłowe ustawienie tego parametru pozwala platformie drukowania uniknąć niepotrzebnego wywoływania metody onLayout()
, zasadniczo buforowania wcześniej napisanego dokumentu wydruku i zwiększenia wydajności.
Poniższy przykład przedstawia podstawową mechanikę tego procesu przy użyciu klasy PrintedPdfDocument
do utworzenia pliku 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); ... }
Ten przykład deleguje renderowanie zawartości strony PDF do metody drawPage()
, która została omówiona w następnej sekcji.
Podobnie jak w przypadku układu, gdy nie można zapisać treści, wykonanie metody onWrite()
może mieć 3 następujące wyniki: zakończenie, anulowanie lub niepowodzenie. Musisz wskazać jeden z tych wyników, wywołując odpowiednią metodę obiektu PrintDocumentAdapter.WriteResultCallback
.
Uwaga: renderowanie dokumentu do wydrukowania może wymagać znacznych zasobów. Aby uniknąć zablokowania głównego wątku interfejsu aplikacji, przeprowadź operacje renderowania i pisania strony w osobnym wątku, np. w interfejsie AsyncTask
.
Więcej informacji o pracy z wątkami wykonywania, takimi jak zadania asynchroniczne, znajdziesz w artykule Procesy i wątki.
Zawartość strony z rysunkiem w pliku PDF
Gdy aplikacja zostanie wydrukowana, musi wygenerować dokument PDF i przekazać go do platformy drukowania w Androidzie w celu drukowania. W tym celu możesz użyć dowolnej biblioteki do generowania plików PDF. Z tej lekcji dowiesz się, jak używać zajęć PrintedPdfDocument
do generowania stron w formacie PDF na podstawie swoich treści.
Klasa PrintedPdfDocument
używa obiektu Canvas
do rysowania elementów na stronie pliku PDF, podobnie jak w przypadku układu aktywności. Możesz rysować elementy na wydrukowanej stronie za pomocą metod rysowania Canvas
. Poniższy przykładowy kod pokazuje, jak rysować proste elementy na stronie dokumentu PDF przy użyciu tych metod:
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); }
Jeśli używasz elementu Canvas
do rysowania na stronie PDF, elementy są określone w punktach, co wynosi 1/72 cala. Pamiętaj, by używać tej jednostki miary do określania rozmiaru elementów na stronie. Do ustalania pozycji rysowanych elementów układ współrzędnych zaczyna się od 0,0 w lewym górnym rogu strony.
Wskazówka: chociaż obiekt Canvas
umożliwia umieszczanie elementów wydruku na krawędzi dokumentu PDF, wiele drukarek nie daje się wydrukować do krawędzi papieru. Podczas tworzenia dokumentu do wydruku z użyciem tej klasy weź pod uwagę krawędzie strony, których nie można wydrukować.