需要图片集和源码请点赞关注收藏后评论区留言~~~
一、平滑翻书效果
与纸质书籍类似,手机上的电子书也有很多页,逐页浏览可采用翻页视图,然而翻页视图犹如一幅从左到右的绵长画卷,与现实生活中上下层叠的书籍并不相像,若想让手机电子书更贴近纸质书的阅读体验,就需要重新设计上下翻动的视图。书页应该具备以下视图特征
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>
创作不易 觉得有帮助请点赞关注收藏~~~