Работа с PDF. Создание PDF-файла

Автор: | 29.04.2018

В предыдущей статье мы рассказывали о том, как с помощью PdfRenderer можно отобразить PDF-документ в приложении. В этой статье мы дополним приложение, добавив в него возможность создания своего PDF-файла.

Для создания PDF-файла мы будем использовать класс PdfDocument, который появился в Android 4.4 KitKat (API 19). Таким образом, если вам нужно только создать PDF без отображения в приложении, вы можете использовать в проекте минимальную версию SDK 19 вместо 21.

Шаг 1. Делаем навигацию в приложении

Изначально мы выбрали шаблон с боковым меню, до сих пор однако его не использовали. Теперь с помощью бокового меню сделаем переключение между экранами выбора PDF-файла и создания PDF. Для этого откроем заранее созданное меню res/menu/activity_main_drawer.xml и заменим код в нём на свой.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

  <group android:checkableBehavior="single">
    <item
        android:id="@+id/nav_read"
        android:icon="@drawable/ic_list_black_24dp"
        android:title="Чтение PDF"/>
    <item
        android:id="@+id/nav_create"
        android:icon="@drawable/ic_add_black_24dp"
        android:title="Создание PDF"/>
  </group>

</menu>

В результате при открытии бокового меню мы увидим эти пункты.

Обработка выбора пункта меню реализовывается в методе onNavigationItemSelected(), добавим в него переключение экранов.

@SuppressWarnings("StatementWithEmptyBody") @Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
  int id = item.getItemId();

  switch (id) {
    case R.id.nav_create: {  // переключаемся на экран создания PDF
      btn_create.setVisible(true);
      listView.setVisibility(View.GONE);
      content_create.setVisibility(View.VISIBLE);
      break;
    }
    case R.id.nav_read: { // переключаемся на список PDF-файлов
      btn_create.setVisible(false);
      listView.setVisibility(View.VISIBLE);
      content_create.setVisibility(View.GONE);
      break;
    }
  }

  DrawerLayout drawer = findViewById(R.id.drawer_layout);
  drawer.closeDrawer(GravityCompat.START);
  return true;
}

Здесь при переключении меняется видимость ListView, в котором находится список, и ScrollView, в котором будет находиться разметка экрана создания PDF-файла и о которой будет сказано далее. Также здесь изменяется видимость пункта меню, расположенного в тулбаре, о котором также будет сказано далее.

Шаг 2. Делаем создание файла

Теперь можно приступить к реализации создания файла. Перед этим дополним код разметки content_main.xml следующей строкой.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:orientation="vertical"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="ru.androidtools.pdfreader.MainActivity"
    >

  <ListView
      android:id="@+id/listView"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:divider="@drawable/divider"
      />

  <include layout="@layout/content_create"/>

</LinearLayout>

В <include> помещёна разметка экрана создания файла, которую можно увидеть ниже. Здесь мы заранее подготовим текст, поместив его в компонент TextView, и изображение, чтобы в итоге сохранить всё в PDF.

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/content_create"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp"
    android:visibility="gone"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    >

  <LinearLayout
      android:id="@+id/ll_pdflayout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:background="#ffffff"
      android:orientation="vertical"
      >

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="300dp"
        android:layout_height="250dp"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:scaleType="fitXY"
        android:src="@mipmap/ic_launcher"
        />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
        android:textColor="#000000"
        android:textSize="14sp"
        />

  </LinearLayout>

</ScrollView>

Теперь нам нужно добавить кнопку, при нажатии на которую будет генерироваться PDF-страница. Для этого создадим новое меню res/menu/pdf_menu.xml и добавим в него нужный пункт.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

  <item
      android:id="@+id/pdf_create"
      android:title="Сгенерировать"
      app:showAsAction="ifRoom"/>

</menu>

В коде активности переопределим метод onCreateOptionsMenu() для того, чтобы добавить пункт меню в тулбар. Также найдём нужный пункт меню по идентификатору, чтобы затем изменять его видимость.

private MenuItem btn_create;

...

@Override public boolean onCreateOptionsMenu(Menu menu) {
  MenuInflater menuInflater = getMenuInflater();
  menuInflater.inflate(R.menu.pdf_menu, menu);
  btn_create = menu.findItem(R.id.pdf_create);
  btn_create.setVisible(false);
  return true;
}

Затем нажатие на пункт меню будем обрабатывать в методе onOptionsItemSelected(). Проверять наличие прав здесь нет необходимости, поскольку на этом этапе они гарантированны выданы пользователем.

@Override public boolean onOptionsItemSelected(MenuItem item) {
  int id = item.getItemId();

  if (id == R.id.pdf_create) {
    generatePdf();
  }

  return super.onOptionsItemSelected(item);
}

Здесь при нажатии вызывается метод generatePdf(), в котором происходит собственно генерация PDF-документа. API PdfDocument позволяет обрабатывать создание контента по страницам, используя для этого PdfDocument.Page. Страница PDF связывается с холстом, на котором и рисуется контент страницы. Для представления атрибутов страницы, таких как высота, ширина и так далее, воспользуемся PdfDocument,PageInfo.

private LinearLayout pdf_layout;

...

@Override protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  ...
  content_create = findViewById(R.id.content_create);
  pdf_layout = findViewById(R.id.ll_pdflayout);
}

...

private void generatePdf() {
  DisplayMetrics displaymetrics = new DisplayMetrics();
  getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
  float height = displaymetrics.heightPixels;
  float width = displaymetrics.widthPixels;

  int convertHeight = (int) height, convertWidth = (int) width;

  // создаем документ
  PdfDocument document = new PdfDocument();
  // определяем размер страницы
  PdfDocument.PageInfo pageInfo =
      new PdfDocument.PageInfo.Builder(convertWidth, convertHeight, 1).create();
  // получаем страницу, на котором будем генерировать контент
  PdfDocument.Page page = document.startPage(pageInfo);

  // получаем холст (Canvas) страницы
  Canvas canvas = page.getCanvas();

  Paint paint = new Paint();
  canvas.drawPaint(paint);

  // получаем контент, который нужно добавить в PDF, и загружаем его в Bitmap
  Bitmap bitmap = loadBitmapFromView(pdf_layout, pdf_layout.getWidth(), pdf_layout.getHeight());
  bitmap = Bitmap.createScaledBitmap(bitmap, convertWidth, convertHeight, true);

  // рисуем содержимое и закрываем страницу
  paint.setColor(Color.BLUE);
  canvas.drawBitmap(bitmap, 0, 0, null);
  document.finishPage(page);

  File dir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/PDF");
  if (!dir.exists()) {
    dir.mkdirs();
  }

  // сохраняем записанный контент
  String targetPdf = dir.getAbsolutePath() + "/test.pdf";
  File filePath = new File(targetPdf);
  try {
    document.writeTo(new FileOutputStream(filePath));
    Toast.makeText(getApplicationContext(), "PDf сохранён в " + filePath.getAbsolutePath(),
        Toast.LENGTH_SHORT).show();
    // обновляем список
    initViews();
  } catch (IOException e) {
    e.printStackTrace();
    Toast.makeText(this, "Что-то пошло не так: " + e.toString(), Toast.LENGTH_LONG).show();
  }

  // закрываем документ
  document.close();
}

public static Bitmap loadBitmapFromView(View v, int width, int height) {
  Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  Canvas c = new Canvas(b);
  v.draw(c);

  return b;
}

В результате работы методы будет создан PDF-файл с одной страницей, на которой будет находиться содержимое, определённое в коде разметки.

Заключение

Создание PDF-файлов очень полезно в случаях, когда нужно быстро генерировать документы, которыми затем можно поделиться и прочитать на других устройствах. На протяжении двух статей с помощью нескольких лёгких операций мы написали приложение, которое может не только читать PDF-документы, но и создавать свои собственные.

Работа с PDF. Создание PDF-файла: 3 комментария

  1. Евгений

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

  2. Екатерина Дудалова

    Как создавать пдф в несколько страниц? У меня большая activity с recyclerView, изменила здесь параметр на 8 PdfDocument.PageInfo pageInfo =
    new PdfDocument.PageInfo.Builder(convertWidth, convertHeight, 1).create();
    все равно создается одна страница, та, которая отображается на экране при сохранении. Передачу длины и ширины тоже изменила, но все равно не работает. Дело в том, что recyclerview удаляется с экрана при перемотке?
    public static int getActivityHeight(Activity activity)
    {
    return activity.findViewById(R.id.pdf_linear).getHeight();
    }
    public static int getActivityWidth(Activity activity)
    {
    return activity.findViewById(R.id.pdf_linear).getWidth();
    }

    1. Владимир

      Здравствуйте!
      Параметр pageNumber в PdfDocument.PageInfo.Builder(int pageWidth, int pageHeight, int pageNumber) только определяет порядковый номер страницы при генерации документа. Если Вы хотите забрать данные из RecyclerView и сохранить их в PDF, можно как вариант в списке, инициализирующим RecyclerView, хранить данные для каждой страницы, и затем при генерации PDF брать этот список и через PdfDocument.PageInfo.Builder поочередно создавать страницы, записывая в них нужные данные.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *