@Override public boolean handleMoveTouchEvent(MotionEvent event) { final View view = mViewAdapter.getView(); if (!mMoveAttr.init(view, event)) { return false; } // Has over-scrolling officially started? if ((mViewAdapter.isInAbsoluteStart() && mMoveAttr.mDir) || (mViewAdapter.isInAbsoluteEnd() && !mMoveAttr.mDir)) { // Save initial over-scroll attributes for future reference. mStartAttr.mPointerId = event.getPointerId(0); mStartAttr.mAbsOffset = mMoveAttr.mAbsOffset; mStartAttr.mDir = mMoveAttr.mDir; issueStateTransition(mOverScrollingState); return mOverScrollingState.handleMoveTouchEvent(event); } return false; }
@Test public void detach_overScrollInEffect_detachFromView() throws Exception { when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); HorizontalOverScrollBounceEffectDecorator uut = getUUT(); uut.onTouch(mView, createShortRightMoveEvent()); // Act uut.detach(); // Assert verify(mView).setOnTouchListener(eq((View.OnTouchListener) null)); verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS); }
@Override public View getView() { return mViewAdapter.getView(); }
@Test public void detach_overScrollInEffect_detachFromView() throws Exception { when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); VerticalOverScrollBounceEffectDecorator uut = getUUT(); uut.onTouch(mView, createShortDownwardsMoveEvent()); // Act uut.detach(); // Assert verify(mView).setOnTouchListener(eq((View.OnTouchListener) null)); verify(mView).setOverScrollMode(View.OVER_SCROLL_ALWAYS); }
protected ObjectAnimator createBounceBackAnimator(float startOffset) { final View view = mViewAdapter.getView(); // Duration is proportional to the view's size. float bounceBackDuration = (Math.abs(startOffset) / mAnimAttributes.mMaxOffset) * MAX_BOUNCE_BACK_DURATION_MS; ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(view, mAnimAttributes.mProperty, mStartAttr.mAbsOffset); bounceBackAnim.setDuration(Math.max((int) bounceBackDuration, MIN_BOUNCE_BACK_DURATION_MS)); bounceBackAnim.setInterpolator(mBounceBackInterpolator); bounceBackAnim.addUpdateListener(this); return bounceBackAnim; } }
@Test public void onTouchMoveAction_dragRightInRightEnd_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createShortRightMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(false); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true); HorizontalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }
@Before public void setUp() throws Exception { mView = mock(View.class); mViewAdapter = mock(IOverScrollDecoratorAdapter.class); when(mViewAdapter.getView()).thenReturn(mView); mStateListener = mock(IOverScrollStateListener.class); mUpdateListener = mock(IOverScrollUpdateListener.class); }
@Test public void onTouchUpAction_eventWhenNotOverscrolled_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createDefaultUpActionEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true); HorizontalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }
@Before public void setUp() throws Exception { mView = mock(View.class); mViewAdapter = mock(IOverScrollDecoratorAdapter.class); when(mViewAdapter.getView()).thenReturn(mView); mStateListener = mock(IOverScrollStateListener.class); mUpdateListener = mock(IOverScrollUpdateListener.class); }
@Test public void onTouchMoveAction_dragDownInUpperEnd_overscrollDownwards() throws Exception { // Arrange MotionEvent event = createShortDownwardsMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); VerticalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert float expectedTransY = (event.getY() - event.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD; verify(mView).setTranslationY(expectedTransY); verify(mView, never()).setTranslationX(anyFloat()); assertTrue(ret); assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState()); verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE)); verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransY)); }
protected Animator createAnimator() { final View view = mViewAdapter.getView(); mAnimAttributes.init(view); // Set up a low-duration slow-down animation IN the drag direction. // Exception: If wasn't dragging in 'forward' direction (or velocity=0 -- i.e. not dragging at all), // skip slow-down anim directly to the bounce-back. if (mVelocity == 0f || (mVelocity < 0 && mStartAttr.mDir) || (mVelocity > 0 && !mStartAttr.mDir)) { return createBounceBackAnimator(mAnimAttributes.mAbsOffset); } // dt = (Vt - Vo) / a; Vt=0 ==> dt = -Vo / a float slowdownDuration = -mVelocity / mDecelerateFactor; slowdownDuration = (slowdownDuration < 0 ? 0 : slowdownDuration); // Happens in counter-direction dragging // dx = (Vt^2 - Vo^2) / 2a; Vt=0 ==> dx = -Vo^2 / 2a float slowdownDistance = -mVelocity * mVelocity / mDoubleDecelerateFactor; float slowdownEndOffset = mAnimAttributes.mAbsOffset + slowdownDistance; ObjectAnimator slowdownAnim = createSlowdownAnimator(view, (int) slowdownDuration, slowdownEndOffset); // Set up the bounce back animation, bringing the view back into the original, pre-overscroll position (translation=0). ObjectAnimator bounceBackAnim = createBounceBackAnimator(slowdownEndOffset); // Play the 2 animations as a sequence. AnimatorSet wholeAnim = new AnimatorSet(); wholeAnim.playSequentially(slowdownAnim, bounceBackAnim); return wholeAnim; }
@Test public void onTouchMoveAction_dragLeftInRightEnd_overscrollLeft() throws Exception { // Arrange MotionEvent event = createShortLeftMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(false); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true); HorizontalOverScrollBounceEffectDecorator uut = getUUT(); // Act final boolean ret = uut.onTouch(mView, event); // Assert final float expectedTransX = (event.getX() - event.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD; verify(mView).setTranslationX(expectedTransX); verify(mView, never()).setTranslationY(anyFloat()); assertTrue(ret); assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState()); verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE)); verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransX)); }
final View view = mViewAdapter.getView(); if (!mMoveAttr.init(view, event)) {
@Test public void onTouchMoveAction_dragUpInUpperEnd_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createShortUpwardsMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); VerticalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }
@Test public void onTouchMoveAction_dragLeftInLeftEnd_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createShortLeftMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); HorizontalOverScrollBounceEffectDecorator uut = getUUT(); // Act final boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }
@Test public void onTouchMoveAction_dragUpInBottomEnd_overscrollUpwards() throws Exception { // Arrange MotionEvent event = createShortUpwardsMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(false); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true); VerticalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert float expectedTransY = (event.getY() - event.getHistoricalY(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD; verify(mView).setTranslationY(expectedTransY); verify(mView, never()).setTranslationX(anyFloat()); assertTrue(ret); assertEquals(STATE_DRAG_END_SIDE, uut.getCurrentState()); verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_END_SIDE)); verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_END_SIDE), eq(expectedTransY)); }
@Test public void onTouchMoveAction_dragRightInLeftEnd_overscrollRight() throws Exception { // Arrange MotionEvent event = createShortRightMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); HorizontalOverScrollBounceEffectDecorator uut = getUUT(); // Act final boolean ret = uut.onTouch(mView, event); // Assert final float expectedTransX = (event.getX() - event.getHistoricalX(0)) / DEFAULT_TOUCH_DRAG_MOVE_RATIO_FWD; verify(mView).setTranslationX(expectedTransX); verify(mView, never()).setTranslationY(anyFloat()); assertTrue(ret); assertEquals(STATE_DRAG_START_SIDE, uut.getCurrentState()); verify(mStateListener).onOverScrollStateChange(eq(uut), eq(STATE_IDLE), eq(STATE_DRAG_START_SIDE)); verify(mUpdateListener).onOverScrollUpdate(eq(uut), eq(STATE_DRAG_START_SIDE), eq(expectedTransX)); }
@Test public void onTouchMoveAction_notInViewEnds_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createShortDownwardsMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(false); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(false); VerticalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut),anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }
@Test public void onTouchMoveAction_dragDownInBottomEnd_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createShortDownwardsMoveEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(false); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true); VerticalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }
@Test public void onTouchUpAction_eventWhenNotOverscrolled_ignoreTouchEvent() throws Exception { // Arrange MotionEvent event = createDefaultUpActionEvent(); when(mViewAdapter.isInAbsoluteStart()).thenReturn(true); when(mViewAdapter.isInAbsoluteEnd()).thenReturn(true); VerticalOverScrollBounceEffectDecorator uut = getUUT(); // Act boolean ret = uut.onTouch(mView, event); // Assert verify(mView, never()).setTranslationX(anyFloat()); verify(mView, never()).setTranslationY(anyFloat()); assertFalse(ret); assertEquals(STATE_IDLE, uut.getCurrentState()); verify(mStateListener, never()).onOverScrollStateChange(eq(uut), anyInt(), anyInt()); verify(mUpdateListener, never()).onOverScrollUpdate(eq(uut), anyInt(), anyFloat()); }