为了方便没有准备好梯子的同学,我把项目在CSDN上打包下载,不过不会继续更新(保留在初始版本)
先贴代码再讲原理吧
缩放事件(Scale)监听
和Scroll一样,Scale也有一个辅助类帮我们来记录用户的缩放操作(代码是缩放和平移的整合版)
private GestureDetector gestureDetector;
private ScaleGestureDetector scaleGestureDetector;
private static final float sDensity = Resources.getSystem().getDisplayMetrics().density;
private static final float sDamping = 0.2f;
private float mDeltaX;
private float mDeltaY;
private float mScale;
private void initGestureHandler(){
mDeltaX=mDeltaY=0;
mScale=1;
gestureDetector=new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mDeltaX+= distanceX / sDensity * sDamping;
mDeltaY+= distanceY / sDensity * sDamping;
return super.onScroll(e1, e2, distanceX, distanceY);
}
});
scaleGestureDetector=new ScaleGestureDetector(context, new ScaleGestureDetector.OnScaleGestureListener() {
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor=detector.getScaleFactor();
updateScale(scaleFactor);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
//return true to enter onScale()
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
});
}
public boolean handleTouchEvent(MotionEvent event) {
boolean ret=scaleGestureDetector.onTouchEvent(event);
if (!scaleGestureDetector.isInProgress()){
ret=gestureDetector.onTouchEvent(event);
}
return ret;
}
public void updateScale(float scaleFactor){
mScale=mScale+(1.0f-scaleFactor);
mScale=Math.max(0.122f,Math.min(1.0f,mScale));
}
我们只需要在onScale
中获取scaleFactor,再在onDrawFrame里面更新projectionMatrix即可
//视角从90度到14度
float currentDegree= (float) (Math.toDegrees(Math.atan(mScale))*2);
Matrix.perspectiveM(projectionMatrix, 0, currentDegree,(float)screenWidth/screenHeight/2, 1f, 500f);
效果预览
这次我决定要来一张效果好一点的GIF(1.7MB)。。
缩放原理
其实原理就一句话:改变透视投影矩阵,即projectionMatrix,通过改变透视投影矩阵来实现只显示画面中更小或更大一部分的内容
那么要怎么改变投影矩阵呢?我们来看Matrix.perspectiveM的定义
/**
* Defines a projection matrix in terms of a field of view angle, an
* aspect ratio, and z clip planes.
*
* @param m the float array that holds the perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param fovy field of view in y direction, in degrees
* @param aspect width to height aspect ratio of the viewport
* @param zNear
* @param zFar
*/
public static void perspectiveM(float[] m, int offset,
float fovy, float aspect, float zNear, float zFar)
关键的参数是fovy
,这个是y方向的可视角度,结合aspect
以及近平面,远平面就可以推出整个视锥体
另外,我们的两个监听器要注意处理顺序,缩放的判断应该在平移之前,不然缩放也变成平移了
结合这张图,应该更容易理解:
细心的朋友应该注意到了0.122f
这个神奇的数字,其实这个就是
tan(7)
的结果,表示我们的最小视角是14度——所以如果要改变最大视角和最小视角的话,知道该怎么办了吧(不要问我为什么最小是14度。。)?
另一种略不完美的解决方案
此方案来自MD360Player4Android
if(statusHelper.getPanoDisPlayMode()==PanoMode.DUAL_SCREEN)
Matrix.frustumM(projectionMatrix, 0, -ratio/4, ratio/4, -0.5f, 0.5f, 0.7f, 500);
else Matrix.frustumM(projectionMatrix, 0, -ratio/2, ratio/2, -0.5f, 0.5f, 0.7f, 500);
frustumM也是透视投影,而且比较直观,看定义就知道了
/**
* Defines a projection matrix in terms of six clip planes.
*
* @param m the float array that holds the output perspective matrix
* @param offset the offset into float array m where the perspective
* matrix data is written
* @param left
* @param right
* @param bottom
* @param top
* @param near
* @param far
*/
public static void frustumM(float[] m, int offset,
float left, float right, float bottom, float top,
float near, float far)
直接指定可视区域的范围——上下左右(这里的上下左右是指近平面的上下左右)前后,够直观的吧~
但是这样做存在一个小问题(个人观点,也许不算问题),由于视角和放大比例其实并不是线性关系,在放大时会出现放大速度越来越快的情况(唰的一下就放到最大了)。因此如果能先计算反正切(atan/atan2),那么就能够实现接近平滑的缩放(Matrix.perspectiveM)
好了,到这里我们已经几乎实现了所有的功能,剩下的就是UI(侧重点主要是实现UI时会碰到的问题)以及扩展功能了~(更多的细节以及功能请去项目中探索)