Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

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

$
0
0

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开发笔记的完整目录
作者:aqi00 发表于2017/5/4 10:08:53 原文链接
阅读:79 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>