Search code examples
javaandroidpdfdocument

How to get the total height of a view


I need to get the total height of my view to generate a PDF, to know the number of pages I need to draw.

I need to generate a PDF with the content of my screen, there is an ScrollView with data that comes from the database.

I created a test project for this purpose, see below the code.

This code always gives me the same number, even if I change the XML adding more images:


        ViewTreeObserver vto2 = layout.getViewTreeObserver();
        vto2.addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    layout.getViewTreeObserver()
                            .removeOnGlobalLayoutListener(this);
                } else {
                    layout.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                }
                int width  = layout.getMeasuredWidth();
                int height = layout.getMeasuredHeight();

            }
        });

And this is my generate PDF code:

public void convertXmlToPdf() {

        PdfDocument document = new PdfDocument();
        DisplayMetrics displayMetrics = new DisplayMetrics();

        int viewWidth = 1080;
        int viewHeight = 1920;

        int numPagina = 1;
        View view = LayoutInflater.from(this).inflate(R.layout.main_activity, null);

        view.measure(View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, View.MeasureSpec.EXACTLY),
                View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels * numPagina, View.MeasureSpec.EXACTLY));
        view.getMeasuredHeight();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            this.getDisplay().getRealMetrics(displayMetrics);
        } else
            this.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        if(ViewCompat.isLaidOut(view)) {
            view.getHeight();
        }
        view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);

        Display display = getWindowManager().getDefaultDisplay();
        Point m_size = new Point();
        display.getSize(m_size);

        int m_height = m_size.y;

        **double totalPages = Math.ceil(Double.parseDouble(m_height + "") / Double.parseDouble(viewHeight + "")); //TODO <----- i can't get the real height number here**
//        double totalPages = 4;

        do {
            view = LayoutInflater.from(this).inflate(R.layout.main_activity, null);

            if(numPagina == 1) {
                view.measure(View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels * numPagina, View.MeasureSpec.EXACTLY));
            } else {
                view.measure(View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels * numPagina + (displayMetrics.heightPixels * numPagina - displayMetrics.heightPixels), View.MeasureSpec.EXACTLY)); //If i don't do this the next pages don't start from where they need
            }
            view.layout(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
            // Create a new PdfDocument instance

            view.getMeasuredHeight();

            PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(displayMetrics.widthPixels, displayMetrics.heightPixels, numPagina).create();

            // Start a new page
            PdfDocument.Page page = document.startPage(pageInfo);

            // Get the Canvas object to draw on the page
            Canvas canvas = page.getCanvas();

            // Create a Paint object for styling the view
            Paint paint = new Paint();
            paint.setColor(Color.BLACK);

            // Draw the view on the canvas
            view.draw(canvas);

            // Finish the page
            document.finishPage(page);
            numPagina++;
        } while (numPagina <= totalPages);


        // Specify the path and filename of the output PDF file
        File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        String fileName = "exampleXML.pdf";
        File filePath = new File(downloadsDir, fileName);

        try {
            // Save the document to a file
            FileOutputStream fos = new FileOutputStream(filePath);
            document.writeTo(fos);
            document.close();
            fos.close();
            // PDF conversion successful
            Toast.makeText(this, "XML to PDF Conversion Successful", Toast.LENGTH_LONG).show();
        } catch (IOException e) {
            e.printStackTrace();
            // Error occurred while converting to PDF
        }
    }

And here's my XML file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:id="@+id/rootview">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:text="Learn to Create PDF"
        android:textStyle="bold"
        android:textColor="@color/black"/>


    <Button
        android:id="@+id/btnCreatePdf"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Create PDF" />

    <Button
        android:id="@+id/btnXMLToPDF"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Convert XML View to PDF" />

    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:padding="30px"
                android:text="1"
                android:textColor="@color/black" />

            <ImageView
                android:id="@+id/image"
                android:layout_width="wrap_content"
                android:layout_height="360px"
                android:src="@drawable/logo_om"
                android:visibility="visible" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:padding="30px"
                android:text="2"
                android:textColor="@color/black"/>

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="360px"
                android:src="@drawable/logo_om"
                android:visibility="visible" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:padding="30px"
                android:text="3"
                android:textColor="@color/black"/>


            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="360px"
                android:src="@drawable/logo_om"
                android:visibility="visible" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:padding="30px"
                android:text="4"
                android:textColor="@color/black"/>

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="360px"
                android:src="@drawable/logo_om"
                android:visibility="visible" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:padding="30px"
                android:text="5"
                android:textColor="@color/black" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="360px"
                android:src="@drawable/logo_om"
                android:visibility="visible" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:padding="30px"
                android:text="FINAL DO DOCUMENTO" />

        </LinearLayout>

    </ScrollView>


</LinearLayout>

@Edit: This are the links I tried: Android: Total height of ScrollView

How can you tell when a layout has been drawn?

@Edit2: this is the tutorial o followed to create the PDF (it generates only 1 page, so the code to generate more pages is what i found around) https://www.youtube.com/watch?v=-JC1PI-dSeE


Solution

  • The code always gives you the same number because your are telling it what number to give and fixing the number to the size you told it to give.

    When you set the Measurespec to EXACTLY from the documentation

    EXACTLY - The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.

    Thus you are forcing the views size to be the incorrect size you have calculated from the screen size.

    When drawing a view to PDF you can make the PDF page a funny size to match the screen or you can make it standard paper size e.g. A4

    If you go for a non standard width then you might as well use a non standard height and only have one page. Then if a user wants to print it they will have to break it in to multiple pages wide and high. From a users perspective if just viewing the PDF on screen there is no benefit from using multiple pages

    I think it is best to stick to standard page sizes with PDF's and then think of a PDF as a separate low resolution screen that has nothing to do with the device's physical screen size (They can scroll the page up and down and left and right)

    When you are measuring a single View you are measuring the whole view and all it's children views, you cannot measure a slice (what you want on the second and third page).

    There are two main approaches to creating a multipage PDF from a view.

    1. Draw everything and then slice and dice in to multiple pages, with this method you are likely to have items that are split between pages e.g. the top half of some text line on one page and the bottom on another making it unreadable. This is not a good approach.

    2. Iterate through the parent view's children views measuring each child view and then adding them to a layout for a single page. This way you get each child view's size and then can calculate the accumulative size of all the views you have added already to see if it would be too big for your page size. If it is too big you would close off the PDF page and start laying out to a new page. This means items won't be split across pages (so much better).

    https://github.com/Zardozz/RecyclerviewPdf is a fully working example of how to generate a multiple page PDF from a scrollable screen by measuring individual child views (in this case the scrollable screen is it's a recyclerview, but you can iterate over the child items of a scrollview in a similar manner).

    Note an A4 page is a very low resolution in size and thus your normal font sizes (or other items sizes) for a physical screen will probably be too big, so you need to adjust these just like you you normally would have different layouts for different screen sizes). There is an alternate approach to adjusting to the low res of a PDF page, this is drawn to a multiple of the PDF page size and then set a scale on the PDF canvas to scale it back to correct size e.g. draw to twice as big layout and then scale everything back to half the size when drawing to the PDF page.

    Though the problem you might have is your Textviews might contain more text in one block than can fit on a page, these should be would need extra handling, to create two Textview instead of one.