# Orientation
调整屏幕方向的操作。
~~~
package io.appium.android.bootstrap.handler;
import android.os.RemoteException;
import com.android.uiautomator.core.UiDevice;
import io.appium.android.bootstrap.*;
import org.json.JSONException;
import java.util.Hashtable;
/**
* This handler is used to get or set the orientation of the device.
*
*/
public class Orientation extends CommandHandler {
/*
* @param command The {@link AndroidCommand} used for this handler.
*
* @return {@link AndroidCommandResult}
*
* @throws JSONException
*
* @see io.appium.android.bootstrap.CommandHandler#execute(io.appium.android.
* bootstrap.AndroidCommand)
*/
@Override
public AndroidCommandResult execute(final AndroidCommand command)
throws JSONException {
final Hashtable<String, Object> params = command.params();
if (params.containsKey("orientation")) {
// Set the rotation
final String orientation = (String) params.get("orientation");
try {
return handleRotation(orientation);
} catch (final Exception e) {
return getErrorResult("Unable to rotate screen: " + e.getMessage());
}
} else {
// Get the rotation
return getRotation();
}
}
/**
* Returns the current rotation
*
* @return {@link AndroidCommandResult}
*/
private AndroidCommandResult getRotation() {
String res = null;
final UiDevice d = UiDevice.getInstance();
final OrientationEnum currentRotation = OrientationEnum.fromInteger(d
.getDisplayRotation());
Logger.debug("Current rotation: " + currentRotation);
switch (currentRotation) {
case ROTATION_0:
case ROTATION_180:
res = "PORTRAIT";
break;
case ROTATION_90:
case ROTATION_270:
res = "LANDSCAPE";
break;
}
if (res != null) {
return getSuccessResult(res);
} else {
return getErrorResult("Get orientation did not complete successfully");
}
}
/**
* Set the desired rotation
*
* @param orientation
* The rotation desired (LANDSCAPE or PORTRAIT)
* @return {@link AndroidCommandResult}
* @throws RemoteException
* @throws InterruptedException
*/
private AndroidCommandResult handleRotation(final String orientation)
throws RemoteException, InterruptedException {
final UiDevice d = UiDevice.getInstance();
OrientationEnum desired;
OrientationEnum current = OrientationEnum.fromInteger(d
.getDisplayRotation());
Logger.debug("Desired orientation: " + orientation);
Logger.debug("Current rotation: " + current);
if (orientation.equalsIgnoreCase("LANDSCAPE")) {
switch (current) {
case ROTATION_0:
d.setOrientationRight();
desired = OrientationEnum.ROTATION_270;
break;
case ROTATION_180:
d.setOrientationLeft();
desired = OrientationEnum.ROTATION_270;
break;
default:
return getSuccessResult("Already in landscape mode.");
}
} else {
switch (current) {
case ROTATION_90:
case ROTATION_270:
d.setOrientationNatural();
desired = OrientationEnum.ROTATION_0;
break;
default:
return getSuccessResult("Already in portrait mode.");
}
}
current = OrientationEnum.fromInteger(d.getDisplayRotation());
// If the orientation has not changed,
// busy wait until the TIMEOUT has expired
final int TIMEOUT = 2000;
final long then = System.currentTimeMillis();
long now = then;
while (current != desired && now - then < TIMEOUT) {
Thread.sleep(100);
now = System.currentTimeMillis();
current = OrientationEnum.fromInteger(d.getDisplayRotation());
}
if (current != desired) {
return getErrorResult("Set the orientation, but app refused to rotate.");
}
return getSuccessResult("Rotation (" + orientation + ") successful.");
}
}
~~~
这个事件有点小复杂哈,当初研究uiautomator源码时就被它折腾的不行,也只实验了左和上的方向成功。没办法,既然又遇到了,那就只能纯理论讲啦。
execute方法中,首先判断参数中是否含有orientation,如果含有调用handleRotation,否则调用getRotation。
所以execute又分流到上面的2个方法中。
## handleRotation
这种情况是参数里含有orientation,此时,我们来看看该方法中做了哪些事。
~~~
final UiDevice d = UiDevice.getInstance();
OrientationEnum desired;
OrientationEnum current = OrientationEnum.fromInteger(d
.getDisplayRotation());
~~~
首先获取当前设备的方向,然后初始化一个私有变量,以备后用。其中OrientationEnum枚举类里定义了4个方向,fromInteger方法是根据整数值得到相应的枚举值,其中各个值的意思。
~~~
public enum OrientationEnum {
ROTATION_0(0), ROTATION_90(1), ROTATION_180(2), ROTATION_270(3);
public static OrientationEnum fromInteger(final int x) {
switch (x) {
case 0:
return ROTATION_0;
case 1:
return ROTATION_90;
case 2:
return ROTATION_180;
case 3:
return ROTATION_270;
}
return null;
}
~~~
ROTATION_0:你正常查看手机时,竖屏。此时屏幕的方向为0度。此时的power键在顶端。如下:
![](https://box.kancloud.cn/2016-01-08_568f4d185bc2b.jpg)
ROTATION_90:你将上面的屏幕向右顺时针旋转90度,此时设备旋转角度为90度,此时我的power键在右端。如果此时你的设备可以自动旋转屏幕的话,你屏幕里面的内容应该是什么样的?如下所示
![](https://box.kancloud.cn/2016-01-08_568f4d1877c31.jpg)
ROTATION_180:从90度角再向下顺时针旋转90度,此时我的power键在下端,此时的角度为180.由于我的手机禁止了这种自由旋转,所以此时的屏幕展现在我的面前是这样一番景象:
![](https://box.kancloud.cn/2016-01-08_568f4d1892320.jpg)
ROTATION_270:从180度再顺时针想左旋转90度,此时我power键在左边。此时为270度,展现在我面前的图如下:
![](https://box.kancloud.cn/2016-01-08_568f4d18ada3c.jpg)
如果你理解了上面4个关键字的意思。那么下面理解代码就很简单啦。
handleRotation方法里做完初始化操作以后,就要判断客户端要求是横屏还是竖屏,如果是横屏,处理如下:
~~~
if (orientation.equalsIgnoreCase("LANDSCAPE")) {
switch (current) {
case ROTATION_0:
d.setOrientationRight();
desired = OrientationEnum.ROTATION_270;
break;
case ROTATION_180:
d.setOrientationLeft();
desired = OrientationEnum.ROTATION_270;
break;
default:
return getSuccessResult("Already in landscape mode.");
}
}
~~~
如果是横屏的话,那么只需要处理屏幕处于0度和180度的情况,因为90度和270度都已经是横屏啦,自然不需要再处理。
如果是ROTATION_0,说明设备朝上,此时想要横屏,自然是顺时针向右旋转一下屏幕。此时,正常情况下可以旋转的话,屏幕里的视图应该是从左到右的,所以desired的值才会被设置为ROTATION_270.所以要分清屏幕的角度和视图的角度。下面就是向右顺时针旋转90度后,里面的视图是270度的,此时power键在右端。
![](https://box.kancloud.cn/2016-01-08_568f4d18ada3c.jpg)
如果是ROTATION_180度,说明设备拿反了,power键朝下。此时你向左顺时针旋转90度或者向右逆时针旋转90度,都能达到横屏的效果。源码里是向左旋转的,此时power键朝左,视图和上面是一样的,desired的值为ROTATION——270.
====================================================================================================================================
如果客户端传递过来的命令想要的是竖屏,就要走else里的代码块:
~~~
else {
switch (current) {
case ROTATION_90:
case ROTATION_270:
d.setOrientationNatural();
desired = OrientationEnum.ROTATION_0;
break;
default:
return getSuccessResult("Already in portrait mode.");
}
}
~~~
此时只要处理90度和270的情况。我们要把它变为ROTATION_0的情况,d.setOrientationNatural()是设置设备转到自然方向,该方向就是设备初始设置的方向。说明通常的设备横屏的2个方向可以旋转,竖屏方向就只有一个方向可以旋转。上面的处理完毕后,方法会做一个判断,判断是否旋转成功。
~~~
current = OrientationEnum.fromInteger(d.getDisplayRotation());
// If the orientation has not changed,
// busy wait until the TIMEOUT has expired
final int TIMEOUT = 2000;
final long then = System.currentTimeMillis();
long now = then;
while (current != desired && now - then < TIMEOUT) {
Thread.sleep(100);
now = System.currentTimeMillis();
current = OrientationEnum.fromInteger(d.getDisplayRotation());
}
if (current != desired) {
return getErrorResult("Set the orientation, but app refused to rotate.");
}
return getSuccessResult("Rotation (" + orientation + ") successful.");
~~~
首先获得此时屏幕的方向,然后判断一下与预期的是否相同。如果不相同,等待2秒钟,再获取一次屏幕的方向,如果经过这么一次验证完毕后,当前的屏幕方向仍然和预期的不相同,那么就返回旋转失败的消息给客户端。如果相同的话,就返回旋转成功的消息给客户端。
到此为止handleRotation处理完毕,下面处理参数里不含有orientation的情况。
## getRotation
该方法中就是根据当前的屏幕的方向得到横屏还是竖屏,将结果返回给客户端。很简单。
# 总结
通过上面的分析,说明客户端关于屏幕方向的命令有2种:
* 获取屏幕的方向
* 改变屏幕的方向
大家要特别主要选择方向的定义,设备的方向和里面视图的方向的区别。
- 前言
- appium框架之bootstrap
- bootstrap之Click事件
- bootstrap之WaitForIdle&&Clear
- bootstrap之Orientation
- bootstrap之Swipe
- bootstrap之Flick
- bootstrap之Drag
- bootstrap之Pinch
- bootstrap之鼠标操作
- bootstrap之文本框的操作
- bootstrap之GetName&&GetAttribute&&GetDeviceSize&&GetSize&&GetLocation&&GetDataDir
- bootstrap之ScrollTo
- bootstrap之Wake&&PressBack&&TakeScreenshot&&OpenNotification
- bootstrap之PressKeyCode&&LongPressKeyCode
- bootstrap之DumpWindowHierarchy
- bootstrap之UpdateStrings
- bootstrap之MultiPointerGesture