ViewPage代码改写增加翻转和缩放效果
(本文属于个人经验总结,如有理解偏差,即使沟通)
1. 一般平移滑动效果原理:
拿ViewPager源码举例(ViewPager代码可以在新版SDK中找到),它继承了ViewGroup,如果实现3屏滑动,需将3个将要展示的页面添加到ViewGroup中去,通过触摸事件来计算滑动距离,使用scrollTo或者Scroller中的startScroll
方法将屏幕定位到需要展示的位置,达到滑动效果。如果将ViewGroup比喻成一个长长的卷轴,我们的手机屏幕就相当于放大镜,放大镜显示的区域有限,所以只能通过移动才能看到全部的卷轴内容。
2. 滑动过程中触摸事件分发:
在ViewGroup的子类中需要重写onInterceptTouchEvent和onTouch方法,onInterceptTouchEvent是对触摸事件进行拦截和分发。由于onInterceptTouchEvent()的机制比较复杂,上面的说明写的也比较复杂,总结一下,基本的规则是:
I.down事件首先会传递到onInterceptTouchEvent()方法
如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后returnfalse,那么后续的move,up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
II如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后returntrue,那么后续的move,up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
III如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
IV如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
如果传递到onTouchEvent方法,则主要是控制整个view的滑动,在onInterceptTouchEvent()down事件出发的时候记录x轴坐标,在onTouchEvent()move事件中记录x轴坐标,通过和onInterceptTouchEvent() down事件中记录的x轴坐标进行比对,从而得到屏幕是向右滑动还是像左滑动,滑动的距离则可通过减法获得。
怎样判断我是否滑动了一个屏幕呢?在viewpager中有两个标准,一个是手势滑动的速度还有一个就是手势滑动的距离,代码如下:
final VelocityTrackervelocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
//得到x滑动速度
int initialVelocity = (int)VelocityTrackerCompat.getXVelocity();
//如果滑动速度大于4000像素每秒或者滑动的距离大于view的宽度的1/3
if ((Math.abs(initialVelocity) > 4000)
|| Math.abs(mInitialMotionX-mLastMotionX) >=(getWidth()/3)) {
表示滑动一屏幕(左屏幕还是右屏幕需要根据坐标去算)
}
至于一些细节的实现和状态控制请参考ViewPager源码
3. dispatchDraw和onDraw方法区别:
常见的翻转变化的操作类是Camera,Matrix,然后通过Canvas画到页面上。滑动过程中要给view添加翻转效果,本人的思路是将本身的view隐藏掉然后得到viewcache中的bitmap图片通过对bitmap图片的操作来做相应的控制(如过想要更好的体验可以在view绘制的时候去操作,这样可能需要深度定制改写源代码)。重写dispatchDraw方法进行绘制,为什么不重写onDraw方法呢?主要原因是:draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法,dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组 件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在 ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法。
4. View组件的cache机制:
View组件显示的内容可以通过cache机制保存为bitmap, 使用到的api有
void setDrawingCacheEnabled(booleanflag),
BitmapgetDrawingCache(boolean autoScale),
我们要获取它的cache先要通过setDrawingCacheEnable方法把cache开启,然后再调用getDrawingCache方法就可以获得view的cache图片了。buildDrawingCache方法可以不用调用,因为调用getDrawingCache方法时,若果cache没有建立,系统会自动调用buildDrawingCache方法生成cache。若果要更新cache, 必须要调用destoryDrawingCache方法把旧的cache销毁,才能建立新的。
当调用setDrawingCacheEnabled方法设置为false, 系统也会自动把原来的cache销毁。
获取cache通常会占用一定的内存,所以通常不需要的时候有必要对其进行清理,通过destroyDrawingCache或setDrawingCacheEnabled(false)实现。
示例代码:
currentView.clearFocus();//currentView表示设置的View对象
currentView.setPressed(false);
currentView.setDrawingCacheBackgroundColor(0);
currentView.setDrawingCacheEnabled(true);
Bitmap viewBitmap = currentView.getDrawingCache();
currentView.setDrawingCacheEnabled(false);
5. 翻转和缩放:
得到bitmap对象后,我们利用Camera,Matrix对图片进行反转缩放
示例代码:
camera.save();
//得到Y轴翻转角度 ,getTrangle方法获得角度,需要自定义
float trangle = getTrangle(mPersonalDeltaX);
//Y轴翻转
camera.rotateY(trangle);
//设置camera作用矩阵
Matrix matrix = new Matrix();
camera.getMatrix(matrix);
camera.restore();
int width = viewBitmap.getWidth();
int height = viewBitmap.getHeight();
if(toLeft){
//设置翻转中心点
matrix.preTranslate(-width, -height/2);
matrix.postTranslate(width, height/2);
}else if(toRight){
//设置翻转中心点
matrix.preTranslate(0,-height/2);
matrix.postTranslate(0, height/2);
}
float scaleHeight =Math.abs(trangle/(MAX_TRANGLE*2));
float scaleWidth = scaleHeight;
//对图片进行缩放
matrix.postScale((1.0f - scaleWidth), (1.0f -scaleHeight), width/2, height/2);
//设置画布原点 canvas.translate(currentView.getWidth()*mCurItem,0);
//画图
canvas.drawBitmap(viewBitmap, matrix, null);