Reputation: 1836
I'm trying to programmatically trigger a swipe in a view. There are other questions with answers but they haven't worked for me. I also saw some comments that said programmatic dragging/scrolling was disabled for security reasons, is that true? I'd love to find a definitive answer on this.
I've tried the following method that the poster showed working via a video, so it should work! Is there something I need in my AndroidManifest
? I've also tried the scrollBy()
API but that doesn't scroll my content, and instead moves the content offscreen.
What's more, I've registered a OnTouchListener
with my view and seen that the below code copies the same format of event firing (ACTION_DOWN
,ACTION_MOVE
, ACTION_UP)
that a click+drag, and mouse wheel do, though the click+drag and a mouse wheel works while the programmatic scroll/swipe does not.
final Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
final MotionEvent event = MotionEvent.obtain(System.currentTimeMillis(), System.currentTimeMillis(), MotionEvent.ACTION_DOWN, 500, 700, 0);
dispatchTouchEvent(event);
event.recycle();
}
});
handler.postDelayed(new Runnable() {
@Override
public void run() {
final MotionEvent event = MotionEvent.obtain(System.currentTimeMillis(), System.currentTimeMillis(), MotionEvent.ACTION_MOVE, 500, 700 ,0);
dispatchTouchEvent(event);
event.recycle();
}
}, 50);
handler.postDelayed(new Runnable() {
@Override
public void run() {
final MotionEvent event = MotionEvent.obtain(System.currentTimeMillis(), System.currentTimeMillis(), MotionEvent.ACTION_MOVE, 500 ,700 + 400, 0);
dispatchTouchEvent(event);
event.recycle();
}
}, 100);
handler.postDelayed(new Runnable() {
@Override
public void run() {
final MotionEvent event = MotionEvent.obtain(System.currentTimeMillis(), System.currentTimeMillis(), MotionEvent.ACTION_UP, 500, 700 + 400, 0);
dispatchTouchEvent(event);
event.recycle();
}
}, 1000);
Upvotes: 1
Views: 3294
Reputation: 45042
This is my fling method
/**
* Simulate touching a specific location and dragging to a new location.
*
* @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
*/
fun fling(
fromX: Float, toX: Float, fromY: Float,
toY: Float, stepCount: Int
) {
val inst = Instrumentation()
val downTime = SystemClock.uptimeMillis()
var eventTime = SystemClock.uptimeMillis()
var y = fromY
var x = fromX
val yStep = (toY - fromY) / stepCount
val xStep = (toX - fromX) / stepCount
var event = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_DOWN, fromX, fromY, 0
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
event.source = InputDevice.SOURCE_TOUCHSCREEN
}
inst.sendPointerSync(event)
for (i in 0 until stepCount) {
y += yStep
x += xStep
eventTime = SystemClock.uptimeMillis()
event = MotionEvent.obtain(
downTime, eventTime + stepCount,
MotionEvent.ACTION_MOVE, x, y, 0
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
event.source = InputDevice.SOURCE_TOUCHSCREEN
}
inst.sendPointerSync(event)
}
eventTime = SystemClock.uptimeMillis() + stepCount.toLong() + 2
event = MotionEvent.obtain(
downTime, eventTime,
MotionEvent.ACTION_UP, toX, toY, 0
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
event.source = InputDevice.SOURCE_TOUCHSCREEN
}
inst.sendPointerSync(event)
}
And this is how I use it
fab.setOnClickListener { view ->
Thread(Runnable {
try {
fling(500f ,900f ,530f ,20f, 5);
// emulateMptionEvent()
} catch (e: Exception) {
}
}).start()
}
This is how it works:
Before
After clicking on Fab Button, Blue dotted line is emulated fling path.
Upvotes: 3
Reputation: 76569
With androidx.test.uiautomator
this is entirely possible - and this is a real-world scenario. I've wrote this method to test an endless scrolling RecyclerView
...just because I'm too lazy to swipe:
protected void fling(UiObject2 view, int speed, int pause) {
Assert.assertThat(view, not(equalTo(null)));
try {
view.fling(Direction.DOWN, speed);
} catch (StaleObjectException e) {
Assert.fail();
}
sleep(pause);
}
event automation with adb shell input swipe
is also possible ...
in both cases, the event does not originate from the app, which is tested.
and in both cases, is relies on android.permission.INJECT_EVENTS
.
on a rooted device, one does not even need adb
- see this answer.
Upvotes: 0