# 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鍵在頂端。如下:

ROTATION_90:你將上面的屏幕向右順時針旋轉90度,此時設備旋轉角度為90度,此時我的power鍵在右端。如果此時你的設備可以自動旋轉屏幕的話,你屏幕里面的內容應該是什么樣的?如下所示

ROTATION_180:從90度角再向下順時針旋轉90度,此時我的power鍵在下端,此時的角度為180.由于我的手機禁止了這種自由旋轉,所以此時的屏幕展現在我的面前是這樣一番景象:

ROTATION_270:從180度再順時針想左旋轉90度,此時我power鍵在左邊。此時為270度,展現在我面前的圖如下:

如果你理解了上面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鍵在右端。

如果是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