PDF文件渲染

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aqi00/article/details/71158380

PDF文件渲染PdfRenderer

在前面的博文中,讲到可以通过Vudroid和MuPDF读取PDF文件,可是这两个开源框架都要使用jni编译出so库,不但步骤繁琐,而且兼容性也有欠缺。幸好Android在5.0后就开始支持PDF文件的读取,直接在内核中集成了PDF的渲染操作,很大程度上方便了开发者,这个内核中的PDF管理工具便是PdfRenderer。

PdfRenderer允许从多个来源读取PDF文件,不同来源的PDF文件打开操作由ParcelFileDescriptor完成,该类的对象可以通过两种方式获得,一种方式是从assets目录下读取pdf文件,另一种方式是从存储卡上读取pdf文件。
从assets目录下读取pdf文件的代码举例如下:
ParcelFileDescriptor fd = getAssets().openFd("example.pdf").getParcelFileDescriptor();
   
   
从存储卡上读取pdf文件的代码举例如下:

   
   
  1. ParcelFileDescriptor fd = ParcelFileDescriptor.open(
  2. new File( "example.pdf"), ParcelFileDescriptor.MODE_READ_ONLY);

打开PDF文件只是第一步,接下来还要使用PdfRenderer加载pdf文件,并进行相关的处理操作,PdfRenderer的常用方法说明如下:
构造函数:从ParcelFileDescriptor对象构造一个PdfRenderer实例。
getPageCount:获取PDF文件的页数。
openPage:打开PDF文件的指定页面,该方法返回一个PdfRenderer.Page对象。
close:关闭PDF文件。

从上面列出的方法看到,PdfRenderer只是提供了对整个PDF文件的管理操作,具体页面的处理比如渲染得由PdfRenderer.Page对象来完成,下面是Page的常用方法说明:
getIndex:获取该页的页码。
getWidth:获取该页的宽度。
getHeight:获取该页的高度。
render:渲染该页面的内容,并将渲染结果写入到一个Bitmap位图对象中。开发者可在此把Bitmap对象保存为存储卡上的图片文件。
close:关闭该pdf页。

总而言之,PdfRenderer的作用就是把一个pdf文件转换为若干个图片,然后开发者可将这些图片展示到手机屏幕上。下面是使用PdfRenderer读取并显示pdf文件的效果图:


下面是使用PdfRenderer读取pdf文件的主要代码:

   
   
  1. String dir = Environment.getExternalStorageDirectory().getAbsolutePath() +
  2. "/Download/pdf/" + MD5Util.encrypByMd5(path);
  3. ArrayList<String> imgArray = new ArrayList<String>();
  4. try {
  5. ParcelFileDescriptor fd = ParcelFileDescriptor.open(
  6. new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
  7. PdfRenderer pdfRenderer = new PdfRenderer(fd);
  8. for ( int i= 0; i<pdfRenderer.getPageCount(); i++) {
  9. String imgPath = String.format( "%s/%d.jpg", dir, i);
  10. imgArray.add(imgPath);
  11. final PdfRenderer.Page page = pdfRenderer.openPage(i);
  12. Bitmap bitmap = Bitmap.createBitmap(page.getWidth(), page.getHeight(),
  13. Bitmap.Config.ARGB_8888);
  14. page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
  15. FileUtil.saveBitmap(imgPath, bitmap);
  16. page.close();
  17. }
  18. pdfRenderer.close();
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. PdfSelfAdapter adapter = new PdfSelfAdapter(getSupportFragmentManager(), imgArray);
  23. vp_content.setAdapter(adapter);
  24. vp_content.setCurrentItem( 0);
  25. vp_content.setVisibility(View.VISIBLE);


栈视图StackView

因为PDF文件本质上是一本书籍,所以在手机上浏览PDF页面,用户更习惯从上到下的层叠显示,而不是ViewPager那种从左到右的画卷方式。在Android的控件家族当中,比较接近上下层叠方式的是栈视图StackView,它的前后两项视图有部分是重叠在一起的,然后可以通过上下滑动来切换当前显示的顶层视图。

StackView的使用方式类似于ListView,都是调用setAdapter方法设置一组子项,多出来的属性只有loopViews,该属性用于控制是否循环显示子项视图。

下面是使用StackView浏览pdf页面的效果图:



层叠翻页效果

上面提到的StackView,仍然不完全符合现实生活中的书页排列,比如上下两页只是部分区域重叠不是完全覆盖,另外前后页面是通过上下滑动切换而不是通过左右滑动切换,所以要想实现现实生活中的层叠翻页效果,还是得自定义书籍页面的控件。

自定义层叠翻页控件,可借鉴ViewFlipper的实现,首先定义一个总体的框架视图,用于存放当前页面与前后两页;其次定义具体页面的视图,每个页面视图展示一个PDF页面。框架视图主要负责两块工作:
1、接管屏幕上的触摸事件,通知当前的页面视图向左或者向右滑动,并在松开手势时判断接下来是继续翻页,还是恢复原状;
2、在翻页结束时,在屏幕上重新组织当前页面与前后两页,类似于ViewPager+Fragment的三页缓存机制;
页面视图主要负责三块工作:
1、将当前页面高亮显示,其它页面变暗显示;
2、按照用户的手势触摸,将当前页面滑动相应的距离;
3、在用户松开手势时,如果当前页面滑动距离不超过页面宽度的二分之一,则将当前页滑动到原来的位置;如果当前页面滑动距离超过页面宽度的二分之一,则将当前页滑动到原来的相反位置,即原来是显示着的则现在隐藏,原来是隐藏着的则现在显示。

下面是层叠翻页的效果图:



下面是层叠翻页的框架视图代码:

   
   
  1. public class ViewSlider extends FrameLayout implements BookView.OnScrollListener {
  2. private final static String TAG = "ViewSlider";
  3. private Context mContext;
  4. private int mWidth, mHeight;
  5. private float rawX = 0;
  6. private ArrayList<String> mPathArray = new ArrayList<String>();
  7. private int mPos = 0;
  8. private BookView mPreView, mCurrentView, mNextView;
  9. private int mShowPage;
  10. private static int SHOW_NONE = 0;
  11. private static int SHOW_PRE = 1;
  12. private static int SHOW_NEXT = 2;
  13. private boolean isScroll = false;
  14. public ViewSlider(Context context) {
  15. this(context, null);
  16. }
  17. public ViewSlider(Context context, AttributeSet attrs) {
  18. this(context, attrs, 0);
  19. }
  20. public ViewSlider(Context context, AttributeSet attrs, int defStyleAttr) {
  21. super(context, attrs, defStyleAttr);
  22. mContext = context;
  23. }
  24. @Override
  25. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  26. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  27. mWidth = getMeasuredWidth();
  28. mHeight = getMeasuredHeight();
  29. }
  30. public void setFilePath(ArrayList<String> pathArray) {
  31. removeAllViews();
  32. mPathArray = pathArray;
  33. if (mPathArray.size() > 0) {
  34. mCurrentView = getBookPage( 0, true);
  35. addView(mCurrentView);
  36. }
  37. if (mPathArray.size() > 1) {
  38. mNextView = getBookPage( 1, false);
  39. addView(mNextView, 0);
  40. }
  41. }
  42. private BookView getBookPage(int position, boolean isUp) {
  43. BookView page = new BookView(mContext);
  44. MarginLayoutParams params = new LinearLayout.LayoutParams(
  45. mWidth, LayoutParams.WRAP_CONTENT);
  46. page.setLayoutParams(params);
  47. ImageView iv = new ImageView(mContext);
  48. iv.setLayoutParams(params);
  49. iv.setScaleType(ScaleType.FIT_CENTER);
  50. iv.setImageBitmap(BitmapFactory.decodeFile(mPathArray.get(position)));
  51. page.addView(iv);
  52. page.setUp(isUp);
  53. return page;
  54. }
  55. @Override
  56. public boolean onTouchEvent(MotionEvent event) {
  57. if (isScroll) {
  58. return super.onTouchEvent(event);
  59. }
  60. int distanceX = ( int) (event.getRawX() - rawX);
  61. Log.d(TAG, "action="+event.getAction()+ ", distanceX="+distanceX);
  62. switch (event.getAction()) {
  63. case MotionEvent.ACTION_DOWN:
  64. rawX = event.getRawX();
  65. break;
  66. case MotionEvent.ACTION_MOVE:
  67. if (distanceX > 0) { //展示上一页
  68. if (mPos == 0) {
  69. mShowPage = SHOW_NONE;
  70. } else {
  71. mShowPage = SHOW_PRE;
  72. mPreView.setUp( true);
  73. mPreView.setMargin(-mWidth + distanceX);
  74. mCurrentView.setUp( false);
  75. }
  76. } else { //展示下一页
  77. if (mPos == mPathArray.size()- 1 || mNextView== null) {
  78. mShowPage = SHOW_NONE;
  79. } else if (mNextView != null) {
  80. mShowPage = SHOW_NEXT;
  81. mCurrentView.setMargin(distanceX);
  82. }
  83. }
  84. break;
  85. case MotionEvent.ACTION_UP:
  86. if (mShowPage == SHOW_PRE) {
  87. int direction = Math.abs(distanceX)<mWidth/ 2 ? BookView.DIRECTION_LEFT : BookView.DIRECTION_RIGHT;
  88. //Log.d(TAG, "direction="+direction+", mShowPage="+mShowPage+", distanceX="+distanceX);
  89. mPreView.scrollView(direction, -mWidth+distanceX, this);
  90. isScroll = true;
  91. } else if (mShowPage == SHOW_NEXT) {
  92. int direction = Math.abs(distanceX)>mWidth/ 2 ? BookView.DIRECTION_LEFT : BookView.DIRECTION_RIGHT;
  93. //Log.d(TAG, "direction="+direction+", mShowPage="+mShowPage+", distanceX="+distanceX);
  94. mCurrentView.scrollView(direction, distanceX, this);
  95. isScroll = true;
  96. } else {
  97. isScroll = false;
  98. }
  99. break;
  100. }
  101. return true;
  102. }
  103. @Override
  104. public void onScrollEnd(int direction) {
  105. //Log.d(TAG, "direction="+direction+", mPos="+mPos);
  106. if (mShowPage == SHOW_PRE) {
  107. if (direction == BookView.DIRECTION_RIGHT) {
  108. mPos--;
  109. if (mNextView != null) {
  110. removeView(mNextView);
  111. }
  112. mNextView = mCurrentView;
  113. mCurrentView = mPreView;
  114. if (mPos > 0) {
  115. mPreView = getBookPage(mPos- 1, false);
  116. addView(mPreView);
  117. mPreView.setMargin(-mWidth);
  118. } else {
  119. mPreView = null;
  120. }
  121. }
  122. mCurrentView.setUp( true);
  123. } else if (mShowPage == SHOW_NEXT) {
  124. if (direction == BookView.DIRECTION_LEFT) {
  125. mPos++;
  126. if (mPreView != null) {
  127. removeView(mPreView);
  128. }
  129. mPreView = mCurrentView;
  130. mCurrentView = mNextView;
  131. if (mPos < mPathArray.size()- 1) {
  132. mNextView = getBookPage(mPos+ 1, false);
  133. addView(mNextView, 0);
  134. } else {
  135. mNextView = null;
  136. }
  137. }
  138. mCurrentView.setUp( true);
  139. }
  140. isScroll = false;
  141. }
  142. }


下面是层叠翻页的页面视图代码:

   
   
  1. public class BookView extends FrameLayout {
  2. private final static String TAG = "BookView";
  3. private Context mContext;
  4. private int mWidth, mHeight;
  5. private boolean mIsUp = false;
  6. private MarginLayoutParams mParams;
  7. public static int DIRECTION_LEFT = - 1;
  8. public static int DIRECTION_RIGHT = 1;
  9. public BookView(Context context) {
  10. super(context);
  11. mContext = context;
  12. }
  13. @Override
  14. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  15. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  16. mWidth = getMeasuredWidth();
  17. mHeight = getMeasuredHeight();
  18. }
  19. @Override
  20. protected void dispatchDraw(Canvas canvas) {
  21. super.dispatchDraw(canvas);
  22. if (mIsUp) {
  23. canvas.drawColor(Color.TRANSPARENT);
  24. } else {
  25. canvas.drawColor( 0x55000000);
  26. }
  27. }
  28. public void setUp(boolean isUp) {
  29. mIsUp = isUp;
  30. invalidate();
  31. }
  32. public void setMargin(int margin) {
  33. mParams = (MarginLayoutParams) getLayoutParams();
  34. mParams.leftMargin = margin;
  35. setLayoutParams(mParams);
  36. invalidate();
  37. }
  38. public void scrollView(int direction, int distance, OnScrollListener listener) {
  39. mListener = listener;
  40. mHandler.postDelayed( new ScrollRunnable(direction, distance), mTimeGap);
  41. }
  42. private OnScrollListener mListener;
  43. public static interface OnScrollListener {
  44. public abstract void onScrollEnd(int direction);
  45. }
  46. private int mTimeGap = 20;
  47. private int mDistanceGap = 20;
  48. private Handler mHandler = new Handler();
  49. private class ScrollRunnable implements Runnable {
  50. private int mDirection;
  51. private int mDistance;
  52. public ScrollRunnable(int direction, int distance) {
  53. mDirection = direction;
  54. mDistance = distance;
  55. }
  56. @Override
  57. public void run() {
  58. if (mDirection==DIRECTION_LEFT && mDistance>-mWidth) {
  59. mDistance -= mDistanceGap;
  60. if (mDistance < -mWidth) {
  61. mDistance = -mWidth;
  62. }
  63. mParams.leftMargin = mDistance;
  64. setLayoutParams(mParams);
  65. mHandler.postDelayed( new ScrollRunnable(mDirection, mDistance), mTimeGap);
  66. } else if (mDirection==DIRECTION_RIGHT && mDistance< 0) {
  67. mDistance += mDistanceGap;
  68. if (mDistance > 0) {
  69. mDistance = 0;
  70. }
  71. mParams.leftMargin = mDistance;
  72. setLayoutParams(mParams);
  73. mHandler.postDelayed( new ScrollRunnable(mDirection, mDistance), mTimeGap);
  74. } else if (mListener != null) {
  75. mListener.onScrollEnd(mDirection);
  76. }
  77. }
  78. }
  79. }


另外有种自然翻页效果,也就是书页卷起来翻动,这个翻页动画参见以前的博文《 Android开发笔记(十八)书籍翻页动画》。


点击下载本文用到的层叠翻页的书籍浏览代码


点此查看Android开发笔记的完整目录
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值