Simulating Swipes in Your Android Tests
Wednesday, Feb 13, 2013 |Simulating Swipes in Your Android Tests
Jason Lee 2013-02-13
We'll cover three, in descreasing order of complexity and pure geekiness. The first will programmatically simulate the swiping action:
protected void swipe(Direction direction) {
Instrumentation inst = getInstrumentation();
Point size = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(size);
int width = size.x;
long downTime = SystemClock.uptimeMillis();
float xStart = ((direction == Direction.Left) ? (width - 10) : 10);
float xEnd = ((direction == Direction.Left) ? 10 : (width - 10));
// The value for y doesn't change, as we want to swipe straight across
inst.sendPointerSync(MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN, xStart, size.y / 2, 0));
inst.sendPointerSync(MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE, xEnd, size.y / 2, 0));
inst.sendPointerSync(MotionEvent.obtain(downTime, SystemClock.uptimeMillis() + 1000,
MotionEvent.ACTION_UP, xEnd, size.y / 2, 0));
}
In this implementation of our swipe()
method, we simulate the physical act of swiping by using MotionEvent
objects. While I'm not going to pretend to understand every last nuance here, we start by determining the size of the screen, then setting our starting position 10 pixels from the edge. The ending position is then set 10 pixels away from the other edge. With those values set, we obtain tree MotionEvent
objects for ACTION_DOWN
, ACTION_MOVE
, and ACTION_UP
, passing them each to Instrumentation.sendPointerSync()
in turn. With that, we've completed our swipe.
For completeness' sake, here is the super simple enum I used to make the method signature more self-explanatory:
public enum Direction {
Left, Right;
}
While that approach is pretty fun, there's a simpler way, which, oddly enough, actually uses the ViewPager
API. :)
protected void swipe(final Direction direction) {
activity.runOnUiThread(new Runnable() {
public void run() {
int current = pager.getCurrentItem();
if (direction == Direction.Right) {
if (current > 0) {
pager.setCurrentItem(current - 1, true);
}
} else {
if (current < pager.getChildCount()) {
pager.setCurrentItem(current + 1, true);
}
}
}
});
}
In this implementation, we make sure that we can safely swipe to the left or right, as appropriate, then set the current item index on the ViewPager
to "current" plus or minus one. We pass true
as the second argument to setCurrentItem()
so that we can see the animation in the UI; otherwise, it just changes in a blink, and where's the fun in that. Note that this must run on the UI thread, so I've wrapped all of that in a Runnable
, which is pass to Activity.runOnUiThread()
.
Lastly, we deviate from the ViewPager
API usage, and look at another, simpler take on our first implementation, this time using Robotium
protected void swipe(final Direction direction) {
Point size = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(size);
int width = size.x;
float xStart = ((direction == Direction.Left) ? (width - 10) : 10);
float xEnd = ((direction == Direction.Left) ? 10 : (width - 10));
// The value for y doesn't change, as we want to swipe straight across
solo.drag(xStart, xEnd, size.y / 2, size.y / 2, 1);
}
Again, we do our endpoint calculations, but we use them in a single call to solo.drag()
. Much simpler.
Assuming you need to do something like this, I guess the implementation is a matter of preference. I tend to prefer option 2, as it seems a more proper use of the API and is a little less hacky than options 1 and #3, but I did enjoy learning those. In a more general sense, though, if you need to perform a swipe in a test and you don't have a control you can directly (and easily) manipulate like the ViewPager
, these two options show how it can be done, either directly with the Android APIs, or with the very nice Robotium wrapper.
Have you found another/better way to do all of this? Hit the comment box and show me the error of my ways! :)