Özel dokümanları yazdırma

Çizim uygulamaları, sayfa düzeni uygulamaları ve grafik çıktısı odaklı diğer uygulamalar gibi bazı uygulamalarda, göz alıcı basılı sayfalar oluşturmak temel özelliklerden biridir. Bu durumda resim veya HTML belgesi yazdırmak yeterli olmaz. Bu tür uygulamaların yazdırma çıktısı, yazı tipleri, metin akışı, sayfa sonları, üstbilgiler, altbilgiler ve grafik öğeler dahil olmak üzere bir sayfaya gelen her şeyin hassas bir şekilde kontrol edilmesini gerektirir.

Uygulamanız için tamamen özelleştirilmiş bir baskı çıktısı oluşturmak, daha önce tartışılan yaklaşımlara kıyasla daha fazla programlama yatırımı gerektirir. Yazdırma çerçevesiyle iletişim kuran bileşenler oluşturmanız, yazıcı ayarlarına göre ayarlama yapmanız, sayfa öğeleri çizmeniz ve birden fazla sayfada yazdırmayı yönetmeniz gerekir.

Bu derste, yazıcı yöneticisiyle nasıl bağlantı kuracağınızı, yazdırma bağdaştırıcısını nasıl oluşturacağınızı ve yazdırma için nasıl içerik oluşturacağınızı öğreneceksiniz.

Uygulamanız yazdırma işlemini doğrudan yönettiğinde, kullanıcınızdan yazdırma isteği aldıktan sonraki ilk adım Android yazdırma çerçevesine bağlanmak ve PrintManager sınıfının bir örneğini almaktır. Bu sınıf, bir yazdırma işini başlatmanıza ve yazdırma yaşam döngüsünü başlatmanıza olanak tanır. Aşağıdaki kod örneğinde, yazıcı yöneticisini alacağınız ve yazdırma işlemini nasıl başlatacağınız gösterilmektedir.

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

Yukarıdaki örnek kodda, bir yazdırma işini nasıl adlandıracağınız ve yazdırma yaşam döngüsünün adımlarını uygulayan PrintDocumentAdapter sınıfının bir örneğinin nasıl ayarlanacağı gösterilmektedir. Yazdırma bağdaştırıcısı sınıfının uygulanması bir sonraki bölümde ele alınmaktadır.

Not: print() yöntemindeki son parametre bir PrintAttributes nesnesi alır. Yazdırma çerçevesiyle ilgili ipuçları sağlamak ve önceki yazdırma döngüsüne göre önceden ayarlanmış seçenekler sağlamak için bu parametreyi kullanabilir, böylece kullanıcı deneyimini iyileştirebilirsiniz. Bu parametreyi, yazdırılan içerik için daha uygun olan seçenekleri (söz konusu yönde bir fotoğrafı yazdırırken yönü yatay olarak ayarlama gibi) belirlemek için de kullanabilirsiniz.

Yazdırma bağdaştırıcısı, Android yazdırma çerçevesiyle etkileşime girer ve yazdırma işleminin adımlarını işler. Bu işlem, kullanıcıların yazdırmak için bir doküman oluşturmadan önce yazıcıları ve yazdırma seçeneklerini belirlemesini gerektirir. Kullanıcı farklı çıkış özelliklerine, farklı sayfa boyutlarına veya farklı sayfa yönlerine sahip yazıcıları seçtiği için bu seçimler nihai sonucu etkileyebilir. Bu seçimler yapıldığında baskı çerçevesi, nihai çıktıya hazırlanmak için adaptörünüzden bir baskı dokümanı hazırlamasını ve oluşturmasını ister. Bir kullanıcı yazdır düğmesine dokunduktan sonra, çerçeve son yazdırma dokümanını alır ve çıktısı için bir yazdırma sağlayıcısına iletir. Yazdırma işlemi sırasında kullanıcılar yazdırma işlemini iptal etmeyi seçebilir. Bu nedenle yazdırma adaptörünüzün de iptal isteklerini dinlemesi ve bunlara yanıt vermesi gerekir.

PrintDocumentAdapter soyut sınıfı, dört ana geri çağırma yöntemi içeren yazdırma yaşam döngüsünü işlemek için tasarlanmıştır. Yazdırma çerçevesiyle doğru şekilde etkileşim kurmak için yazdırma bağdaştırıcınızda aşağıdaki yöntemleri uygulamanız gerekir:

  • onStart(): Yazdırma işleminin başında bir kez çağrıldı. Uygulamanızın, yazdırılacak verilerin anlık görüntüsünü alma gibi tek seferlik hazırlama görevleri varsa bunları burada yürütün. Bağdaştırıcınızda bu yöntemi uygulamanız gerekmez.
  • onLayout() - Kullanıcılar çıktıyı etkileyen bir yazdırma ayarını (ör. farklı sayfa boyutu veya sayfa yönü) her değiştirdiğinde çağrılır; bu da uygulamanıza, yazdırılacak sayfaların düzenini hesaplama fırsatı verir. En azından, bu yöntem yazdırılan dokümanda kaç sayfanın beklendiğini döndürmelidir.
  • onWrite(): Yazdırılan sayfaları yazdırılacak bir dosya halinde oluşturmak için çağrılır. Bu yöntem, her onLayout() çağrısından sonra bir veya daha fazla kez çağrılabilir.
  • onFinish(): Yazdırma işleminin sonunda bir defa çağrıldı. Uygulamanızın gerçekleştirmesi gereken tek seferlik sökme görevleri varsa bunları burada yürütün. Bağdaştırıcınızda bu yöntemi uygulamanız gerekmez.

Aşağıdaki bölümlerde, yazdırma adaptörünün çalışması için kritik öneme sahip olan düzen ve yazma yöntemlerinin nasıl uygulanacağı açıklanmaktadır.

Not: Bu bağdaştırıcı yöntemleri uygulamanızın ana iş parçacığında çağrılır. Uygulamanızda bu yöntemlerin yürütülmesinin çok zaman alacağını düşünüyorsanız bunları ayrı bir iş parçacığında yürütülecek şekilde uygulayın. Örneğin, düzeni kapsülleyebilir veya belge yazma çalışmasını ayrı AsyncTask nesnelerine yazdırabilirsiniz.

Basılı belge bilgilerini hesapla

PrintDocumentAdapter sınıfının uygulamasında, uygulamanızın oluşturduğu belge türünü belirtmesi ve yazdırılan sayfa boyutu hakkında bilgi verildiğinde yazdırma işi için toplam sayfa sayısını hesaplayabilmesi gerekir. Bağdaştırıcıda onLayout() yönteminin uygulanması bu hesaplamaları yapar ve sayfa sayısı ile içerik türü de dahil olmak üzere PrintDocumentInfo sınıfındaki yazdırma işinin beklenen çıkışı hakkında bilgi sağlar. Aşağıdaki kod örneğinde, PrintDocumentAdapter için onLayout() yönteminin temel bir uygulaması gösterilmektedir:

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

onLayout() yönteminin çalıştırılmasının üç sonucu olabilir: tamamlanma, iptal etme veya düzen hesaplamasının tamamlanamadığı durumlarda başarısız olma. PrintDocumentAdapter.LayoutResultCallback nesnesinin uygun yöntemini çağırarak bu sonuçlardan birini belirtmeniz gerekir.

Not: onLayoutFinished() yönteminin boole parametresi, düzen içeriğinin son istekten bu yana gerçekten değişip değişmediğini ifade eder. Bu parametrenin doğru şekilde ayarlanması, yazdırma çerçevesinin onWrite() yöntemini gereksiz yere çağırmayı önlemesine olanak tanır. Bu işlem, temelde önceden yazılmış yazdırma dokümanını önbelleğe alır ve performansı artırır.

onLayout() işlevinin ana işi, yazıcının özellikleri dikkate alınarak çıktı olarak beklenen sayfa sayısını hesaplamaktır. Bu sayıyı nasıl hesaplayacağınız, büyük ölçüde uygulamanızın sayfaları baskı için nasıl hazırladığına bağlıdır. Aşağıdaki kod örneğinde, sayfa sayısının yazdırma yönüne göre belirlendiği bir uygulama gösterilmektedir:

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

Yazdırılacak belge dosyası yazın

Bir dosyaya yazdırma çıkışı yazma zamanı geldiğinde, Android yazdırma çerçevesi, uygulamanızın PrintDocumentAdapter sınıfının onWrite() yöntemini çağırır. Yöntemin parametreleri, hangi sayfaların yazılması gerektiğini ve kullanılacak çıkış dosyasını belirtir. Bu yöntemi uygulamanız, istenen her içerik sayfasını çok sayfalı bir PDF belge dosyası olarak oluşturmalıdır. Bu işlem tamamlandığında, geri çağırma nesnesinin onWriteFinished() yöntemini çağırırsınız.

Not: Android yazdırma çerçevesi, onWrite() yöntemini her onLayout() çağrısı için bir veya daha fazla kez çağırabilir. Bu nedenle, yazdırma dokümanının gereksiz yere yeniden yazılmasını önlemek için, yazdırma içeriği düzeni değişmediğinde onLayoutFinished() yönteminin boole parametresinin false olarak ayarlanması önemlidir.

Not: onLayoutFinished() yönteminin boole parametresi, düzen içeriğinin son istekten bu yana gerçekten değişip değişmediğini ifade eder. Bu parametrenin doğru şekilde ayarlanması, yazdırma çerçevesinin onLayout() yöntemini gereksiz yere çağırmayı önlemesine olanak tanır. Bu işlem, temelde önceden yazılmış yazdırma dokümanını önbelleğe alır ve performansı artırır.

Aşağıdaki örnekte, bir PDF dosyası oluşturmak için PrintedPdfDocument sınıfının kullanıldığı bu işlemin temel mekanizması gösterilmektedir:

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

    ...
}

Bu örnekte, PDF sayfası içeriğinin oluşturulması için drawPage() yöntemi yetki verilir. Bu yöntem bir sonraki bölümde ele alınacaktır.

Düzende olduğu gibi, onWrite() yönteminin çalıştırılması üç sonuç olabilir: tamamlama, iptal veya içeriğin yazılamadığı durumda başarısızlık. PrintDocumentAdapter.WriteResultCallback nesnesinin uygun yöntemini çağırarak bu sonuçlardan birini belirtmeniz gerekir.

Not: Bir dokümanı yazdırmak için oluşturmak, yoğun kaynak kullanan bir işlem olabilir. Uygulamanızın ana kullanıcı arayüzü iş parçacığını engellememek için sayfa oluşturma ve yazma işlemlerini ayrı bir iş parçacığında (örneğin, AsyncTask) gerçekleştirmeyi düşünmeniz gerekir. Eşzamansız görevler gibi yürütme iş parçacıklarıyla çalışma hakkında daha fazla bilgi için İşlemler ve İş Parçacıkları bölümüne bakın.

Çizim PDF sayfası içeriği

Uygulamanız yazdırıldığında uygulamanız bir PDF dokümanı oluşturmalı ve bu dokümanı baskı için Android yazdırma çerçevesine iletmelidir. Bu amaçla dilediğiniz PDF oluşturma kitaplığını kullanabilirsiniz. Bu derste, içeriğinizden PDF sayfaları oluşturmak için PrintedPdfDocument sınıfının nasıl kullanılacağı gösterilmektedir.

PrintedPdfDocument sınıfı, PDF sayfasında öğe çizmek için etkinlik düzeni üzerinde yapılan çizime benzer şekilde bir Canvas nesnesi kullanır. Canvas çizim yöntemlerini kullanarak yazdırılan sayfada öğeler çizebilirsiniz. Aşağıdaki örnek kod, PDF belgesi sayfasında bazı basit öğelerin aşağıdaki yöntemleri kullanarak nasıl çizileceğini gösterir:

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

Bir PDF sayfasında çizim yapmak için Canvas kullanılırken öğeler noktalar cinsinden belirtilir. Bu, bir inçin 1/72'sidir. Sayfadaki öğelerin boyutunu belirtmek için bu ölçü birimini kullandığınızdan emin olun. Çizilen öğelerin konumlandırması için koordinat sistemi, sayfanın sol üst köşesi için 0,0'dan başlar.

İpucu: Canvas nesnesi, yazdırma öğelerini PDF dokümanlarının kenarına yerleştirmenize olanak sağlasa da, birçok yazıcı fiziksel bir kağıdın kenarına yazdıramaz. Bu sınıfla bir yazdırma dokümanı oluştururken sayfanın yazdırılamayan kenarlarını hesaba kattığınızdan emin olun.