在我的应用程序中,我有一个功能,即用户可以通过长按以创建拖动阴影来重新排序列表中的项目,然后用户可以在所选位置拖动和插入拖动阴影。丢弃后,物品将被重新订购。
我正在努力为此开发UI测试。我能够成功地长按项目,创建拖动阴影或实现拖动动作。我似乎无法将两者结合成一个动作。
我在Android UI测试中使用Espresso和Barista。
长按一下,我使用了Barista的API:
longClickOn("ITEM");
对于拖动动作,我尝试创建自己的Espresso ViewAction:
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(ViewGroup.class);
}
@Override
public String getDescription() {
return "Swiping child " + srcIndex + " to child " + destIndex;
}
@Override
public void perform(UiController uiController, View view) {
ViewGroup parent = (ViewGroup) view;
final View srcChild = parent.getChildAt(srcIndex);
final View destChild = parent.getChildAt(destIndex);
final CoordinatesProvider srcCoordinatesProvider = new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
int[] location = new int[2];
srcChild.getLocationInWindow(location);
float x = location[0] + (view.getMeasuredWidth() / 2);
float y = location[1] + (view.getMeasuredHeight() / 2);
return new float[] {x, y};
}
};
final CoordinatesProvider destCoordinatesProvider = new CoordinatesProvider() {
@Override
public float[] calculateCoordinates(View view) {
int[] location = new int[2];
destChild.getLocationInWindow(location);
float x = location[0] + (view.getMeasuredWidth() / 2);
float y = location[1] + (view.getMeasuredHeight() / 2);
return new float[] {x, y};
}
};
GeneralSwipeAction swipe = new GeneralSwipeAction(Swipe.FAST,
srcCoordinatesProvider, destCoordinatesProvider, Press.FINGER);
swipe.perform(uiController, parent);
}
};
编辑:
根据@Be_Negative的回答,我已经定制了给定的答案并提出了这个问题:
private static ViewAction drag(final int srcIndex, final int destIndex) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isAssignableFrom(ViewGroup.class);
}
@Override
public String getDescription() {
return "Swiping child " + srcIndex + " to child " + destIndex;
}
@Override
public void perform(UiController uiController, View view) {
ViewGroup parent = (ViewGroup) view;
uiController.loopMainThreadUntilIdle();
final View srcChild = parent.getChildAt(srcIndex);
final View destChild = parent.getChildAt(destIndex);
final CoordinatesProvider coordinatesProvider = getCoordinatesProdvider();
float[] precision = Press.PINPOINT.describePrecision();
MotionEvent downEvent = MotionEvents.sendDown(uiController, coordinatesProvider.calculateCoordinates(srcChild), precision).down;
try {
long longPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5);
uiController.loopMainThreadForAtLeast(longPressTimeout);
float[][] steps = interpolateDragging(
coordinatesProvider.calculateCoordinates(srcChild),
coordinatesProvider.calculateCoordinates(destChild)
);
uiController.loopMainThreadUntilIdle();
for(float[] step : steps) {
if( !MotionEvents.sendMovement(uiController, downEvent, step)) {
MotionEvents.sendCancel(uiController, downEvent);
}
}
if(!MotionEvents.sendUp(uiController, downEvent, coordinatesProvider.calculateCoordinates(destChild))) {
MotionEvents.sendCancel(uiController, downEvent);
}
} catch(Exception e) {
System.out.println(e);
} finally {
downEvent.recycle();
}
}
};
}
不幸的是,它会在MotionEvents.sendMovement
处抛出错误,如下所示:
Error performing 'inject motion event (corresponding down event: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=540.0, y[0]=302.0, toolType[0]=TOOL_TYPE_UNKNOWN, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=34586525, downTime=34586525, deviceId=0, source=0x1002 })' on view 'unknown'.
答案 0 :(得分:0)
这是我对此的看法。我严重依赖long press和swipe的现有实现将它们组合在一起。有一些分歧思想 - 例如默认实现中的拖动插补器倾向于下冲,所以我不得不稍微调整一下。
class DragAndDropAction(private val sourceViewPosition: Int,
private val targetViewPosition: Int) : ViewAction {
override fun getConstraints(): Matcher<View> {
return allOf(isDisplayed(), isAssignableFrom(RecyclerView::class.java))
}
override fun getDescription(): String {
return "Drag and drop action"
}
override fun perform(uiController: UiController, view: View) {
val recyclerView: RecyclerView = view as RecyclerView
//Sending down
recyclerView.scrollToPosition(sourceViewPosition)
uiController.loopMainThreadUntilIdle()
val sourceView = recyclerView.findViewHolderForAdapterPosition(sourceViewPosition).itemView
val sourceViewCenter = GeneralLocation.VISIBLE_CENTER.calculateCoordinates(sourceView)
val fingerPrecision = Press.FINGER.describePrecision()
val downEvent = MotionEvents.sendDown(uiController, sourceViewCenter, fingerPrecision).down
try {
// Factor 1.5 is needed, otherwise a long press is not safely detected.
val longPressTimeout = (ViewConfiguration.getLongPressTimeout() * 1.5f).toLong()
uiController.loopMainThreadForAtLeast(longPressTimeout)
//Drag to the position
recyclerView.scrollToPosition(targetViewPosition)
uiController.loopMainThreadUntilIdle()
val targetView = recyclerView.findViewHolderForAdapterPosition(targetViewPosition).itemView
val targetViewLocation = if (targetViewPosition > sourceViewPosition) {
GeneralLocation.BOTTOM_CENTER.calculateCoordinates(targetView)
} else {
GeneralLocation.TOP_CENTER.calculateCoordinates(targetView)
}
val steps = interpolate(sourceViewCenter, targetViewLocation)
for (i in 0 until steps.size) {
if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) {
MotionEvents.sendCancel(uiController, downEvent)
}
}
//Release
if (!MotionEvents.sendUp(uiController, downEvent, targetViewLocation)) {
MotionEvents.sendCancel(uiController, downEvent)
}
} finally {
downEvent.recycle()
}
}
private val SWIPE_EVENT_COUNT = 10
private fun interpolate(start: FloatArray, end: FloatArray): Array<FloatArray> {
val res = Array(SWIPE_EVENT_COUNT) { FloatArray(2) }
for (i in 1..SWIPE_EVENT_COUNT) {
res[i - 1][0] = start[0] + (end[0] - start[0]) * i / SWIPE_EVENT_COUNT
res[i - 1][1] = start[1] + (end[1] - start[1]) * i / SWIPE_EVENT_COUNT
}
return res
}
}
还有很大的改进空间,例如理想情况下,我希望我的行动能够吸引观察者的职位。除此之外,似乎工作正常。