前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android开发笔记(一百四十二)平滑翻页的书籍浏览

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

作者头像
aqi00
发布2019-01-18 15:12:48
1.2K0
发布2019-01-18 15:12:48
举报
文章被收录于专栏:老欧说安卓

PDF文件渲染PdfRenderer

在前面的博文中,讲到可以通过Vudroid和MuPDF读取PDF文件,可是这两个开源框架都要使用jni编译出so库,不但步骤繁琐,而且兼容性也有欠缺。幸好Android在5.0后就开始支持PDF文件的读取,直接在内核中集成了PDF的渲染操作,很大程度上方便了开发者,这个内核中的PDF管理工具便是PdfRenderer。 PdfRenderer允许从多个来源读取PDF文件,不同来源的PDF文件打开操作由ParcelFileDescriptor完成,该类的对象可以通过两种方式获得,一种方式是从assets目录下读取pdf文件,另一种方式是从存储卡上读取pdf文件。 从assets目录下读取pdf文件的代码举例如下:

代码语言:javascript
复制
ParcelFileDescriptor fd = getAssets().openFd("example.pdf").getParcelFileDescriptor();

从存储卡上读取pdf文件的代码举例如下:

代码语言:javascript
复制
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文件的主要代码:

代码语言:javascript
复制
		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、在用户松开手势时,如果当前页面滑动距离不超过页面宽度的二分之一,则将当前页滑动到原来的位置;如果当前页面滑动距离超过页面宽度的二分之一,则将当前页滑动到原来的相反位置,即原来是显示着的则现在隐藏,原来是隐藏着的则现在显示。 下面是层叠翻页的效果图:

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

代码语言:javascript
复制
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;
	}

}

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

代码语言:javascript
复制
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开发笔记的完整目录

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年05月04日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • PDF文件渲染PdfRenderer
  • 栈视图StackView
  • 层叠翻页效果
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档