观察者与陀螺仪传感器的应用(基于PanoramaImageView)

来源:互联网 发布:广告投放算法好吃 编辑:程序博客网 时间:2024/04/26 10:16

原作者的PanoramaImageView项目github地址:https://github.com/gjiazhe/PanoramaImageView

实现原理:

首先将自定义PanoramaImageView的ScaleType手动置为CENTER_CROP:将图片等比例缩放,让图像的短边与ImageView的边长度相同,即不能留有空白,缩放后截取中间部分进行显示。图像相对长的边就会“显示”在屏幕外边。然后获取手机陀螺仪传感器,注册监听,并根据陀螺仪传感器传来的数据判断手机当前的状态。

这里写图片描述

手机传感器坐标系如上图所示,陀螺仪传感器的监听方法onSensorChanged(SensorEvent event)传过来event参数,其中属性event.values是一个float类型的数组,数组的前三个值event.values[0],event.values[1],event.values[2]分别代表了手机绕X,Y,Z轴旋转的角速度。我们判断这个三个角速度绝对值的相对大小既可以粗略的判断出手机当前所处的状态。代码如下:

     @Override    public void onSensorChanged(SensorEvent event) {        if (lastTimestamp == 0) {            lastTimestamp = event.timestamp;            return;        }        float angularX = Math.abs(event.values[0]);        float angularY = Math.abs(event.values[1]);        float angularZ = Math.abs(event.values[2]);        final float dT = (event.timestamp - lastTimestamp) * NS2S;        if (angularX > angularY + angularZ) {  //围绕x轴运动,即手机上下摆动(脑补拿手机打手板的动作)            rotateRadianX += event.values[0] * dT;            rotateRadian = rotateRadianX;            type = rotateAroundX;        } else if (angularY > angularX + angularZ) {//围绕y轴运动,即手机左右旋转(脑补四驱兄弟里面的“旋风冲锋龙卷风”)            rotateRadianY += event.values[1] * dT;            type = rotateAroundY;            rotateRadian = rotateRadianY;        } else {//围绕Z轴运动,可以理解为手机放在桌子上转圈            rotateRadianZ += event.values[2] * dT;            type = rotateAroundZ;            rotateRadian = rotateRadianZ;        }        updata(gyroscopeControllInterface);        lastTimestamp = event.timestamp;    }

获取到手机转动方向和角度后,就可以根据情况设置图片的显示状态了。前面我们将imageView的ScaleType设置为CENTER_CROP,这时会有两种情况。
第一种情况:图片宽度/view宽度>图片高度/view高度(高度边填充imageview,宽度边溢出):

这里写图片描述

这里写图片描述

这种情况下查看图片的方法是“左右旋转“手机,即手机绕Y轴转动。

第二种情况:图片高度/view高度>图片宽度/view宽度(宽度边填充imageview,高度边溢出):

这里写图片描述

这里写图片描述

这种情况下查看图片的方法是“上下摆动“手机,即手机绕X轴转动。当比例相同的时候暂不考虑,这种情况归于上面哪种都可以,反正不会自己动~~。
上面我们通过陀螺仪传感器获取了手机旋转的状态和角度,再结合图片的状态:如果手机绕Y轴旋转,且图片是第一种状态或者手机绕X轴旋转,且图片是第二种状态 则符合我们重绘的条件,即可以触发图片的移动。那么如何进行绘制来达到Panorama(全景)的效果呢?先从效果来说,全景效果其实就是canvas根据手机旋转角度进行translate,左右平移或者上下平移,核心代码就一句: canvas.translate(currentOffsetX, 0)或者canvas.translate( 0,currentOffsetY)。重点是currentOffsetX,Y的计算过程,手机绕某一轴旋转的度数(弧度)是没有限制的,旋转一周是±2π,旋转十周是±20π,我们要根据需要取得我们所需要的度数范围,一般我们手持设备转动,合适观看的度数±π/6即左右或上下偏移30°就差不多了。所以我们先设置旋转的最大度数maxRotateRadian = Math.PI / 6 (即30°),再根据从传感器获取的旋转状态和度数计算出当前手机旋转的角度与最大角度的比例值(可以理解为手机转动的程度):

  @Override    public void updata(double rotateRadian, int type) {        //如果type与当前图片的type一致        if (orientation == type) {            //限制旋转弧度最大为maxRotateRadian            if (rotateRadian >= maxRotateRadian) {                rotateRadian = maxRotateRadian;            } else if (rotateRadian <= -maxRotateRadian) {                rotateRadian = -maxRotateRadian;            }            //计算出比例值            progress = (float) (rotateRadian / maxRotateRadian);            invalidate();        }    }

这个比例值progress 与canvas平移的比例是呈正比的:

这里写图片描述

得到了比例值progress ,要去求canvas平移的距离,拿这个比例值去乘上我们位移的最大值即可。那么问题来了,这个最大值从哪里来的呢?以宽幅照片为例:

这里写图片描述

这个位移的最大值就是上图中单个阴影的长度x,图片初始加载时,两阴影的长度是相等的,也就是我们图片可以左/右位移的最大值(若竖幅则是上/下位移)。这个最大值如何计算呢?

假设图片处于第一种情况(图片宽度/view宽度>图片高度/view高度):
刚开始我是这么计算的:最大值=(图片宽度-imageview宽度)/2,看上图很明显吗~这就too simple了,这种情况其实是imageview把照片根据(图片高度/view高度)这个比例把图片缩放了,上图中图片的宽度是经过缩放后的宽度,并非原图宽度,所以这个宽度应该等于原图宽度去除以这个比例:即
上图宽幅照片的宽度=原图宽度/(图片高度/view高度),
那么
最大值=(原图宽度/(图片高度/view高度)-imageview宽度)/2
假设图片处于第二种情况(图片宽度/view宽度<图片高度/view高度):
最大值=(原图高度/(图片宽度/view宽度)-imageview高度)/2。
这里我用了除法,不是很直观,原作者的代码更加直观些:

            if (mDrawableWidth * mHeight > mDrawableHeight * mWidth) {                mOrientation = ORIENTATION_HORIZONTAL;                float imgScale = (float) mHeight / (float) mDrawableHeight;                mMaxOffset = Math.abs((mDrawableWidth * imgScale - mWidth) * 0.5f);            } else if(mDrawableWidth * mHeight < mDrawableHeight * mWidth) {                mOrientation = ORIENTATION_VERTICAL;                float imgScale = (float) mWidth / (float) mDrawableWidth;                mMaxOffset = Math.abs((mDrawableHeight * imgScale - mHeight) * 0.5f);            }

得到了最大偏移值就可以计算出位移的偏移量了,maxOffset (位移最大值)*progress (比例值)=位移偏移量。之后根据偏移量重绘:

  if (orientation == ORIENTATION_HORIZONTAL) {            float currentOffsetX = maxOffset * progress;            canvas.save();            canvas.translate(currentOffsetX, 0);            super.onDraw(canvas);            canvas.restore();        } else if (orientation == ORIENTATION_VERTICAL) {            float currentOffsetY = maxOffset * progress;            canvas.save();            canvas.translate(0, currentOffsetY);            super.onDraw(canvas);            canvas.restore();        }

大致思路就是这样,此项目作者构建了一个观察者模式来获取陀螺仪传感器的数值并发送给PanoramaImageView绘制,我也重新系统学习了一下观察者模式并在该项目基础上做了改动,构建了结合代理模式的观察者模式,将观察者(observe)与被观察者(observed)解耦,由一个控制中心统一调控。上面代码是修改后的代码,与原项目有些不同,但思路是一致的。我fork的此项目的地址:https://github.com/Ljy2016/PanoramaImageView。先到这里,吃饭了,明天再记录一下观察者模式的实现。

1 0
原创粉丝点击