package com.robotium.solo;
import java.util.ArrayList;
import com.robotium.solo.Solo.Config;
import junit.framework.Assert;
import android.app.Instrumentation;
import android.content.Context;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.GridView;
import android.widget.ListView;
import android.widget.ScrollView;
/**
* Contains scroll methods. Examples are scrollDown(), scrollUpList(),
* scrollToSide().
*
* @author Renas Reda, renas.reda@robotium.com
*
*/
class Scroller {
public static final int DOWN = 0;
public static final int UP = 1;
public enum Side {LEFT, RIGHT}
private boolean canScroll = false;
private final Instrumentation inst;
private final ViewFetcher viewFetcher;
private final Sleeper sleeper;
private final Config config;
/**
* Constructs this object.
*
* @param inst the {@code Instrumentation} instance
* @param viewFetcher the {@code ViewFetcher} instance
* @param sleeper the {@code Sleeper} instance
*/
public Scroller(Config config, Instrumentation inst, ViewFetcher viewFetcher, Sleeper sleeper) {
this.config = config;
this.inst = inst;
this.viewFetcher = viewFetcher;
this.sleeper = sleeper;
}
/**
* Simulate touching a specific location and dragging to a new location.
*
* This method was copied from {@code TouchUtils.java} in the Android Open Source Project, and modified here.
*
* @param fromX X coordinate of the initial touch, in screen coordinates
* @param toX Xcoordinate of the drag destination, in screen coordinates
* @param fromY X coordinate of the initial touch, in screen coordinates
* @param toY Y coordinate of the drag destination, in screen coordinates
* @param stepCount How many move steps to include in the drag
*/
public void drag(float fromX, float toX, float fromY, float toY,
int stepCount) {
long downTime = SystemClock.uptimeMillis();
long eventTime = SystemClock.uptimeMillis();
float y = fromY;
float x = fromX;
float yStep = (toY - fromY) / stepCount;
float xStep = (toX - fromX) / stepCount;
**// Create a new MotionEvent, filling in a subset of the basic motion values.**
MotionEvent event = MotionEvent.obtain(downTime, eventTime,MotionEvent.ACTION_DOWN, fromX, fromY, 0);
try {
inst.sendPointerSync(event); //Instrumentation 用来模拟touch 点击down
} catch (SecurityException ignored) {}
for (int i = 0; i < stepCount; ++i) {
y += yStep;
x += xStep;
eventTime = SystemClock.uptimeMillis();
event = MotionEvent.obtain(downTime, eventTime,MotionEvent.ACTION_MOVE, x, y, 0);//移动事件
try {
inst.sendPointerSync(event); //移动
} catch (SecurityException ignored) {}
}
eventTime = SystemClock.uptimeMillis();
event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP,toX, toY, 0); //抬起手指事件
try {
inst.sendPointerSync(event);//
} catch (SecurityException ignored) {}
}
/**
* Scrolls a ScrollView.
*
* @param direction the direction to be scrolled
* @return {@code true} if scrolling occurred, false if it did not
*/
private boolean scrollView(final View view, int direction){
if(view == null){
return false;
}
//获取View高度
int height = view.getHeight();
height--; //减一,
int scrollTo = -1;
//向下滚动,滚动量为正值
if (direction == DOWN) {
scrollTo = height;
}
//向上滚动,滚动量为负值
else if (direction == UP) {
scrollTo = -height;
}
//记录当前控件处于滚动框顶部的位置
int originalY = view.getScrollY();
final int scrollAmount = scrollTo;
inst.runOnMainSync(new Runnable(){
public void run(){
view.scrollBy(0, scrollAmount);(x,y)
}
});
//对比滚动前后的位置,如果一样,说明已到达底部,返回false,否则返回true
if (originalY == view.getScrollY()) {
return false;
}
else{
return true;
}
}
/**
* Scrolls a ScrollView to top or bottom.
*
* @param direction the direction to be scrolled
*/
private void scrollViewAllTheWay(final View view, final int direction) {
while(scrollView(view, direction));
}
/**
* Scrolls up or down.
*
* @param direction the direction in which to scroll
* @return {@code true} if more scrolling can be done
*/
public boolean scroll(int direction) {
return scroll(direction, false);
}
/**
* Scrolls down.
*
* @return {@code true} if more scrolling can be done
*/
public boolean scrollDown() {
if(!config.shouldScroll) {
return false;
}
return scroll(Scroller.DOWN);
}
/**
* Scrolls up and down.
*
* @param direction the direction in which to scroll
* @param allTheWay <code>true</code> if the view should be scrolled to the beginning or end,
* <code>false</code> to scroll one page up or down.
* @return {@code true} if more scrolling can be done
*/
@SuppressWarnings("unchecked")
public boolean scroll(int direction, boolean allTheWay) {
ArrayList<View> viewList = RobotiumUtils.
removeInvisibleViews(viewFetcher.getAllViews(true));
ArrayList<View> views = RobotiumUtils.filterViewsToSet(new Class[] { ListView.class,
ScrollView.class, GridView.class, WebView.class}, viewList);
View view = viewFetcher.getFreshestView(views);
if (view == null) {
view = viewFetcher.getRecyclerView(0, 5);
if(view == null){
return false;
}
}
if (view instanceof AbsListView) {
return scrollList((AbsListView)view, direction, allTheWay);
}
if(view instanceof WebView){
return scrollWebView((WebView)view, direction, allTheWay);
}
if (allTheWay) {
scrollViewAllTheWay(view, direction);
return false;
} else {
return scrollView(view, direction);
}
}
/**
* Scrolls a WebView.
*
* @param webView the WebView to scroll
* @param direction the direction to scroll
* @param allTheWay {@code true} to scroll the view all the way up or down, {@code false} to scroll one page up or down or down.
* @return {@code true} if more scrolling can be done
*/
//滚动webView时,直接调用webView.pageDown()或
//webView.pageUp()
public boolean scrollWebView(final WebView webView, int direction, final boolean allTheWay){
if (direction == DOWN) {
inst.runOnMainSync(new Runnable(){
public void run(){
canScroll = webView.pageDown(allTheWay);
}
});
}
if(direction == UP){
inst.runOnMainSync(new Runnable(){
public void run(){
canScroll = webView.pageUp(allTheWay);
}
});
}
return canScroll;
}
/**
* Scrolls a list.
*
* @param absListView the list to be scrolled
* @param direction the direction to be scrolled
* @param allTheWay {@code true} to scroll the view all the way up or down, {@code false} to scroll one page up or down
* @return {@code true} if more scrolling can be done
*/
public <T extends AbsListView> boolean scrollList(T absListView, int direction, boolean allTheWay) {
if(absListView == null){
return false;
}
if (direction == DOWN) { //向下滚动
//AdapterView 包含的条目数目(所有的条目,不仅仅是可见的)
int listCount = absListView.getCount();
//返回adapter's 数据集 可见的最后一个条目的位置
int lastVisiblePosition = absListView.getLastVisiblePosition();
//直接滚到底
if (allTheWay) {
scrollListToLine(absListView, listCount-1);
return false;
}
if (lastVisiblePosition >= listCount - 1) {
//已经到达列表底部,
if(lastVisiblePosition > 0){
scrollListToLine(absListView, lastVisiblePosition);//滚到最后一个
}
return false;
}
//第一个可见的位置
int firstVisiblePosition = absListView.getFirstVisiblePosition();
//列表的宽度大于1
if(firstVisiblePosition != lastVisiblePosition)
scrollListToLine(absListView, lastVisiblePosition);
//列表的显示宽度等于1,则要滚动至FirstVisiblePosition()+1
else
scrollListToLine(absListView, firstVisiblePosition + 1);
} else if (direction == UP) { //向上滚动
int firstVisiblePosition = absListView.getFirstVisiblePosition();
//allTheWay或列表长度小于2,则直接滚到顶部
if (allTheWay || firstVisiblePosition < 2) {
scrollListToLine(absListView, 0);
return false;
}
int lastVisiblePosition = absListView.getLastVisiblePosition();
//计算列表的显示宽度
final int lines = lastVisiblePosition - firstVisiblePosition;
//根据宽度计算出该滚动至第几行
int lineToScrollTo = firstVisiblePosition - lines;
//当显示宽度为1时,向上回滚一行即可
if(lineToScrollTo == lastVisiblePosition)
lineToScrollTo--;
//当上面的隐藏行数小于显示宽度时,直接滚动至第一行即可
if(lineToScrollTo < 0)
lineToScrollTo = 0;
scrollListToLine(absListView, lineToScrollTo);//滚动到指定行
}
sleeper.sleep();
return true;
}
/**
* Scroll the list to a given line
*
* @param view the {@link AbsListView} to scroll
* @param line the line to scroll to
*/
public <T extends AbsListView> void scrollListToLine(final T view, final int line){
if(view == null)
Assert.fail("AbsListView is null!");
final int lineToMoveTo;
if(view instanceof GridView) {
lineToMoveTo = line+1;
}
else {
lineToMoveTo = line;
}
//在应用主线程执行一个请求可以通过在主线程直接运行修改控件属性的代码来操作控件
inst.runOnMainSync(new Runnable(){
public void run(){
view.setSelection(lineToMoveTo);
}
});
}
/**
* Scrolls horizontally.
*
* @param side the side to which to scroll; {@link Side#RIGHT} or {@link Side#LEFT}
* @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.55.
* @param stepCount how many move steps to include in the scroll. Less steps results in a faster scroll
*/
@SuppressWarnings("deprecation")
public void scrollToSide(Side side, float scrollPosition, int stepCount) {
WindowManager windowManager = (WindowManager)
inst.getTargetContext().getSystemService(Context.WINDOW_SERVICE);
int screenHeight = windowManager.getDefaultDisplay()
.getHeight();
int screenWidth = windowManager.getDefaultDisplay()
.getWidth();
float x = screenWidth * scrollPosition;
float y = screenHeight / 2.0f; //y屏幕中间
if (side == Side.LEFT)//左滑,为啥是70开始?
drag(70, x, y, y, stepCount);
else if (side == Side.RIGHT)//右滑
drag(x, 0, y, y, stepCount);
}
/**
* Scrolls view horizontally.
*
* @param view the view to scroll
* @param side the side to which to scroll; {@link Side#RIGHT} or {@link Side#LEFT}
* @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.55.
* @param stepCount how many move steps to include in the scroll. Less steps results in a faster scroll
*/
public void scrollViewToSide(View view, Side side, float scrollPosition, int stepCount) {
int[] corners = new int[2];
view.getLocationOnScreen(corners);
int viewHeight = view.getHeight();
int viewWidth = view.getWidth();
float x = corners[0] + viewWidth * scrollPosition;
float y = corners[1] + viewHeight / 2.0f;
if (side == Side.LEFT)
drag(corners[0], x, y, y, stepCount);
else if (side == Side.RIGHT)
drag(x, corners[0], y, y, stepCount);
}
}