(如果想知道结果,直接看总结)
先来看下各自的源码(4.1.1版)
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); }
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(); } } }
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.要低调!