1.在4.4.2版本中滑动屏幕时scrollForward()不起作用而swipeLeft()可以滑动?
(如果想知道结果,直接看总结)
先来看下各自的源码(4.1.1版)
swipeLeft
public boolean swipeLeft(int steps) throws UiObjectNotFoundException {
Rect rect = getBounds();
if(rect.width() <= SWIPE_MARGIN_LIMIT * 2)
return false; // too small to swipe
return getInteractionController().swipe(rect.right - SWIPE_MARGIN_LIMIT,
rect.centerY(), rect.left + SWIPE_MARGIN_LIMIT, rect.centerY(), steps);
}
可以看出里面调用了InteractionController.swipe()
public boolean swipe(int downX, int downY, int upX, int upY, int steps) {
boolean ret = false;
int swipeSteps = steps;
double xStep = 0;
double yStep = 0;
// avoid a divide by zero
if(swipeSteps == 0)
swipeSteps = 1;
xStep = ((double)(upX - downX)) / swipeSteps;
yStep = ((double)(upY - downY)) / swipeSteps;
// first touch starts exactly at the point requested
ret = touchDown(downX, downY);
for(int i = 1; i < swipeSteps; i++) {
ret &= touchMove(downX + (int)(xStep * i), downY + (int)(yStep * i));
if(ret == false)
break;
// set some known constant delay between steps as without it this
// become completely dependent on the speed of the system and results
// may vary on different devices. This guarantees at minimum we have
// a preset delay.
SystemClock.sleep(5);
}
ret &= touchUp(upX, upY);
return(ret);
}
在swipe方法里在起始点touchDown,然后中间点都是touchMove,在结束点touchUp
scrollForward
public boolean scrollForward(int steps) {
Log.d(LOG_TAG, "scrollForward() on selector = " + getSelector());
AccessibilityNodeInfo node = findAccessibilityNodeInfo(WAIT_FOR_SELECTOR_TIMEOUT);
if(node == null) {
// Object Not Found
return false;
}
Rect rect = new Rect();;
node.getBoundsInScreen(rect);
int downX = 0;
int downY = 0;
int upX = 0;
int upY = 0;
// scrolling is by default assumed vertically unless the object is explicitly
// set otherwise by setAsHorizontalContainer()
if(mIsVerticalList) {
int swipeAreaAdjust = (int)(rect.height() * getSwipeDeadZonePercentage());
// scroll vertically: swipe down -> up
downX = rect.centerX();
downY = rect.bottom - swipeAreaAdjust;
upX = rect.centerX();
upY = rect.top + swipeAreaAdjust;
} else {
int swipeAreaAdjust = (int)(rect.width() * getSwipeDeadZonePercentage());
// scroll horizontally: swipe right -> left
// TODO: Assuming device is not in right to left language
downX = rect.right - swipeAreaAdjust;
downY = rect.centerY();
upX = rect.left + swipeAreaAdjust;
upY = rect.centerY();
}
return getInteractionController().scrollSwipe(downX, downY, upX, upY, steps);
}
源码可以看出最后调用了InteractionController.scrollSwipe,进入到该方法:
public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
final int steps) {
Log.d(LOG_TAG, "scrollSwipe (" + downX + ", " + downY + ", " + upX + ", "
+ upY + ", " + steps +")");
try {
mUiAutomatorBridge.executeCommandAndWaitForAccessibilityEvent(
new Runnable() {
@Override
public void run() {
swipe(downX, downY, upX, upY, steps);
}
},
new Predicate<AccessibilityEvent>() {
@Override
public boolean apply(AccessibilityEvent event) {
return (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED);
}
}, DEFAULT_SCROLL_EVENT_TIMEOUT_MILLIS);
} catch (Exception e) {
Log.e(LOG_TAG, "Error in scrollSwipe: " + e.getMessage());
return false;
}
return true;
}
可以看到在run方法里他也是调用了swipe方法。那为何swipeLeft可以滑动,而scrollForward却不可以?
去4.4.2版本里去看看这两个方法是否有改动。但是4.4.2源码与4.1.1的源码是一样的的,没有改动,从swipe方法的坐标入手试试。因为swipeleft方法中滑动点的坐标是从距右边框5个像素,滑倒距左边框5个像素,而scrollForward是从距右边框54像素滑到距左边框54像素(宽度*盲区百分比)的。在距离上swipeleft传给swipe的坐标轨迹要比scrollForward的距离远。然后看看界面的视图
从uiautomatorviewer中可以看出可滑动的区域为整个屏幕bounds:[0,0][540,960].而滑动时真正滚动的区域为[6,92][534,748],有6个像素的区别。下面我们通过直接调用InteractionController里的swipe方法,来验证这个想法的正确性。由于InteractionController里的方法都是定义的包内可用,不能直接通过UiObject里的getInteractionContoller得到该对象,然后调用swipe()。那么怎么办呢?好在UiDevice里有个方法swipe:
public boolean swipe(int startX, int startY, int endX, int endY, int steps) {
Tracer.trace(startX, startY, endX, endY, steps);
return getAutomatorBridge().getInteractionController()
.swipe(startX, startY, endX, endY, steps);
}
刚好也是调用InteractionController中的swipe方法,那么我们来开始代码的编写:
public void test_EnterApp() throws UiObjectNotFoundException{
uiDevice = getUiDevice();
uiDevice.pressHome();
UiScrollable appList = UiUtil.findUiScrollableByScrollable(true);
if (appList.exists()) {
appList.setAsHorizontalList();
//uiDevice.swipe(535, 480, 5, 480, 20);
uiDevice.swipe(540-54, 480, 54, 480, 20);
}
}
通过上面的实验,结论是2个方法都成功的滚动了,说明不是坐标差异造成的滚动差异。这就又开始让我疑惑了,看了一下4.4.2里InteractionController中scrollSwipe改动太大了:
public boolean scrollSwipe(final int downX, final int downY, final int upX, final int upY,
final int steps) {
Log.d(LOG_TAG, "scrollSwipe (" + downX + ", " + downY + ", " + upX + ", "
+ upY + ", " + steps +")");
Runnable command = new Runnable() {
@Override
public void run() {
swipe(downX, downY, upX, upY, steps);
}
};
// Collect all accessibility events generated during the swipe command and get the
// last event
ArrayList<AccessibilityEvent> events = new ArrayList<AccessibilityEvent>();
runAndWaitForEvents(command,
new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
Configurator.getInstance().getScrollAcknowledgmentTimeout());
AccessibilityEvent event = getLastMatchingEvent(events,
AccessibilityEvent.TYPE_VIEW_SCROLLED);
if (event == null) {
// end of scroll since no new scroll events received
recycleAccessibilityEvents(events);
return false;
}
// AdapterViews have indices we can use to check for the beginning.
boolean foundEnd = false;
if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
foundEnd = event.getFromIndex() == 0 ||
(event.getItemCount() - 1) == event.getToIndex();
Log.d(LOG_TAG, "scrollSwipe reached scroll end: " + foundEnd);
} else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
// Determine if we are scrolling vertically or horizontally.
if (downX == upX) {
// Vertical
foundEnd = event.getScrollY() == 0 ||
event.getScrollY() == event.getMaxScrollY();
Log.d(LOG_TAG, "Vertical scrollSwipe reached scroll end: " + foundEnd);
} else if (downY == upY) {
// Horizontal
foundEnd = event.getScrollX() == 0 ||
event.getScrollX() == event.getMaxScrollX();
Log.d(LOG_TAG, "Horizontal scrollSwipe reached scroll end: " + foundEnd);
}
}
recycleAccessibilityEvents(events);
return !foundEnd;
}
教训:看源码一定要看到方法一层层调用,直到谷歌在API没有的类。那个里面往往有着不被人发现的秘密。
但是该方法也没有太大的逻辑上的改动。我尝试着在其他可滚动的控件里使用scrollForward方法。比如在更换壁纸里,该方法是可用的。而且在其他手机的appLiist界面使用scrollForward也无错。
这个与上面那张applist的区别在于,scrollable为true的布局和真正滚动的是一个布局。而上面scrollable布局为整个屏幕,而真正滚动的是中间那部分应用列表。这个我是不是可以归咎与开发的问题,所以问题的根究应该在与scrollable布局的设置,我改一下定位可滚动的控件试试。首先我获得之前获得的控件的坐标:
public void test_EnterApp() throws UiObjectNotFoundException {
uiDevice = getUiDevice();
//uiDevice.pressHome();
UiScrollable appList = UiUtil.findUiScrollableByScrollable(true);
Log.i(TAG, appList.getBounds().toString());
if (appList.exists()) {
appList.setAsHorizontalList();
while (true) {
appList.scrollForward();
}
}
}
Log输出:
01-02 02:21:47.869: I/Stress(27282): Rect(-1698, -832 - 2240, 1793)
01-02 02:32:28.474: I/QueryController(27608): Matched selector: UiSelector[SCROLLABLE=true] <<==>>
[android.view.accessibility.AccessibilityNodeInfo@8718; boundsInParent: Rect(540, 0 - 4479, 2626);
boundsInScreen: Rect(-1698, -832 - 2240, 1793); packageName: com.android.sprdlauncher2; className: android.view.View;
text: null; contentDescription: null; viewIdResName: com.android.sprdlauncher2:id/workspace; checkable: false;
checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: true; enabled: true;
password: false; scrollable: true;
[ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_LONG_CLICK, ACTION_ACCESSIBILITY_FOCUS, ACTION_SCROLL_FORWARD, ACTION_SCROLL_BACKWARD]]
01-02 02:32:28.474: D/InteractionController(27608): scrollSwipe (1847, 480, -1305, 480, 20)
01-02 02:32:28.474: I/InputDispatcher(594): Dropping event because there is no touchable window at (1847, 480).
01-02 02:32:28.474: W/InputManager(594): Input event injection from pid 27608 failed.
01-02 02:32:28.484: W/InputManager(594): Input event injection from pid 27608 failed.
01-02 02:32:28.484: W/InputManager(594): Input event injection from pid 27608 failed.
01-02 02:32:28.684: W/InteractionController(27608): runAndwaitForEvent timedout waiting for events
从日志来看,点击的区域不在屏幕可见位置,终于找到它不滚动的原因啦。瞎琢磨一通啊,终于找到原因。现在分析原因开始。
总结:
1.swipeLeft的方法调用过程是swipeLeft--->swipe,scrollForward调用过程scrollForward--->scrollSwipe--->swipe。scrollForward就卡在了第二步scrollSwipe。因为scrollSwipe里的方法点击的点需要可触摸。而swipe则不需要这样的限制。
2.在遇到这种可滚动控件是整个屏幕的,尽量使用swipeLeft。
3.分析源码时针对当前版本,因为不同版本改动很大。
4.要低调!