Android开发笔记(一百四十二)平滑翻页的书籍浏览

xiaoxiao2021-02-27  371

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文件的效果图: 下面是使用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页面的效果图:

层叠翻页效果

上面提到的StackView,仍然不完全符合现实生活中的书页排列,比如上下两页只是部分区域重叠不是完全覆盖,另外前后页面是通过上下滑动切换而不是通过左右滑动切换,所以要想实现现实生活中的层叠翻页效果,还是得自定义书籍页面的控件。 自定义层叠翻页控件,可借鉴ViewFlipper的实现,首先定义一个总体的框架视图,用于存放当前页面与前后两页;其次定义具体页面的视图,每个页面视图展示一个PDF页面。框架视图主要负责两块工作: 1、接管屏幕上的触摸事件,通知当前的页面视图向左或者向右滑动,并在松开手势时判断接下来是继续翻页,还是恢复原状; 2、在翻页结束时,在屏幕上重新组织当前页面与前后两页,类似于ViewPager+Fragment的三页缓存机制; 页面视图主要负责三块工作: 1、将当前页面高亮显示,其它页面变暗显示; 2、按照用户的手势触摸,将当前页面滑动相应的距离; 3、在用户松开手势时,如果当前页面滑动距离不超过页面宽度的二分之一,则将当前页滑动到原来的位置;如果当前页面滑动距离超过页面宽度的二分之一,则将当前页滑动到原来的相反位置,即原来是显示着的则现在隐藏,原来是隐藏着的则现在显示。 下面是层叠翻页的效果图: 下面是层叠翻页的框架视图代码: 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开发笔记的完整目录
转载请注明原文地址: https://www.6miu.com/read-1169.html

最新回复(0)