2017-12-31 164 views
0

私のアプリケーションでは、ユーザがリスト内の項目を並べ替えることができるようにしました。ドラッグしてドラッグしてドラッグ・シャドウを作成すると、選択した位置で一旦落とすと、アイテムは並べ替えられます。AndroidのUIテスト長いクリックとドラッグ

私はこのためのUIテストを開発するのに苦労しています。ドラッグ・シャドウを作成するため、またはドラッグ・モーションを実装するために、項目を長時間クリックすることができます。私は2つを組み合わせて1つの動きにすることはできないようです。

私はAndroid UIテストでEspressoとBaristaを使用しています。私はバリスタのAPIを使用長いクリックのために

:ドラッグの動きを

longClickOn("ITEM"); 

を、私は私自身のエスプレッソ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); 
     } 
    }; 

EDIT:

@ Bee_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(); 
      } 
     } 
    }; 
} 

次のようにUnforunately、それは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

が、この時の私のショットです。私は、既存の実装であるlong pressswipeに大いに依存していました。いくつかのディジェネレーションの考え方があります。たとえば、デフォルト実装のドラッグインターポレータはアンダーシュートする傾向がありますので、少し調整する必要がありました。

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 
    } 
} 

は、例えば、理想的に私は私の行動は、位置のviewmatchersのinstseadに取るしたい、改善の余地のトンがあります。それ以外は、正常に動作するようです。

+0

ありがとうございました。残念ながら、それはまだかなり働いていません。詳細については私の編集をチェックしてください。 –

+0

システムのUI要素(キーボード、通知パネル、または下部のボタンバー)のいずれかに触れ始めるようです。私はあなたのビューアクションにスクロールを組み込む必要があると思う。私の場合、私はリサイクラビューを使用していて、アイテムがドラッグされているアイテムにスクロールしていました。 recyclerviewにいない場合は、[ScrollToAction](https://android.googlesource.com/platform/frameworks/testing/+/android-support-test/espresso/core/src/main/java/android)を使用できます。 /support/test/espresso/action/ScrollToAction.java)を参照として –

+0

実際には、あなたのカスタム 'ViewMatchers.withParentIndex()'からビューアクションを呼び出すことは便利かもしれませんし、与えられたものとちょうど一致する独自のマッチャーを書くこともできますビュー(デフォルトではエスプレッソであり、悪用される可能性のあるマッチャーは正当に提供されません) –

関連する問題