一、Android2.3上横竖屏切换的逻辑详细分析
android2.2怎么根据重力感应来改变布局呢,我们来详细分析一下WindowOrientationListener.java这个文件
这个文件里有个重要的概念和两个重要的数组,对于我们理解翻转手机带来屏幕布局变化有很大帮助
一个概念:布局方式
在android2.2官方系统中有三种布局方式,分别叫做ROTATION_0,ROTATION_90, ROTATION_270,不要把后面的0,90,270与后面我们即将提到的角度值混淆,这里的0,90,270就是个名称,跟A,B,C没什么区别,只是告诉我们这是一种布局方式,本段的核心就是研究在什么情况下采用哪一种布局方式。
两个数组:THRESHOLDS, ROTATE_TO
先看两个数组的定义
Private final int[][][] THRESHOLDS = newint[][][] {
{{60,180}, {180, 300}},
{{0,45}, {45, 165}, {330, 360}},
{{0,30}, {195, 315}, {315, 360}}
}
Private final int[][] ROTATE_TO= new int[][] {
{ROTATION_270,ROTATION_90},
{ROTATION_0,ROATION_270, ROATION_0},
{ROATION_0,ROTATION_90, ROATION_0}
}
THRESHOLDS的基本元素是数值,实际上是代表角度,0~360正好代表一个圆周;
ROTATE_TO的基本元素是布局方式。
这两个数组正好组成一个映射关系如下:
注意:这里有缓冲区的设计,并非给定一个角度就对应一个布局,后面会详细讲到。
我们从角度开始研究
Android系统中,手机的角度如下图所示
当手机的speaker在上方(天空的方向)时,手机所在角度为0°,朝右为90°,朝下为180°,朝左为270°。
手机为0°,是我们常用的方向,对应的布局为ROTATION_0,根据映射关系发现:
当角度在60-180之间时,转变布局为ROTATION_270;
转过180到了180-300之间时,改变布局为ROATION_90;
如果是在300-360,0-60之间(宽度为120),则不做改变。
可以发现,三种布局把整个圆周三分天下,每个布局占据120度,300-360和0-60这个区间内为布局ROTATION_0,60-180这个区间为布局ROTATION_270,180-300这个区间的布局为ROTATION_90。 有了这个基本的认识,再看映射表的其余部分就简单多了
手机在270度方向时,对应的布局为ROTATION_90(而非ROTATION_270),根据映射关系:
当角度变为0-45和330-360的时候,改变布局为ROTATION_0;
当角度变为45-165的时候,改变布局为ROTATION_270;
当角度在165-330这个区间(宽度为165),则布局不变。
手机在90度方向时,对应的布局为ROTATION_270(而非ROTATION_90),根据映射关系:
当角度变为0-30和315-360的时候,改变布局为ROTATION_0;
当角度变为195-315的时候,改变布局为ROTATION_90;
当角度在30-195这个区间(宽度为165),则布局不变。
注意当前布局的边界值,一旦不在此布局类型了,就切换到新的布局类型的边界值判断了。当手机是90度时,布局肯定是ROTATION_270,当转到195时,布局类型切为ROTATION_90(这时再立即回转到194发现布局并不会立即切换回去)在这个状态的边界变成了165~330,这正是缓冲区的意义所在,当切到缓冲区时总是为上次布局类型,根据以上3付图可以得到缓冲区示图:
可见每个缓冲区宽度为30,分别是30~60,165~195,300~330
在这些区间内,手机的布局类型不是唯一的。
根据这几个数值,可以谨慎推断android这么做的思路:
当正常拿着手机的时候,即布局为ROTATION_0时,会根据等分的120度来判断改变成哪个布局;
当横着拿手机,即布局为ROTATION_90或者ROTATION_270时,布局改变的条件就要苛刻一些,改回ROTATION_0的条件尤其苛刻(角度范围跨度只有75度)。
有了上面的认识,我们想把布局缩减为2种或者增加为4种(如增加ROTATION_180)就可以方便的改变数组来实现。
二、Android2.3上横竖屏切换的代码分析
判断是否旋转的关键文件是frameworks/base/core/java/android/view/WindowOrientationListener.java
控制手机屏幕旋转的关键类为frameworks/base/core/java/android/view/
WindowOrientationListener.java , 该类为抽象类,
系统中唯一实现继承该类的为PhoneWindowManager类,在WindowOrientationListener类的构造函数中,
会获取一个SensorManager类的实例对象,通过SensorManager获取一个加速度传感器对象,最后new一个SensorEventListener。
代码为:
mSensorManager=(SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
mRate = rate;
mSensor =mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist
mSensorEventListener = newSensorEventListenerImpl();
}
在WindowOrientationListener类的enable方法中,向SensorManager中注册一个传感器事件监听器。
/**
* Enables theWindowOrientationListener so it will monitor the sensor and call
* {@link#onOrientationChanged} when the device orientation changes.
*/
public void enable() {
if (mSensor == null) {
Log.w(TAG,"Cannot detect sensors. Not enabled");
return;
}
if (mEnabled == false){
if (localLOGV)Log.d(TAG, "WindowOrientationListener enabled");
mSensorManager.registerListener(mSensorEventListener,mSensor, mRate);
mEnabled = true;
}
}
在WindowOrientationListener类中有一个实现了SensorEventListener接口的SensorEventListenerImpl的内部类,当传感器的数据发生变化时,会调用其onSensorChanged方法,该方法的实现主要是将加速度传感器上报的数据转换为设备旋转的方位角,然后在调用calculateNewRotation方法将方位角确定为屏幕的旋转方向,其实现主要是利用WindowOrientationListener类里的两个数组:THRESHOLDS和ROTATE_TO数组来实现确定屏幕的旋转方向。
即完成如下转化: 加速度值----->角度值--->布局类型(ROTATION_0/90/270)
示例代码为:
private int[][][]THRESHOLDS = new int[][][] {
{{79, 180}, {180, 282}},
{{0, 45}, {45, 165}, {300,360}},
{{0, 53}, {195, 315}, {315,360}}
};
// See THRESHOLDS
private final int[][] ROTATE_TO = new int[][]{
{ROTATION_270, ROTATION_90},
{ROTATION_0, ROTATION_270,ROTATION_0},
{ROTATION_0, ROTATION_90,ROTATION_0}
};
这两个数组是一一对应的,THRESHOLDS数组为三维数组(可看作一个一维数组,数组中的元素类型为二维数组),THRESHOLDS数组第一行代表当手机屏幕处于ROTATION_0时,当手机旋转到79~180度,calculateNewRotation将根据相应的ROTATE_TO数组计算得出屏幕该旋转的方向为ROTATION_270(数组索引), 旋转到180~282度时,同理得出屏幕该旋转的方向为ROTATION_90。THRESHOLDS数组第二行代表当手机屏幕处于ROTATION_90(数组索引),如果屏幕旋转的角度在所定义的角度范围内,屏幕将会旋转,最终的旋转方向取决于ROTATE_TO数组的第二行,依次类推,第三行对应ROTATION_270,如果要支持ROTATION_180,则只需要在两个数组中添加相应的元素即可。
当calculateNewRotation计算出应该旋转的方位时,会将结果传递给onOrientationChanged( ),该方法为抽象方法,需要继承的子类来实现,系统中WindowOrientaionListener唯一继承该子类为PhoneWindowManager类的一个内部类MyOrientationListener类,在该类的onOrientationChanged方法的实现中,将会调用WindowManagerService类的一个实例对象的setRotation方法。
class MyOrientationListener extends WindowOrientationListener {
//构造函数
MyOrientationListener(Context context){
super(context);
}
@Override
public void onOrientationChanged(introtation) {
// Send updates based onorientation value
if (localLOGV) Log.v(TAG,"onOrientationChanged, rotation changed to " +rotation);
try {
mWindowManager.setRotation(rotation, false,
mFancyRotationAnimation);
} catch (RemoteException e) {
// Ignore
}
}
}
在WindowManagerService的setRotation方法中实现屏幕的旋转和view树的重绘。其主要实现是Surface.setOrientation方法的调用。