Печать пользовательских документов

Для некоторых приложений, таких как графические редакторы, приложения макетов страниц и других приложений, которые сосредоточились на графическом выводе, создание красивых отпечатанных страниц является ключевой функцией. В этом случае, не достаточно напечатать изображение или HTML документ. Печать для таких приложений требует точного контроля всего, что происходит на странице, включая шрифты, текстовый поток, разрывы страниц, заголовки, колонтитулы и графические элементы.

Вывод на печать, который полностью настроен для вашего приложения, требует больше программирования, чем рассмотренные в предыдущих уроках подходы. Вы должны создавать компоненты, которые взаимодействуют с библиотекой печати, подстраиваются под настройки принтера, рисуют элементы страницы, и управляют печатью нескольких страниц.

Этот урок покажет вам, как вам связаться с менеджером печати, создать адаптер печати и создать контент для печати.

Когда приложение управляет процессом печати непосредственно, первый шаг после получения запроса на печать от вашего пользователя является подключение к платформе печати Android, и получение экземпляра PrintManager класса. Этот класс позволяет инициализировать задание и начать жизненный цикл печати. В следующем примере кода показано, как получить менеджер печати и запустить процесс печати.

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); //
}

В примере кода выше демонстрируется, как задать название для задания печати, и установить экземпляр PrintDocumentAdapter класса, который обрабатывает следующие шаги жизненного цикла печати. Реализация класса адаптера печати обсуждается в следующем разделе.

Примечание: Последний параметр в print() метода принимает PrintAttributes объект. Вы можете использовать этот параметр, чтобы обеспечить дополнительные настройки для платформы печати и предварительно заданные варианты настроек, основанные на предыдущем цикле печати, тем самым улучшая пользовательский опыт. Вы также можете использовать этот параметр для задания параметров, которые больше подходят для печати данного контента, такие как настройка ориентации печати фотографии, в зависимости от ориентации самой фотографии.

Адаптер печати взаимодействует с платформой печати Android и обрабатывает этапы процесса печати. Этот процесс требует, чтобы пользователь выбрал принтер и параметры печати, прежде чем вы создадите документ для печати. Эти настройки могут повлиять на конечный результат, так как пользователь может выбирать принтеры с разными возможностями, разными размерами страниц, или выбрать ориентацию страницы, не такую как вы предполагали. Как только настройки выбраны, платформа печати просит адаптер печати создать макет и сформировать документ для печати, в качестве подготовки к конечной стадии печати. После того, как пользователь нажимает кнопку печать, платформа принимает окончательный документ для печати, и передает его поставщику сервиса печати. В процессе печати, пользователи могут выбрать отмену действия печати, поэтому ваш адаптер печати должен также прослушивать и реагировать на запросы отмены.

PrintDocumentAdapter абстрактный класс, предназначенный для обработки жизненного цикла печати, который состоит из четырех основных методов обратного вызова. Вы должна реализовать эти методы вашего адаптера печати, чтобы должным образом взаимодействовать с платформой печати Android.:

  • onStart() - Вызывается один раз в начале процесса печати. Если ваше приложение имеет какие-либо подготовительные задачи, такие как получение снимка данных для печати, то выполнять их нужно здесь. Реализация данного метода не обязательна.
  • onLayout() - Вызывается каждый раз, когда пользователь изменяет параметры печати, который влияют на вывод, например, выбрал страницу с другим размером, или изменил ориентацию страницы, давая вашему приложению возможность перестроить макет страницы, которую вы собираетесь печать. Как минимум, этот метод должен возвращать количество ожидаемых страниц в печатном документе.
  • onWrite() - Вызывается для формирования печатных страниц в файл для печати. Этот метод может вызываться один или несколько раз после каждого onLayout() вызова.
  • onFinish() - Вызывается один раз в конце процесса печати. Если ваше приложение имеет какие-либо задачи для завершения процесса печати, выполните их здесь. Реализация данного метода не обязательна.

Следующие разделы описывают как реализовать методы создания макета и записи, которые имеют решающее значение для работы адаптера печати.

Примечание: Эти методы адаптера вызываются в основном потоке приложения. Если вы ожидаете, что выполнение вашей реализации этих методов может занять значительное времени, реализуйте их так, чтобы они выполняли работу в отдельном потоке. Например, вы можете инкапсулировать создание макета или запись печатного документа, используя отдельные AsyncTask объекты.

Вычисление информации печати документа

В реализации PrintDocumentAdapter класса, приложение должно иметь возможность указать тип документа, который оно создает, и рассчитать общее количество страниц для задания печати, учитывая информацию о размере печатной страницы. Реализация onLayout() метода адаптера делает эти расчеты, предоставляя информацию об ожидаемых результатах задания печати в PrintDocumentInfo классе, включая количество страниц и тип контента. В следующем примере кода показана базовая реализация onLayout() метода для PrintDocumentAdapter:

@Override
public void onLayout(PrintAttributes oldAttributes,
                     PrintAttributes newAttributes,
                     CancellationSignal cancellationSignal,
                     LayoutResultCallback callback,
                     Bundle metadata) {
    // Create a new PdfDocument with the requested page attributes
    mPdfDocument = new PrintedPdfDocument(getActivity(), newAttributes);

    // Respond to cancellation request
    if (cancellationSignal.isCancelled() ) {
        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.");
    }
}

Выполнение onLayout() метода может иметь три исхода: завершение, отмена или ошибка в случае, когда расчет макета не может быть завершен. Вы должны указать один из этих результатов, вызывая соответствующий метод PrintDocumentAdapter.LayoutResultCallback объекта.

Примечание: Логический параметр onLayoutFinished() метода указывает изменилось ли на самом деле содержание макета с момента последнего запроса. Этот параметр, установленный должным образом, позволяет платформе печати избежать лишнего вызова onWrite() метода, по существу кэшируя ранее записанный печатный документ, повышая тем самым производительность.

Основная работа onLayout() метода это вычисление ожидаемого количества страниц, учитывая атрибуты принтера. Как рассчитать это число сильно зависит от того, какой макет страниц ваше приложение использует для печати. В следующем примере кода показана реализация, где количество страниц определяется ориентацией печати:

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);
}

Послать файл документа на печать

Когда пришло время записать вывода на печать в файл, платформа печати Android вызывает onWrite() метод обратного вызова вашего PrintDocumentAdapter класса. Параметры метода указывают, какие страницы должны быть записаны в выходной файл, который будет использоваться. Ваша реализация этого метода должна визуализировать запрошенные страницы в многостраничный PDF файл. Когда этот процесс завершится, вы должны вызвать onWriteFinished() метод объекта обратного вызова.

Примечание: Платформа печати Android может вызвать onWrite() метод один или несколько раз для каждого вызова onLayout(). По этой причине очень важно установить логический параметр onLayoutFinished() метода в false когда макет печати содержимого не изменился, чтобы избежать ненужной перезаписи документа печати.

Примечание: Логический параметр onLayoutFinished() метода указывает изменилось ли на самом деле содержание макета с момента последнего запроса. Этот параметр, установленный должным образом, позволяет платформе печати избежать лишнего вызова onLayout() метода, по существу кэшируя ранее записанный печатный документ, повышая тем самым производительность.

Следующий пример демонстрирует основной механизм этого процесса с помощью PrintedPdfDocument класса для создания PDF файла:

@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 = mPdfDocument.startPage(i);

            // check for cancellation
            if (cancellationSignal.isCancelled()) {
                callback.onWriteCancelled();
                mPdfDocument.close();
                mPdfDocument = null;
                return;
            }

            // Draw page content for printing
            drawPage(page);

            // Rendering is complete, so page can be finalized.
            mPdfDocument.finishPage(page);
        }
    }

    // Write PDF document to file
    try {
        mPdfDocument.writeTo(new FileOutputStream(
                destination.getFileDescriptor()));
    } catch (IOException e) {
        callback.onWriteFailed(e.toString());
        return;
    } finally {
        mPdfDocument.close();
        mPdfDocument = null;
    }
    PageRange[] writtenPages = computeWrittenPages();
    // Signal the print framework the document is complete
    callback.onWriteFinished(writtenPages);

    ...
}

Этот пример делегирует визуализацию контента в виде PDF страницы в drawPage() метод, который обсуждается в следующем разделе.

Как и при создании макета, выполнение onWrite() метод может иметь три исхода: завершение, отмена или ошибка в случае, когда содержимое не может быть записано. Вы должны указать один из этих результатов, вызывая соответствующий метод PrintDocumentAdapter.WriteResultCallback объекта.

Примечание: Формирование документа для печати может быть ресурсоёмкой операцией. Чтобы избежать блокирования основного потока пользовательского интерфейса вашего приложения, вы должны рассмотреть вариант выполнения отрисовки страницы и операции записи в отдельном потоке, например, в AsyncTask. Для получения дополнительной информации о работе с потоками выполнения, такими как асинхронные задачи, см. Процессы и потоки.

Отрисовка содержимого PDF страницы

Когда ваше приложение отпечатает, приложение должно создать PDF документ и передать его в платформу печати Android. Вы можете использовать любую библиотеку создания PDF для этой цели. Этот урок показывает, как использовать PrintedPdfDocument класс для генерации PDF страниц с вашим контентом.

PrintedPdfDocument класс использует Canvas объект для отрисовки элементов на странице PDF, аналогично отрисовке на макете деятельности. Вы можете нарисовать элементы на печатной странице с помощью методов рисования Canvas объекта. В следующем примере кода показано, как нарисовать некоторые простые элементы на странице PDF документа с помощью этих методов:

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);
}

При использовании Canvas для рисования на PDF странице, элементы задаются в пунктах, что соответствует 1/72 дюйма. Убедитесь, что вы используете эту единицу измерения для задания размеров элементов на странице. Для позиционирования рисуемых элементов, система координат начинается с 0,0 в левом верхнем углу страницы.

Полезный совет: Хотя Canvas объект позволяет размещать печатаемые элементы на краю PDF документа, многие принтеры не могут печатать на краю физического листа бумаги. Убедитесь, что вы учли непечатные края страницы, когда вы строили документ для печати с помощью этого класса.