版权声明:本文为博主原创文章,未经博主允许不得转载。 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文件的代码举例如下:
-
ParcelFileDescriptor fd = ParcelFileDescriptor.open(
-
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文件的效果图:
![](https://i-blog.csdnimg.cn/blog_migrate/fe535ba7c432c64a48779eed863294b0.gif)
下面是使用PdfRenderer读取pdf文件的主要代码:
-
String dir = Environment.getExternalStorageDirectory().getAbsolutePath() +
-
"/Download/pdf/" + MD5Util.encrypByMd5(path);
-
ArrayList<String> imgArray =
new ArrayList<String>();
-
try {
-
ParcelFileDescriptor fd = ParcelFileDescriptor.open(
-
new File(path), ParcelFileDescriptor.MODE_READ_ONLY);
-
PdfRenderer pdfRenderer =
new PdfRenderer(fd);
-
for (
int i=
0; i<pdfRenderer.getPageCount(); i++) {
-
String imgPath = String.format(
"%s/%d.jpg", dir, i);
-
imgArray.add(imgPath);
-
final PdfRenderer.Page page = pdfRenderer.openPage(i);
-
Bitmap bitmap = Bitmap.createBitmap(page.getWidth(), page.getHeight(),
-
Bitmap.Config.ARGB_8888);
-
page.render(bitmap,
null,
null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
-
FileUtil.saveBitmap(imgPath, bitmap);
-
page.close();
-
}
-
pdfRenderer.close();
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
-
PdfSelfAdapter adapter =
new PdfSelfAdapter(getSupportFragmentManager(), imgArray);
-
vp_content.setAdapter(adapter);
-
vp_content.setCurrentItem(
0);
-
vp_content.setVisibility(View.VISIBLE);
栈视图StackView
因为PDF文件本质上是一本书籍,所以在手机上浏览PDF页面,用户更习惯从上到下的层叠显示,而不是ViewPager那种从左到右的画卷方式。在Android的控件家族当中,比较接近上下层叠方式的是栈视图StackView,它的前后两项视图有部分是重叠在一起的,然后可以通过上下滑动来切换当前显示的顶层视图。StackView的使用方式类似于ListView,都是调用setAdapter方法设置一组子项,多出来的属性只有loopViews,该属性用于控制是否循环显示子项视图。
下面是使用StackView浏览pdf页面的效果图:
![](https://i-blog.csdnimg.cn/blog_migrate/e5f20e8e21c1aaf827c6d2a8756e369a.gif)
层叠翻页效果
上面提到的StackView,仍然不完全符合现实生活中的书页排列,比如上下两页只是部分区域重叠不是完全覆盖,另外前后页面是通过上下滑动切换而不是通过左右滑动切换,所以要想实现现实生活中的层叠翻页效果,还是得自定义书籍页面的控件。自定义层叠翻页控件,可借鉴ViewFlipper的实现,首先定义一个总体的框架视图,用于存放当前页面与前后两页;其次定义具体页面的视图,每个页面视图展示一个PDF页面。框架视图主要负责两块工作:
1、接管屏幕上的触摸事件,通知当前的页面视图向左或者向右滑动,并在松开手势时判断接下来是继续翻页,还是恢复原状;
2、在翻页结束时,在屏幕上重新组织当前页面与前后两页,类似于ViewPager+Fragment的三页缓存机制;
页面视图主要负责三块工作:
1、将当前页面高亮显示,其它页面变暗显示;
2、按照用户的手势触摸,将当前页面滑动相应的距离;
3、在用户松开手势时,如果当前页面滑动距离不超过页面宽度的二分之一,则将当前页滑动到原来的位置;如果当前页面滑动距离超过页面宽度的二分之一,则将当前页滑动到原来的相反位置,即原来是显示着的则现在隐藏,原来是隐藏着的则现在显示。
下面是层叠翻页的效果图:
![](https://i-blog.csdnimg.cn/blog_migrate/5b191f925cefea7c0c62b7192143f08c.gif)
下面是层叠翻页的框架视图代码:
-
public
class ViewSlider extends FrameLayout implements BookView.OnScrollListener {
-
private
final
static String TAG =
"ViewSlider";
-
private Context mContext;
-
private
int mWidth, mHeight;
-
private
float rawX =
0;
-
private ArrayList<String> mPathArray =
new ArrayList<String>();
-
private
int mPos =
0;
-
private BookView mPreView, mCurrentView, mNextView;
-
private
int mShowPage;
-
private
static
int SHOW_NONE =
0;
-
private
static
int SHOW_PRE =
1;
-
private
static
int SHOW_NEXT =
2;
-
private
boolean isScroll =
false;
-
-
public ViewSlider(Context context) {
-
this(context,
null);
-
}
-
-
public ViewSlider(Context context, AttributeSet attrs) {
-
this(context, attrs,
0);
-
}
-
-
public ViewSlider(Context context, AttributeSet attrs, int defStyleAttr) {
-
super(context, attrs, defStyleAttr);
-
mContext = context;
-
}
-
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
mWidth = getMeasuredWidth();
-
mHeight = getMeasuredHeight();
-
}
-
-
public void setFilePath(ArrayList<String> pathArray) {
-
removeAllViews();
-
mPathArray = pathArray;
-
if (mPathArray.size() >
0) {
-
mCurrentView = getBookPage(
0,
true);
-
addView(mCurrentView);
-
}
-
if (mPathArray.size() >
1) {
-
mNextView = getBookPage(
1,
false);
-
addView(mNextView,
0);
-
}
-
}
-
-
private BookView getBookPage(int position, boolean isUp) {
-
BookView page =
new BookView(mContext);
-
MarginLayoutParams params =
new LinearLayout.LayoutParams(
-
mWidth, LayoutParams.WRAP_CONTENT);
-
page.setLayoutParams(params);
-
ImageView iv =
new ImageView(mContext);
-
iv.setLayoutParams(params);
-
iv.setScaleType(ScaleType.FIT_CENTER);
-
iv.setImageBitmap(BitmapFactory.decodeFile(mPathArray.get(position)));
-
page.addView(iv);
-
page.setUp(isUp);
-
return page;
-
}
-
-
@Override
-
public boolean onTouchEvent(MotionEvent event) {
-
if (isScroll) {
-
return
super.onTouchEvent(event);
-
}
-
int distanceX = (
int) (event.getRawX() - rawX);
-
Log.d(TAG,
"action="+event.getAction()+
", distanceX="+distanceX);
-
switch (event.getAction()) {
-
case MotionEvent.ACTION_DOWN:
-
rawX = event.getRawX();
-
break;
-
case MotionEvent.ACTION_MOVE:
-
if (distanceX >
0) {
//展示上一页
-
if (mPos ==
0) {
-
mShowPage = SHOW_NONE;
-
}
else {
-
mShowPage = SHOW_PRE;
-
mPreView.setUp(
true);
-
mPreView.setMargin(-mWidth + distanceX);
-
mCurrentView.setUp(
false);
-
}
-
}
else {
//展示下一页
-
if (mPos == mPathArray.size()-
1 || mNextView==
null) {
-
mShowPage = SHOW_NONE;
-
}
else
if (mNextView !=
null) {
-
mShowPage = SHOW_NEXT;
-
mCurrentView.setMargin(distanceX);
-
}
-
}
-
break;
-
case MotionEvent.ACTION_UP:
-
if (mShowPage == SHOW_PRE) {
-
int direction = Math.abs(distanceX)<mWidth/
2 ? BookView.DIRECTION_LEFT : BookView.DIRECTION_RIGHT;
-
//Log.d(TAG, "direction="+direction+", mShowPage="+mShowPage+", distanceX="+distanceX);
-
mPreView.scrollView(direction, -mWidth+distanceX,
this);
-
isScroll =
true;
-
}
else
if (mShowPage == SHOW_NEXT) {
-
int direction = Math.abs(distanceX)>mWidth/
2 ? BookView.DIRECTION_LEFT : BookView.DIRECTION_RIGHT;
-
//Log.d(TAG, "direction="+direction+", mShowPage="+mShowPage+", distanceX="+distanceX);
-
mCurrentView.scrollView(direction, distanceX,
this);
-
isScroll =
true;
-
}
else {
-
isScroll =
false;
-
}
-
break;
-
}
-
return
true;
-
}
-
-
@Override
-
public void onScrollEnd(int direction) {
-
//Log.d(TAG, "direction="+direction+", mPos="+mPos);
-
if (mShowPage == SHOW_PRE) {
-
if (direction == BookView.DIRECTION_RIGHT) {
-
mPos--;
-
if (mNextView !=
null) {
-
removeView(mNextView);
-
}
-
mNextView = mCurrentView;
-
mCurrentView = mPreView;
-
if (mPos >
0) {
-
mPreView = getBookPage(mPos-
1,
false);
-
addView(mPreView);
-
mPreView.setMargin(-mWidth);
-
}
else {
-
mPreView =
null;
-
}
-
}
-
mCurrentView.setUp(
true);
-
}
else
if (mShowPage == SHOW_NEXT) {
-
if (direction == BookView.DIRECTION_LEFT) {
-
mPos++;
-
if (mPreView !=
null) {
-
removeView(mPreView);
-
}
-
mPreView = mCurrentView;
-
mCurrentView = mNextView;
-
if (mPos < mPathArray.size()-
1) {
-
mNextView = getBookPage(mPos+
1,
false);
-
addView(mNextView,
0);
-
}
else {
-
mNextView =
null;
-
}
-
}
-
mCurrentView.setUp(
true);
-
}
-
isScroll =
false;
-
}
-
-
}
下面是层叠翻页的页面视图代码:
-
public
class BookView extends FrameLayout {
-
private
final
static String TAG =
"BookView";
-
private Context mContext;
-
private
int mWidth, mHeight;
-
private
boolean mIsUp =
false;
-
private MarginLayoutParams mParams;
-
public
static
int DIRECTION_LEFT = -
1;
-
public
static
int DIRECTION_RIGHT =
1;
-
-
public BookView(Context context) {
-
super(context);
-
mContext = context;
-
}
-
-
@Override
-
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
mWidth = getMeasuredWidth();
-
mHeight = getMeasuredHeight();
-
}
-
-
@Override
-
protected void dispatchDraw(Canvas canvas) {
-
super.dispatchDraw(canvas);
-
if (mIsUp) {
-
canvas.drawColor(Color.TRANSPARENT);
-
}
else {
-
canvas.drawColor(
0x55000000);
-
}
-
}
-
-
public void setUp(boolean isUp) {
-
mIsUp = isUp;
-
invalidate();
-
}
-
-
public void setMargin(int margin) {
-
mParams = (MarginLayoutParams) getLayoutParams();
-
mParams.leftMargin = margin;
-
setLayoutParams(mParams);
-
invalidate();
-
}
-
-
public void scrollView(int direction, int distance, OnScrollListener listener) {
-
mListener = listener;
-
mHandler.postDelayed(
new ScrollRunnable(direction, distance), mTimeGap);
-
}
-
-
private OnScrollListener mListener;
-
public
static
interface OnScrollListener {
-
public abstract void onScrollEnd(int direction);
-
}
-
-
private
int mTimeGap =
20;
-
private
int mDistanceGap =
20;
-
private Handler mHandler =
new Handler();
-
private
class ScrollRunnable implements Runnable {
-
private
int mDirection;
-
private
int mDistance;
-
public ScrollRunnable(int direction, int distance) {
-
mDirection = direction;
-
mDistance = distance;
-
}
-
-
@Override
-
public void run() {
-
if (mDirection==DIRECTION_LEFT && mDistance>-mWidth) {
-
mDistance -= mDistanceGap;
-
if (mDistance < -mWidth) {
-
mDistance = -mWidth;
-
}
-
mParams.leftMargin = mDistance;
-
setLayoutParams(mParams);
-
mHandler.postDelayed(
new ScrollRunnable(mDirection, mDistance), mTimeGap);
-
}
else
if (mDirection==DIRECTION_RIGHT && mDistance<
0) {
-
mDistance += mDistanceGap;
-
if (mDistance >
0) {
-
mDistance =
0;
-
}
-
mParams.leftMargin = mDistance;
-
setLayoutParams(mParams);
-
mHandler.postDelayed(
new ScrollRunnable(mDirection, mDistance), mTimeGap);
-
}
else
if (mListener !=
null) {
-
mListener.onScrollEnd(mDirection);
-
}
-
}
-
}
-
}
另外有种自然翻页效果,也就是书页卷起来翻动,这个翻页动画参见以前的博文《 Android开发笔记(十八)书籍翻页动画》。
点击下载本文用到的层叠翻页的书籍浏览代码
点此查看Android开发笔记的完整目录