Android App开发手机阅读中实现平滑翻书效果和卷曲翻书动画实战(附源码 简单易懂 可直接使用)

简介: Android App开发手机阅读中实现平滑翻书效果和卷曲翻书动画实战(附源码 简单易懂 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~

一、平滑翻书效果

与纸质书籍类似,手机上的电子书也有很多页,逐页浏览可采用翻页视图,然而翻页视图犹如一幅从左到右的绵长画卷,与现实生活中上下层叠的书籍并不相像,若想让手机电子书更贴近纸质书的阅读体验,就需要重新设计上下翻动的视图。书页应该具备以下视图特征

1:能够容纳图片在内的多个控件,意味着自定义视图必须由某种布局派生而来

2:书页存在两种状态 未遮挡时的高亮状态 被遮挡时的阴影状态

3:鉴于书页允许拉动 考虑给它设置左侧间距 左侧间距为零时 该页完整显示 左侧检测为负值时 该页向左缩进

效果如下图 连接真机测试食用效果更佳~~~

代码如下

Java类

package com.example.ebook;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager.widget.PagerTabStrip;
import androidx.viewpager.widget.ViewPager;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
import com.example.ebook.adapter.PdfPageAdapter;
import com.example.ebook.dao.BookDao;
import com.example.ebook.entity.BookInfo;
import com.example.ebook.util.AssetsUtil;
import java.util.ArrayList;
import java.util.List;
public class PdfRenderActivity extends AppCompatActivity {
    private final static String TAG = "PdfRenderActivity";
    private List<String> mPathList = new ArrayList<>(); // 图片路径列表
    private String mFileName = "tangshi.pdf"; // 演示文件的名称
    private ViewPager vp_content; // 声明一个翻页视图对象
    private BookDao bookDao; // 声明一个书籍的持久化对象
    private ProgressDialog mDialog; // 声明一个进度对话框对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pdf_render);
        initView(); // 初始化视图
        // 从App实例中获取唯一的书籍持久化对象
        bookDao = MainApplication.getInstance().getBookDB().bookDao();
        // 弹出进度对话框
        mDialog = ProgressDialog.show(this, "请稍候", "正在努力加载");
        new Thread(() -> importPDF()).start(); // 启动pdf文件的导入线程
    }
    // 初始化视图
    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);
        setSupportActionBar(tl_head); // 替换系统自带的ActionBar
        // 设置工具栏左侧导航图标的点击监听器
        tl_head.setNavigationOnClickListener(view -> finish());
        vp_content = findViewById(R.id.vp_content);
        PagerTabStrip pts_tab = findViewById(R.id.pts_tab);
        // 设置翻页标题栏的文本大小
        pts_tab.setTextSize(TypedValue.COMPLEX_UNIT_SP, 17);
    }
    // 从指定的资产文件导入pdf文件
    private void importPDF() {
        mPathList = AssetsUtil.getPathListFromPdf(this, mFileName);
        Log.d(TAG, "mPathList.size="+mPathList.size());
        BookInfo book = bookDao.queryBookByName(mFileName);
        if (book != null) {
            book.setPageCount(mPathList.size());
            bookDao.updateBook(book); // 更新数据库中该书籍记录的总页数
        }
        // 回到主线程显示导入后的pdf各页面
        runOnUiThread(() -> {
            PdfPageAdapter adapter = new PdfPageAdapter(getSupportFragmentManager(), mPathList);
            vp_content.setAdapter(adapter);
            if (mDialog != null && mDialog.isShowing()) {
                mDialog.dismiss(); // 关闭进度对话框
            }
        });
    }
    // 在创建选项菜单时调用
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_book, menu);
        return true;
    }
    // 在选中菜单项时调用
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.menu_slide) { // 点击了“平滑翻页”
            Intent intent = new Intent(this, PdfSlideActivity.class);
            intent.putExtra("file_name", mFileName);
            startActivity(intent);
        } else if (item.getItemId() == R.id.menu_curve) { // 点击了“卷曲翻页”
            Intent intent = new Intent(this, PdfCurveActivity.class);
            intent.putExtra("file_name", mFileName);
            startActivity(intent);
        } else if (item.getItemId() == R.id.menu_opengl) { // 点击了“OpenGL翻页”
            Intent intent = new Intent(this, PdfOpenglActivity.class);
            intent.putExtra("file_name", mFileName);
            startActivity(intent);
        }
        return super.onOptionsItemSelected(item);
    }
}

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: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" />
    <com.example.ebook.widget.ViewSlider
        android:id="@+id/vs_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

二、实现卷曲翻书动画

前面介绍的平滑翻书固然实现了层叠翻页,可是该方式依然无法模拟现实生活中的翻书动画,现实当中每翻过一页,手指捏住书页的右下角,然后轻轻的往左上方翻,可以看出翻书的效果映射到平面上可以划分为三个区域,A区域为当前正在翻的页面,B区域为当前页的背面,C区域为露出来的下一页。

鉴于贝塞尔曲线的柔韧特性,可将其应用于翻书时的卷曲线条,其中直线通过首位两个端点连接起来

至此 翻书效果还剩下两个功能点有待实现 说明如下

1:在手指触摸的过程中 要实时计算各个坐标点的位置 并调整书页的画面绘制

2:手指松开之后 要判断接下来是往前翻页还是往后缩回去,并在前翻与后缩的过程中展示翻书动画

第二点可以借助滚动其Scroller实现,第一点则需要重写onTouchEvent方法,分别处理手指按下,易懂,松开三种情况的视图变迁

实现效果如下 连接真机测试效果更佳

 

代码如下

Java类

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); // 设置卷曲视图的文件路径
    }
}

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: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" />
    <Button
        android:id="@+id/btn_resume"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="恢复原状"
        android:textColor="@color/black"
        android:textSize="17sp"
        android:visibility="gone" />
    <com.example.ebook.widget.CurveView
        android:id="@+id/cv_book"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

相关文章
|
5天前
|
JSON 编译器 开发工具
VS Code阅读Android源码
VS Code阅读Android源码
11 1
|
1月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
89 3
|
1月前
|
Android开发 开发者 UED
个人开发 App 成功上架手机应用市场的关键步骤
个人开发 App 成功上架手机应用市场的关键步骤
|
1月前
|
开发工具 数据安全/隐私保护 Android开发
【教程】APP 开发后如何上架?
【教程】APP 开发后如何上架?
|
1月前
|
API
uni-app 146朋友圈列表api开发
uni-app 146朋友圈列表api开发
18 0
|
5天前
|
测试技术 Android开发
Android App获取不到pkgInfo信息问题原因
Android App获取不到pkgInfo信息问题原因
14 0
|
1月前
|
缓存 移动开发 Java
构建高效Android应用:内存优化实战指南
在移动开发领域,性能优化是提升用户体验的关键因素之一。特别是对于Android应用而言,由于设备和版本的多样性,内存管理成为开发者面临的一大挑战。本文将深入探讨Android内存优化的策略和技术,包括内存泄漏的诊断与解决、合理的数据结构选择、以及有效的资源释放机制。通过实际案例分析,我们旨在为开发者提供一套实用的内存优化工具和方法,以构建更加流畅和高效的Android应用。
|
1月前
|
Java Android开发 开发者
【Uniapp开发】APP的真机调试指南,从开发到上架全过程
【Uniapp开发】APP的真机调试指南,从开发到上架全过程
36 3
游戏直播APP平台开发多少钱成本:定制与成品源码差距这么大
开发一款游戏直播APP平台所需的费用是多少?对于计划投身这一领域的投资者来说,首要关心的问题之一就是。本文将探讨两种主要的开发模式——定制开发与成品源码二次开发的成本差异及其优劣势。
|
1月前
|
开发框架 移动开发 JavaScript
SpringCloud微服务实战——搭建企业级开发框架(四十六):【移动开发】整合uni-app搭建移动端快速开发框架-环境搭建
正如优秀的软件设计一样,uni-app把一些移动端常用的功能做成了独立的服务或者插件,我们在使用的时候只需要选择使用即可。但是在使用这些服务或者插件时一定要区分其提供的各种服务和插件的使用场景,例如其提供的【uni-starter快速开发项目模版】几乎集成了移动端所需的所有基础功能,使用非常方便,但是其许可协议只允许对接其uniCloud的JS开发服务端,不允许对接自己的php、java等其他后台系统。
145 2