Drukowanie dokumentów niestandardowych

W przypadku niektórych aplikacji, np. do rysowania, układów strony i innych, które skupiają się grafiki, tworzenie atrakcyjnych drukowanych stron to kluczowa funkcja. W tym przypadku nie wystarczy, w celu wydrukowania obrazu lub dokumentu HTML. Drukowanie w aplikacjach tego typu wymaga precyzyjna kontrola wszystkich elementów strony, w tym czcionek, przepływu tekstu, podziałów stron, nagłówki, stopki i elementy graficzne.

Tworzenie materiałów wyjściowych, które są w pełni dostosowane do Twojej aplikacji, wymaga więcej niż w omawianych wcześniej podejściach. Musisz tworzyć komponenty, które komunikowanie się z platformą druku, dostosowywanie ustawień drukarki, rysowanie elementów stron i zarządzać drukowaniem na wielu stronach.

Z tej lekcji dowiesz się, jak połączyć się z menedżerem drukowania, utworzyć adapter wydruku tworzyć treści do druku.

Jeśli aplikacja bezpośrednio zarządza procesem drukowania, pierwszy krok po otrzymaniu żądanie drukowania od użytkownika to połączenie się z platformą drukowania na Androidzie i pozyskanie instancji. klasy PrintManager. Ta klasa umożliwia inicjowanie zadania drukowania i rozpocząć cykl drukowania. Ten przykładowy kod pokazuje, jak pobrać menedżera drukowania i rozpocznij 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ć instancję klasy PrintDocumentAdapter, która obsługuje kroki cyklu życia drukowania. implementacji klasy adaptera wydruku zostało omówione w następnej sekcji.

Uwaga: ostatni parametr w tagu print() przyjmuje obiekt PrintAttributes. Za pomocą tego parametru udostępnia wskazówki dotyczące platformy drukowania i gotowych opcji dostępnych na podstawie poprzedniego cyklu i zwiększa wygodę użytkowników. Możesz także użyć tego parametru, aby ustawić opcje, które są lepiej dostosować do drukowanej treści, na przykład ustawić orientację poziomą. podczas drukowania zdjęcia w tej orientacji.

Adapter wydruku współpracuje z platformą druku Androida i obsługuje czynności proces drukowania. Ten proces wymaga, aby przed utworzeniem raportu użytkownicy wybrali drukarki i opcje drukowania dokument do wydrukowania. Te wybory mogą mieć wpływ na ostateczny wynik, w zależności od wyboru użytkownika drukarki o różnych możliwościach wyjściowych, różnych rozmiarach lub orientacjach stron. Po dokonaniu wyboru system druku prosi adapter o ułożenie i wygenerowanie pliku wydrukować dokument w ramach przygotowań do ostatecznego wydrukowania. Gdy użytkownik kliknie przycisk drukowania, pobiera ostateczną wydrukowaną wersję dokumentu i przekazuje go dostawcy odbitek w celu wydrukowania. W trakcie drukowania użytkownicy mogą anulować czynność drukowania, więc adapter musi też nasłuchiwać i reagować na prośby o anulowanie.

Klasa abstrakcyjna PrintDocumentAdapter jest przeznaczona do obsługi cyklu życia drukowania, który udostępnia 4 główne metody wywołań zwrotnych. Musisz wdrożyć te metody za pomocą adaptera wydruku, aby zapewnić prawidłową współpracę z platformą druku:

  • onStart() – nawiązano jedno połączenie z numeru na początku procesu drukowania. Jeśli zgłoszenie zawiera zadania jednorazowe przygotowawcze, np. aby pobrać migawkę danych do wydrukowania, wykonaj je tutaj. Wdrożenie nie jest wymagana w adapterze.
  • onLayout() – połączenie za każdym razem, gdy użytkownik zmienia ustawienie drukowania, które ma wpływ na format wyjściowy, np. inny rozmiar strony, lub orientację strony, dzięki czemu aplikacja może obliczyć układ strony do wydrukowania. Ta metoda musi zwracać co najmniej liczbę oczekiwanych stron w wydrukowanym dokumencie.
  • onWrite() – wywołany w celu renderowania do pliku, który ma zostać wydrukowany. Ta metoda może być wywoływana co najmniej raz po każdej onLayout() połączenie.
  • onFinish() – wywołanie raz na końcu procesu drukowania. Jeśli Twoja aplikacja ma do wykonania jednorazowe zadania demontażu, wykonaj je tutaj. Stosowanie tej metody w przejściu nie jest wymagane.

W sekcjach poniżej opisujemy, jak wdrożyć metody układu i zapisu, które są ma kluczowe znaczenie dla działania adaptera wydruku.

Uwaga: te metody adaptera są wywoływane w głównym wątku aplikacji. Jeśli spodziewasz się, że wykonanie tych metod w Twojej implementacji należy wdrożyć je w osobnym wątku. Możesz na przykład umieścić parametr układy lub drukowane dokumenty działają w oddzielnych obiektach AsyncTask.

Informacje o dokumencie drukowanego Compute

W ramach implementacji klasy PrintDocumentAdapter element aplikacja musi być w stanie określić typ tworzonego dokumentu i obliczyć łączną liczba stron zadania drukowania oraz informacje o rozmiarze drukowanej strony. Implementacja metody onLayout() w: przeprowadza te obliczenia i dostarcza informacje o oczekiwanych wynikach zadania drukowania w ramach zajęć PrintDocumentInfo, w tym liczbę stron i typu treści. Poniższy przykładowy kod ilustruje podstawową implementację metody onLayout() dla 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.");
    }
}

Wykonanie metody onLayout() może mogą mieć 3 wyniki: ukończenie, anulowanie lub niepowodzenie w przypadku, gdy obliczenie nie można ukończyć układu. Musisz wskazać jeden z tych wyników, wywołując odpowiednie polecenie obiektu PrintDocumentAdapter.LayoutResultCallback.

Uwaga: parametr logiczny funkcji Metoda onLayoutFinished() wskazuje, czy treść układu faktycznie uległa zmianie. od czasu ostatniego żądania. Ustawienie tego parametru prawidłowo pozwala na unikanie niepotrzebnie wywoływać metodę onWrite(), zasadniczo zapisanych wcześniej dokumentów w pamięci podręcznej, co zwiększa wydajność.

Główne dzieło onLayout() to obliczanie oczekiwanej liczby stron jako danych wyjściowych na podstawie atrybutów drukarki. Sposób obliczania tej liczby w dużym stopniu zależy od tego, jak aplikacja wyświetla strony drukowania. Następujący przykładowy kod ilustruje implementację, w której liczba stron wynosi zależy od orientacji:

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 dokumentu do wydruku

Gdy nadejdzie pora na zapisanie danych wyjściowych w pliku, platforma drukowania na Androidzie wywołuje metodę onWrite() klasy PrintDocumentAdapter aplikacji. Parametry metody określają, które strony oraz plik wyjściowy do użycia. Implementacja tej metody musi następnie renderować każdy żądanej strony treści do pliku wielostronicowego dokumentu PDF. Po zakończeniu tego procesu możesz: metody onWriteFinished() obiektu wywołania zwrotnego.

Uwaga: platforma drukowana na Androidzie może wywoływać metodę onWrite() co najmniej raz na każde połączenie z numerem onLayout(). Z tego powodu ważne jest, aby ustawić wartość logiczną parametru onLayoutFinished() na false, gdy układ treści drukowanych nie uległ zmianie. aby uniknąć zbędnych przeredagowania drukowanego dokumentu.

Uwaga: parametr logiczny funkcji Metoda onLayoutFinished() wskazuje, czy treść układu faktycznie uległa zmianie. od czasu ostatniego żądania. Ustawienie tego parametru prawidłowo pozwala na unikanie niepotrzebnie wywoływać metodę onLayout(), zasadniczo zapisanych wcześniej dokumentów w pamięci podręcznej, co zwiększa wydajność.

Poniższy przykład przedstawia podstawową mechanikę tego procesu z użyciem 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 przekazuje renderowanie zawartości strony PDF usłudze drawPage() Metodę tę omówiono w następnej sekcji.

Tak jak w przypadku układu, wykonanie kodu onWrite() może mieć 3 wyniki: ukończenie, anulowanie lub niepowodzenie, jeśli nie można napisać. Musisz wskazać jeden z tych wyników, wywołując metodę odpowiednią metodę obiektu PrintDocumentAdapter.WriteResultCallback.

Uwaga: wyrenderowanie dokumentu do wydrukowania może wymagać zasobów. W aby uniknąć blokowania głównego wątku interfejsu aplikacji, rozważ wykonując operacje renderowania i zapisywania stron w osobnym wątku, na przykład w: AsyncTask. Więcej informacji o pracy z wątkami wykonania, takimi jak zadania asynchroniczne, znajdziesz w artykule Więcej informacji: Procesy i Threads.

Zawartość strony z rysunkami w formacie PDF

Po wydrukowaniu aplikacji aplikacja musi wygenerować dokument PDF i przekazać go do czyli platformy do druku na Androidzie. Do tego celu można użyć dowolnej biblioteki generowania plików PDF. cel. Z tej lekcji dowiesz się, jak korzystać z zajęć PrintedPdfDocument do generowania stron PDF na podstawie treści.

Klasa PrintedPdfDocument używa Canvas do rysowania elementów na stronie PDF, podobnie jak w układzie aktywności. Możesz rysować elementów na wydrukowanej stronie za pomocą metody rysowania Canvas. Poniżej przykładowy kod pokazujący, jak narysować proste elementy na stronie dokumentu PDF za pomocą metody:

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 do rysowania na stronie w pliku PDF używasz Canvas, elementy są określone w czyli 1/72 cala. Pamiętaj, aby użyć tej jednostki miary przy określaniu rozmiaru elementów na stronie. Przy określaniu pozycji rysowanych elementów układ współrzędnych zaczyna się od 0,0 w lewym górnym rogu strony.

Wskazówka: obiekt Canvas umożliwia umieszczenie wydruku elementów na krawędzi dokumentu PDF, wiele drukarek nie może wydrukować kartkę papieru. Podczas tworzenia arkusza, bierz pod uwagę krawędzie, których nie można wydrukować utworzysz dokument do wydrukowania w ramach tych zajęć.