La réalisation du projet pratique de développement de l'application de bibliothèque électronique de l'application Android (avec le code source et la vidéo de démonstration, il est facile à comprendre et peut être utilisé directement)

Si vous avez besoin d'une collection d'images et d'un code source, veuillez aimer et suivre la collection et laisser un message dans la zone de commentaire ~~~

1. Description des exigences

L'expérience de navigation de la navigation dans les livres électroniques sur les téléphones mobiles est similaire à celle de la lecture de livres papier. Les effets visuels du retournement du papier sont toujours présents pendant le processus de rotation des pages, ce qui rend les lecteurs agréables à regarder. En résumé, la lecture sur téléphone mobile n'est rien de plus que deux fonctions principales : l'une est la gestion de la bibliothèque, qui sert principalement à ajouter, supprimer, modifier et vérifier des livres, et l'autre est la navigation, qui sert principalement à gérer le processus de rotation des pages. .

2. Analyse fonctionnelle

Il y a plusieurs problèmes avec les livres électroniques. D'une part, il existe différents formats de livres électroniques. D'autre part, Android n'a pas de contrôles prêts à l'emploi pour afficher ces livres électroniques de manière unifiée. Il est très difficile d'afficher des e-books dans différents formats sur l'écran d'un téléphone portable.

Pour la question précédente. Les livres électroniques peuvent être unifiés en quelques formats publics pour réduire la difficulté de codage. Pour ce dernier problème, chaque page du livre électronique peut être convertie en un fichier image, puis le livre électronique peut être parcouru à l'aide de l'image voir.

Dans le prochain projet pratique, seuls deux formats de livres électroniques sont pris en charge pour le moment, à savoir PDF, un fichier électronique indépendant de la plate-forme et DJVU.

Ensuite, analysez certaines technologies utilisées dans les lecteurs de livres électroniques

Asset Manager AssetManager cinq premiers eBooks de démonstration

Cadre de la base de données Room Le titre du livre et les pages d'auteur de chaque e-book sont enregistrés de manière uniforme dans la base de données

Le moteur de rendu de fichiers PDF analyse les fichiers PDF en un ensemble d'images

La courbe de Bézier est utilisée pour réaliser l'effet spécial de rotation de page dans le processus de navigation sur les pages de livres électroniques

Interface JNI

Traitement des fichiers images

boîte de dialogue de saisie

 Ce qui suit présente brièvement la relation entre les principaux modules de code

EbookReaderActivity La page de la liste des livres du lecteur de livre électronique

PdfRenderActivity Page de lecture de livre électronique PDF

PdfSlideActivity PDF e-book rotation de page en douceur

PdfCurveActivity Courbe de Bézier tourner la page

PdfOpenglActivity curl page

DvjuRenderActivity Page de lecture du livre électronique DJVU

ImageFragment Affiche des fragments d'images de livres électroniques sur chaque page

3. Affichage des effets

L'effet est montré comme suit. Il y a à la fois une rotation de page lisse ordinaire et une courbe de Bézier et une rotation de page bouclée. Il semble plus réaliste et agréable à l'œil.

 La vidéo de démonstration est la suivante

Application de livre électronique Android

Les rendus sont les suivants

 

 

 

 

 4.Code

Cours de livre électronique

package com.example.ebook;

import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.example.ebook.adapter.BookListAdapter;
import com.example.ebook.dao.BookDao;
import com.example.ebook.entity.BookInfo;
import com.example.ebook.util.AssetsUtil;
import com.example.ebook.widget.InputDialog;

import java.util.ArrayList;
import java.util.List;

public class EbookReaderActivity extends AppCompatActivity implements
        AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener {
    private static final String TAG = "EbookReaderActivity";
    private ListView lv_ebook; // 声明一个用于展示书籍列表的列表视图对象
    private String[] mFileNameArray = {"tangshi.pdf", "android.pdf", "zhugeliang.djvu", "dufu.djvu", "luyou.djvu"};
    private List<BookInfo> mBookList = new ArrayList<>(); // 书籍信息列表
    private BookListAdapter mAdapter; // 声明一个书籍列表的适配器对象
    private BookDao bookDao; // 声明一个书籍的持久化对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ebook_reader);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // 保持屏幕常亮
        initView(); // 初始化视图
        new Thread(() -> copyPdfFile()).start(); // 启动演示文件的复制线程
    }

    // 初始化视图
    private void initView() {
        Toolbar tl_head = findViewById(R.id.tl_head);
        tl_head.setTitle("电子书架");
        setSupportActionBar(tl_head); // 替换系统自带的ActionBar
        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        lv_ebook = findViewById(R.id.lv_ebook);
    }

    // 把assets目录下的演示文件复制到存储卡
    private void copyPdfFile() {
        // 从App实例中获取唯一的书籍持久化对象
        bookDao = MainApplication.getInstance().getBookDB().bookDao();
        mBookList = bookDao.queryAllBook(); // 获取所有书籍记录
        if (mBookList!=null && mBookList.size()>0) {
            runOnUiThread(() -> initBookList()); // 初始化书籍列表
            return;
        }
        List<BookInfo> bookList = new ArrayList<>();
        for (String file_name : mFileNameArray) {
            String dir = String.format("%s/%s/",
                    getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
                    file_name.substring(file_name.lastIndexOf(".")+1)
            );
            String fileName = file_name.substring(file_name.lastIndexOf("/") + 1);
            // 把资产目录下的电子书复制到存储卡
            AssetsUtil.Assets2Sd(this, fileName, dir + fileName);
            bookList.add(new BookInfo(file_name));
        }
        bookDao.insertBookList(bookList); // 把演示用的电子书信息添加到数据库
        runOnUiThread(() -> initBookList()); // 初始化书籍列表
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        initBookList(); // 初始化书籍列表
    }

    // 初始化书籍列表
    private void initBookList() {
        mBookList = bookDao.queryAllBook(); // 获取所有书籍记录
        // 下面把书籍列表通过ListView展现出来
        mAdapter = new BookListAdapter(this, mBookList);
        lv_ebook.setAdapter(mAdapter);
        lv_ebook.setOnItemClickListener(this);
        lv_ebook.setOnItemLongClickListener(this);
    }

    // 在点击书籍记录时触发
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        String file_name = mBookList.get(position).getFileName();
        String title = mBookList.get(position).getTitle();
        Log.d(TAG, "file_name="+file_name+", title="+title);
        if (file_name.endsWith(".pdf")) { // PDF格式
            // 跳转到PDF阅读界面
            startReader(file_name, title, PdfRenderActivity.class);
        } else if (file_name.endsWith(".djvu")) { // DJVU格式
            // 跳转到第三方Vudroid提供的阅读界面
            startReader(file_name, title, DjvuRenderActivity.class);
        } else {
            Toast.makeText(this, "暂不支持该格式的电子书", Toast.LENGTH_SHORT).show();
        }
    }

    // 在长按书籍记录时触发
    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        // 以下创建并弹出标题填写对话框
        InputDialog dialog = new InputDialog(this, mBookList.get(position).getFileName(),
                position, "请输入书籍名称", (idt, content, seq) -> {
            BookInfo book = mBookList.get(seq);
            book.setTitle(content);
            bookDao.updateBook(book); // 更新数据库中该书籍记录的标题
        });
        dialog.show();
        return true;
    }

    // 启动指定的电子书阅读界面
    private void startReader(String file_name, String title, Class<?> cls) {
        Intent intent = new Intent(this, cls);
        intent.putExtra("file_name", file_name);
        intent.putExtra("title", title);
        startActivity(intent);
    }

}

classe PDF

package com.example.ebook;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup;

import com.example.ebook.util.AssetsUtil;
import com.example.ebook.util.Utils;
import com.example.ebook.widget.CurveView;

import java.util.ArrayList;
import java.util.List;

public class PdfCurveActivity extends AppCompatActivity {
    private final static String TAG = "PdfCurveActivity";
    private CurveView cv_book; // 声明一个卷曲视图对象
    private List<String> mPathList = new ArrayList<>(); // 图片路径列表
    private String mFileName = "tangshi.pdf"; // 演示文件的名称

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pdf_curve);
        initView(); // 初始化视图
        // 加载pdf会花一点点时间,这里先让整个界面出来,再慢慢渲染pdf
        new Handler(Looper.myLooper()).post(() -> renderPDF());
    }

    // 初始化视图
    private void initView() {
        String title = "";
        // 从前个页面传来的数据中获取书籍的标题和文件名称
        if (getIntent().getExtras()!=null && !getIntent().getExtras().isEmpty()) {
            title = getIntent().getStringExtra("title");
            mFileName = getIntent().getStringExtra("file_name");
        }
        Toolbar tl_head = findViewById(R.id.tl_head);
        tl_head.setTitle(!TextUtils.isEmpty(title) ? title : mFileName);

        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        cv_book = findViewById(R.id.cv_book);
        findViewById(R.id.btn_resume).setOnClickListener(v -> cv_book.reset());
    }

    // 开始渲染PDF文件
    private void renderPDF() {
        // 把资产文件转换为图片路径列表
        mPathList = AssetsUtil.getPathListFromPdf(this, mFileName);
        Log.d(TAG, "mPathList.size="+mPathList.size());
        Bitmap first = BitmapFactory.decodeFile(mPathList.get(0));
        int height = (int)(1.0*first.getHeight()/first.getWidth() * Utils.getScreenWidth(this));
        Log.d(TAG, "height="+height);
        ViewGroup.LayoutParams params = cv_book.getLayoutParams();
        params.height = height; // 根据书页图片的尺寸调整卷曲视图的高度
        cv_book.setLayoutParams(params); // 设置卷曲视图的布局参数
        cv_book.setFilePath(mPathList); // 设置卷曲视图的文件路径
    }

}

Cours de tournage de pages bouclées

package com.example.ebook;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.pdf.PdfRenderer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import com.example.ebook.util.AssetsUtil;
import com.example.ebook.util.BitmapUtil;
import com.example.ebook.util.Utils;

import java.io.File;
import java.util.ArrayList;

import fi.harism.curl.CurlPage;
import fi.harism.curl.CurlView;

public class PdfOpenglActivity extends AppCompatActivity {
    private final static String TAG = "PdfOpenglActivity";
    private CurlView cv_content; // 声明一个卷曲视图对象
    private ArrayList<String> mImgList = new ArrayList<>(); // 图片路径列表
    private String mFileName = "tangshi.pdf"; // 演示文件的名称

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pdf_opengl);
        initView(); // 初始化视图
        // 加载pdf会花一点点时间,这里先让整个界面出来,再慢慢渲染pdf
        new Handler(Looper.myLooper()).post(() -> renderPDF());
    }

    // 初始化视图
    private void initView() {
        String title = "";
        // 从前个页面传来的数据中获取书籍的标题和文件名称
        if (getIntent().getExtras()!=null && !getIntent().getExtras().isEmpty()) {
            title = getIntent().getStringExtra("title");
            mFileName = getIntent().getStringExtra("file_name");
        }
        Toolbar tl_head = findViewById(R.id.tl_head);
        tl_head.setTitle(!TextUtils.isEmpty(title) ? title : mFileName);

        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        cv_content = findViewById(R.id.cv_content);
    }

    // 开始渲染PDF文件
    private void renderPDF() {
        String dir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/pdf/";
        String filePath = dir + mFileName;
        // 无法直接从asset目录读取PDF文件,只能先把PDF文件复制到存储卡,再从存储卡读取PDF
        AssetsUtil.Assets2Sd(this, mFileName, filePath);
        try {
            // 打开存储卡里指定路径的PDF文件
            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                    new File(filePath), ParcelFileDescriptor.MODE_READ_ONLY);
            // 创建一个PDF渲染器
            PdfRenderer pdfRenderer = new PdfRenderer(pfd);
            Log.d(TAG, "page count=" + pdfRenderer.getPageCount());
            // 依次处理PDF文件的每个页面
            for (int i = 0; i < pdfRenderer.getPageCount(); i++) {
                // 生成该页图片的保存路径
                String imgPath = String.format("%s/%03d.jpg", dir, i);
                mImgList.add(imgPath);
                // 打开序号为i的页面
                PdfRenderer.Page page = pdfRenderer.openPage(i);
                // 创建该页面的临时位图
                Bitmap bitmap = Bitmap.createBitmap(page.getWidth(), page.getHeight(),
                        Bitmap.Config.ARGB_8888);
                bitmap.eraseColor(Color.WHITE); // 将临时位图洗白
                // 渲染该PDF页面并写入到临时位图
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
                BitmapUtil.saveImage(imgPath, bitmap); // 把位图对象保存为图片文件
                page.close(); // 关闭该PDF页面
            }
            pdfRenderer.close(); // 处理完毕,关闭PDF渲染器
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        // 从指定路径的图片文件中获取位图数据
        Bitmap bitmap = BitmapFactory.decodeFile(mImgList.get(0));
        int iv_height = (int)(1.0*bitmap.getHeight()/bitmap.getWidth() * Utils.getScreenWidth(this));
        showImage(iv_height); // 在卷曲视图上显示位图图像
        bitmap.recycle(); // 回收位图对象
    }

    // 在卷曲视图上显示位图图像
    private void showImage(int height) {
        LayoutParams params = cv_content.getLayoutParams();
        params.height = height;
        // 设置卷曲视图的布局参数
        cv_content.setLayoutParams(params);
        // 设置卷曲视图的书页提供器
        cv_content.setPageProvider(new PageProvider(mImgList));
        // 设置卷曲视图的尺寸变更观察器
        cv_content.setSizeChangedObserver(new SizeChangedObserver());
        // 设置卷曲视图默认显示第一页
        cv_content.setCurrentIndex(0);
        // 设置卷曲视图的背景颜色
        cv_content.setBackgroundColor(Color.LTGRAY);
    }

    // 定义一个加载图片页面的提供器
    private class PageProvider implements CurlView.PageProvider {
        private ArrayList<String> mPathArray = new ArrayList<>();

        public PageProvider(ArrayList<String> pathArray) {
            mPathArray = pathArray;
        }

        @Override
        public int getPageCount() {
            return mPathArray.size();
        }

        // 在页面更新时触发
        public void updatePage(CurlPage page, int width, int height, int index) {
            // 加载指定页面的位图
            Bitmap front = BitmapFactory.decodeFile(mPathArray.get(index));
            // 设置书页的纹理
            page.setTexture(front, CurlPage.SIDE_BOTH);
        }
    }

    // 定义一个监听卷曲视图发生尺寸变更的观察器
    private class SizeChangedObserver implements CurlView.SizeChangedObserver {
        @Override
        public void onSizeChanged(int w, int h) {
            // 设置卷曲视图的观看模式
            cv_content.setViewMode(CurlView.SHOW_ONE_PAGE);
            // 设置卷曲视图的四周边缘
            cv_content.setMargins(0f, 0f, 0f, 0f);
        }
    }

}

Fichier XML

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/book_bg3"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/tl_head"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/blue_light"
        app:navigationIcon="@drawable/icon_back" />

    <ListView
        android:id="@+id/lv_ebook"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

Ce n'est pas facile à créer et à le trouver utile, veuillez aimer, suivre et collecter ~~~

Je suppose que tu aimes

Origine blog.csdn.net/jiebaoshayebuhui/article/details/127982542
conseillé
Classement