最近由于需要实现Android上的书籍翻页效果,于是就在CSDN上找到了何明桂(http://blog.csdn.net/hmg25)的一个系列文章,在此感谢大神的无私奉献。具体原理何大神已经将的很清楚了,具体请看

Android 实现书籍翻页效果----原理篇

Android 实现书籍翻页效果----完结篇

Android 实现书籍翻页效果----升级篇

Android 实现书籍翻页效果---番外篇之光影效果

在此基础上我做了一些修改,

1、将其改写为一个FrameLayout,可以通过BaseAdapter添加其他的布局文件;

2、从中间分页,采用两页的结构;

效果如下


具体的思路还是通过计算翻页过程中各个视图的显示区域,然后控制canvas的绘制过程。何大神实现了将文字转化为相应的图片,之后交给canvas绘制在屏幕上。那么控件或者布局该如何绘制呢?其实控件和布局本质都是view,他们的绘制过程最终都是通过canvas的draw方法绘制在屏幕上的,而且view的绘制是通过调用draw(canvas)方法实现,(view视图绘制原理请看->http://blog.csdn.net/qinjuning/article/details/7110211),因此就可以通过控制canvas来绘制不同的显示区域。

分析一下:

首先,FramLayout绘制过程会调用onDraw(),在onDraw里会调用dispatchDraw()用于绘制子视图,在dispatchDraw里又会调用drawChild()来分别绘制各个子视图,因此我们需要在这里控制一下canvas。

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {// TODO Auto-generated method stubif(child.equals(currentView)) {drawCurrentPageArea(canvas, child, mPath0);} else {drawNextPageAreaAndShadow(canvas, child);}return true;}

其中在drawCurrentPageArea和drawNextPageArea里都会对canvas进行处理,如下

private void drawCurrentPageArea(Canvas canvas, View child, Path path) {mPath0.reset();mPath0.moveTo(mBezierStart1.x, mBezierStart1.y);mPath0.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x,mBezierEnd1.y);mPath0.lineTo(mTouch.x, mTouch.y);mPath0.lineTo(mBezierEnd2.x, mBezierEnd2.y);mPath0.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x,mBezierStart2.y);mPath0.lineTo(mCornerX, mCornerY);mPath0.close();canvas.save();canvas.clipPath(path, Region.Op.XOR);//这里即裁剪出了当前页应该绘制的区域child.draw(canvas);//这里再将canvas交给子视图绘制canvas.restore();}

private void drawNextPageAreaAndShadow(Canvas canvas, View child) {mPath1.reset();mPath1.moveTo(mBezierStart1.x, mBezierStart1.y);mPath1.lineTo(mBeziervertex1.x, mBeziervertex1.y);mPath1.lineTo(mBeziervertex2.x, mBeziervertex2.y);mPath1.lineTo(mBezierStart2.x, mBezierStart2.y);mPath1.lineTo(mCornerX, mCornerY);mPath1.close();mDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl1.x- mCornerX, mBezierControl2.y - mCornerY));int leftx;int rightx;GradientDrawable mBackShadowDrawable;if (mIsRTandLB) {leftx = (int) (mBezierStart1.x);rightx = (int) (mBezierStart1.x + mTouchToCornerDis / 4);mBackShadowDrawable = mBackShadowDrawableLR;} else {leftx = (int) (mBezierStart1.x - mTouchToCornerDis / 4);rightx = (int) mBezierStart1.x;mBackShadowDrawable = mBackShadowDrawableRL;}canvas.save();canvas.clipPath(mPath0);canvas.clipPath(mPath1, Region.Op.INTERSECT);//这里裁剪出下一页应该绘制的区域child.draw(canvas);//这里子视图开始绘制canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y);//这里旋转是用来画阴影的mBackShadowDrawable.setBounds(leftx, (int) mBezierStart1.y, rightx,(int) (mMaxLength + mBezierStart1.y));mBackShadowDrawable.draw(canvas);canvas.restore();}

还有对当前页背面的绘制过程是一样的,除了裁剪出指定的区域,还有就是对绘制图像的旋转操作,想深入分析的可以看源代码。

其他相关说明:

1、这里为什么采用FrameLayout?

因为FramLayout布局是上下叠加的,这样就可以同时添加几个子视图而只显示其中的一个,如果用LinearLayout子视图只能垂直或者平行布置,无法完成上下同时显示的效果,这里换成RelativeLayout应该也是可以的,感兴趣的同学可以试一下。

2、关于添加子视图的问题。

我在FrameLayout里添加了3个子视图

private View currentView = null;//当前显示视图private View nextView = null;//翻页后显示视图private View nextViewTranscript = null;//翻页后显示视图副本,用于翻页过程中当前页背面的显示
之后在加载BaseAdapter的时候对着三个视图实例化并添加到FramLayout里,代码如下

public void setAdapter(BaseAdapter adapter) {mAdapter = adapter;itemCount = mAdapter.getCount();currentView = null;nextView = null;nextViewTranscript = null;removeAllViews();if(itemCount != 0) {currentPosition = 0;currentView = mAdapter.getView(currentPosition, null, null);//取得实例addView(currentView);//添加到父视图里if(itemCount > 1) {nextView = mAdapter.getView(currentPosition+1, null, null);//取得实例,添加nextViewTranscript = mAdapter.getView(currentPosition+1, null, null);addView(nextView);addView(nextViewTranscript);}} else {currentPosition = -1;}mTouch.x = 0.01f;mTouch.y = 0.01f;mCornerX = 0;mCornerY = 0;postInvalidate();}
因为三个显示的视图只在这里添加一次,因此当翻页改变内容是需要对这三个视图进行复用,也就是在mAdapter.getView()时只改变内容,而不返回新的实例,BaseAdapter的getView()方法如下

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {// TODO Auto-generated method stubViewGroup layout;if(convertView == null) {//convertView即传入的当前要改变内容的视图,这里判断是否已创建layout = (ViewGroup) inflater.inflate(R.layout.item_layout, null);//创建实例} else {layout = (ViewGroup) convertView;//复用实例}setViewContent(layout, position);//这里来改变现实内容return layout;}
这样就会产生2点限制,1、不同页的内容结构是必须是一样的,也就是用的同一个布局;2、baseAdapter的getview方法需要对convertView进行判断是否进行复用。


Bug修正:

2012.7.23 从右向左翻页时阴影绘制不正确,原因:扩展的时候没有修改mMaxLength,导致阴影长度计算出错;

修正方法:在onMeasure()函数中添加mMaxLength的计算,如下

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);mWidth = getWidth();mHeight = getHeight();mMaxLength = (float) Math.hypot(mWidth, mHeight);//添加在这里}


示例Demo源码下载->http://download.csdn.net/detail/xu_fu/4443142

Bug修正:

2012.11.17 修复了不能点击按钮的问题,因为视图上下叠加,上下层的按钮重叠后会使按钮点击无响应或出错,解决方法是在动画结束后将下层的视图隐藏。

修正控件下载->http://download.csdn.net/detail/xu_fu/4776029


更多相关文章

  1. Android动画机制与使用技巧
  2. DataBing
  3. 提示Android(安卓)dependency 'com.android.support:appcompat-v
  4. 垂直跑马灯
  5. Android菜单实例
  6. Android(安卓)3.0 r1中文API文档(104) ―― ViewTreeObserver
  7. 【Android(安卓)界面效果47】RecyclerView详解
  8. 跟着做 Android(安卓)NDK学习入门如此简单(二)
  9. Android(安卓)中文 API (29) ―― CompoundButton

随机推荐

  1. 38 Android(安卓)actionbar 简单使用
  2. Android的GLSurfaceView测试源码
  3. Android的一些开源库
  4. android的四种点击事件的设置
  5. android 获取手机联系人信息
  6. Android(安卓)简单购物车
  7. Android(安卓)apps应用检查更新代码
  8. Android(安卓)BottomNavigationView的使
  9. android正确使用killProcess完全退出应用
  10. Android异常捕获防止崩溃弹框