原生屏幕旋转算法(AccelSensor)
一、屏幕旋转算法的流程
关键设计逻辑:
这些跳过条件共同构成了多级数据验证机制:
传感器层面:过滤硬件异常数据(时间戳错乱/全零数据)
物理层面:排除不可靠测量值(加速度过小/角度过大)
状态层面:等待设备进入稳定状态(停止移动/旋转)
逻辑层面:验证角度组合合理性(倾斜+方向匹配)
这种设计确保了屏幕旋转决策只在设备处于稳定状态且传感器数据可靠时触发,有效防止了屏幕意外旋转或频繁抖动。
二、主要方法
1.onSensorChanged(SensorEvent)
1.1数据来源
float x = event.values[ACCELEROMETER_DATA_X];
float y = event.values[ACCELEROMETER_DATA_Y];
float z = event.values[ACCELEROMETER_DATA_Z];
1.2数据过滤
// Apply a low-pass filter to the acceleration up vector in cartesian space.
// Reset the orientation listener state if the samples are too far apart in time
// or when we see values of (0, 0, 0) which indicates that we polled the
// accelerometer too soon after turning it on and we don't have any data yet.
final long now = event.timestamp;
final long then = mLastFilteredTimestampNanos;
final float timeDeltaMS = (now - then) * 0.000001f;
final boolean skipSample;
if (now < then
|| now > then + MAX_FILTER_DELTA_TIME_NANOS
|| (x == 0 && y == 0 && z == 0)) {
if (LOG) {
Slog.v(TAG, "Resetting orientation listener.");
}
resetLocked(true /* clearCurrentRotation */);
skipSample = true;
} else {
final float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
x = alpha * (x - mLastFilteredX) + mLastFilteredX;
y = alpha * (y - mLastFilteredY) + mLastFilteredY;
z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
if (LOG) {
Slog.v(TAG, "Filtered acceleration vector: "
+ "x=" + x + ", y=" + y + ", z=" + z
+ ", magnitude=" + Math.sqrt(x * x + y * y + z * z));
}
skipSample = false;
}
mLastFilteredTimestampNanos = now;
mLastFilteredX = x;
mLastFilteredY = y;
mLastFilteredZ = z;
这段代码工作的主要流程如下:
从传感器事件中提取加速度数据。
计算时间间隔。
检查是否需要跳过当前样本,如果需要则重置状态。
应用低通滤波器平滑加速度数据。
更新最近一次处理的时间戳和加速度数据。
1.3更新预测角度
处理传感器数据,以确定设备的旋转角度,并使用加速度传感器数据预测设备的旋转状态。代码的详细解析和总结:
1)样本处理
if (!skipSample) {
如果没有跳过样本,则进行以下处理。
2)计算加速度矢量的模
final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
计算加速度矢量的模,方法是计算 x2+y2+z2x2+y2+z2。
3)忽略接近零的数据
if (magnitude < NEAR_ZERO_MAGNITUDE) { if (LOG) { Slog.v(TAG, "Ignoring sensor data, magnitude too close to zero."); } clearPredictedRotationLocked(); } else {
如果模接近零,则忽略当前传感器数据,并调用 clearPredictedRotationLocked() 清除预测的旋转状态。
4)检测外部加速度
if (isAcceleratingLocked(magnitude)) { isAccelerating = true; mAccelerationTimestampNanos = now; }
如果检测到设备正在经历外部加速度,设置 isAccelerating 为真,并记录当前时间戳。
5)计算倾斜角度
final int tiltAngle = (int) Math.round( Math.asin(z / magnitude) * RADIANS_TO_DEGREES); addTiltHistoryEntryLocked(now, tiltAngle);
计算倾斜角度(设备顶部与水平面的夹角),并将其记录到倾斜历史中。
6)检查是否平放或摆动
if (isFlatLocked(now)) { isFlat = true; mFlatTimestampNanos = now; } if (isSwingingLocked(now, tiltAngle)) { isSwinging = true; mSwingTimestampNanos = now; }
检查设备是否平放或摆动,并相应地更新状态和时间戳。
7)检查倾斜角度是否过大或过小
if (tiltAngle <= TILT_OVERHEAD_ENTER) { mOverhead = true; }
else if (tiltAngle >= TILT_OVERHEAD_EXIT) { mOverhead = false; }
if (mOverhead) { if (LOG) { Slog.v(TAG, "Ignoring sensor data, device is overhead: " + "tiltAngle=" + tiltAngle); } clearPredictedRotationLocked(); }
else if (Math.abs(tiltAngle) > MAX_TILT) { if (LOG) { Slog.v(TAG, "Ignoring sensor data, tilt angle too high: " + "tiltAngle=" + tiltAngle); } clearPredictedRotationLocked(); }
如果设备过于倾斜(例如,屏幕水平面对地或面对天空),则忽略当前传感器数据,并清除预测的旋转状态。
8)计算方向角
else { int orientationAngle = (int) Math.round( -Math.atan2(-x, y) * RADIANS_TO_DEGREES);
if (orientationAngle < 0) { orientationAngle += 360; // atan2 返回 [-180, 180],将其归一化到 [0, 360] }
// 找到最近的旋转角度,0对应范围(0,45)和(315,360)
int nearestRotation = (orientationAngle + 45) / 90; if (nearestRotation == 4) { nearestRotation = 0; }
// 确定预测的旋转状态
if (isTiltAngleAcceptableLocked(nearestRotation, tiltAngle) && isOrientationAngleAcceptableLocked(nearestRotation, orientationAngle)) { updatePredictedRotationLocked(now, nearestRotation); }
else { clearPredictedRotationLocked(); } } }
}
这部分代码:
计算方向角(方向角是加速度矢量在 x-y 平面上的投影与 y 轴的夹角)。
找到最近的 90 度倍数的旋转角度。
判断该旋转角度是否满足条件(倾斜角度和方向角)。
如果满足条件,更新预测的旋转状态;否则,清除预测的旋转状态。
总结这段代码的主要功能是:
在需要时应用低通滤波器平滑传感器数据。
在检测到有效的传感器数据后,通过加速度数据计算设备的倾斜角度和方向角。
通过多种条件(如加速度大小、倾斜角度和方向角)判断当前设备的旋转状态,更新并记录预测的旋转状态。
1.4更新建议角度
// Determine new proposed rotation.
//保留旧角度,以备后续使用
oldProposedRotation = mProposedRotation;
//判断建议角度并更新。mPredictedRotation是在上一步updatePredictedRotationLocked中更新
if (mPredictedRotation < 0 || isPredictedRotationAcceptableLocked(now)) {
mProposedRotation = mPredictedRotation;
}
proposedRotation = mProposedRotation;
1.5通知监听器
// Tell the listener.
//判断角度改变,并通知监听器
if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
onProposedRotationChanged(proposedRotation);
}
重点参数说明:
orientationAngle:数学中反正切得到的角度,取值范围0~360
nearestRotation:构建最接近的方向角度,即预测方向角度
重点方法说明:
updatePredictedRotationLocked:更新预测角度
clearPredictedRotationLocked:取消预测角度
isTiltAngleAcceptableLocked:
isOrientationAngleAcceptableLocked:判断旋转方向是否可以接受,并进行归一,即nearestRotation(0,90,180,270)-->(0,1,2,3)
isPredictedRotationAcceptableLocked:判断系统是否需要更新方向
2.isOrientationAngleAcceptableLocked(int rotation, int orientationAngle)
判断提议的旋转角度和方向角度是否可以接受。该方法通过引入“迟滞”(Hysteresis)机制来避免频繁的方向变化。
2.1获取当前旋转角度
final int currentRotation = mCurrentRotation;
首先获取当前的旋转角度 mCurrentRotation,即上一次的旋转角度。
2.2无当前旋转角度的情况
if (currentRotation >= 0) {
如果 currentRotation 小于 0,表示没有当前的有效旋转角度。
2.3处理逆时针和顺时针相邻的情况
逆时针相邻:(顺时针旋转)
//方向不便或顺时针加90度旋转
if (rotation == currentRotation || rotation == (currentRotation + 1) % 4) {
如果提议的旋转角度和当前旋转角度一致,或者是逆时针相邻,则设置方向角度的下限。具体判断标准如下:
//0-360度减去22.5度,即(0,1,2,3)-->(-22.5,67.5,157.5,247.5)
int lowerBound = rotation * 90 - 45 + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
//0(竖直向上):(315,360)缩短(337.5,360)
if (rotation == 0) { if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) { return false; } }
//其他范围边际缩小22.5度 ,
else { if (orientationAngle < lowerBound) { return false; } }
该部分代码计算方向角度的下限,如果方向角度小于下限,则返回 false。即各个方向的范围如下:0-->(0,45)&(337.5,360),1-->(67.5,135),2-->(157.5,225),3-->(247.5,315)
顺时针相邻:(逆时针旋转)
j if (rotation == currentRotation || rotation == (currentRotation + 3) % 4) {
如果提议的旋转角度和当前旋转角度一致,或者是顺时针相邻,则设置方向角度的上限。具体判断标准如下:
int upperBound = rotation * 90 + 45 - ADJACENT_ORIENTATION_ANGLE_GAP / 2; if (rotation == 0) { if (orientationAngle <= 45 && orientationAngle > upperBound) { return false; } } else { if (orientationAngle > upperBound) { return false; } }
该部分代码计算方向角度的上限,如果方向角度大于上限,则返回 false。
2.4默认返回结果
return true;
如果给定的方向角度在允许范围内,则返回 true,表示该方向角度是可以接受的。
补充说明:
如何理解逆时针相邻
1.旋转角度的定义
通常,设备的旋转角度(例如手机屏幕的旋转方向)有四种基本状态:
ROTATION_0: 设备处于正常的纵向(竖屏)模式,上边朝上。
ROTATION_90: 设备向右旋转90度,处于横向(横屏)模式,右边朝上。
ROTATION_180: 设备处于反向的纵向(竖屏)模式,下边朝上。
ROTATION_270: 设备向左旋转90度,处于横向(横屏)模式,左边朝上。
2.逆时针相邻的定义
逆时针相邻意味着设备的当前旋转角度 currentRotation 和提议的旋转角度 rotation 之间的关系是旋转角度相差1个90度单位,经过两者连线所围绕的方向形成了顺时针方向。
具体地,可以通过以下公式来确定相邻关系:当前角度和提议角度相差 90 度,即: (currentRotation + 1) % 4 == rotation
3.举例说明
假设当前设备的旋转角度 currentRotation 是 ROTATION_0,那么逆时针相邻的提议角度 rotation 是 ROTATION_90。
ROTATION_0: 设备正常纵向(0度)
ROTATION_90: 设备向右旋转90度