从四个屏幕边的CustomDrawerLayout问题与飞掷手势和检测

我正在尝试创build和改进现有的SlidingDrawers项目,可以在屏幕的所有四个方面{左,右,上,下}工作。 有几个图书馆,但是,他们都有局限性,并发症和错误。 其中比较常见的就是umano的AndroidSlidingUpPanel,但是我不喜欢这个库,因为你只需要包含两个子布局,还需要注意对抽屉的主要内容的一个具体的安排。 其他库类似,或更复杂,或有错误。

我接近完成我的SlidingDrawers版本,我专注于底部重力。 我需要一些帮助的一举一动的手势。 点击抽屉会打开并closures它。 您也可以用手指滑动抽屉。 但是,如果你把抽屉扔掉了,那么整个观点就会变得比应该的还要低。

我该如何解决这个问题? 据我所知,我的math是正确的。 我传给我的animation师的翻译价值应该是正确的。 以下是我已完成的工作。 请检查这个项目https://github.com/drxeno02/CustomDrawerLayout.git 。 先谢谢你。

对于那些想看到有问题的代码片段的人来说,我现在正在做我的手势。

@Override public boolean onInterceptTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mActivePointerId = MotionEventCompat.getPointerId(event, 0); switch (mStickTo) { case GRAVITY_BOTTOM: case GRAVITY_TOP: mInitialCoordinate = event.getY(); break; case GRAVITY_LEFT: case GRAVITY_RIGHT: mInitialCoordinate = event.getX(); break; } break; case MotionEvent.ACTION_MOVE: float coordinate = 0; switch (mStickTo) { case GRAVITY_BOTTOM: case GRAVITY_TOP: coordinate = event.getY(); break; case GRAVITY_LEFT: case GRAVITY_RIGHT: coordinate = event.getX(); break; } final int diff = (int) Math.abs(coordinate - mInitialCoordinate); // confirm that difference is enough to indicate drag action if (diff > mTouchSlop) { // start capturing events Logger.d(TAG, "drag is being captured"); return true; } break; case MotionEvent.ACTION_UP: if (!FrameworkUtils.checkIfNull(mVelocityTracker)) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; } // add velocity movements if (FrameworkUtils.checkIfNull(mVelocityTracker)) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); return super.onInterceptTouchEvent(event); } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN && event.getEdgeFlags() != 0) { return false; } // add velocity movements if (FrameworkUtils.checkIfNull(mVelocityTracker)) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(event); final View parent = (View) getParent(); final int coordinate; final int distance = getDistance(); final int tapCoordinate; switch (mStickTo) { case GRAVITY_BOTTOM: coordinate = (int) event.getRawY(); tapCoordinate = (int) event.getRawY(); break; case GRAVITY_LEFT: coordinate = parent.getWidth() - (int) event.getRawX(); tapCoordinate = (int) event.getRawX(); break; case GRAVITY_RIGHT: coordinate = (int) event.getRawX(); tapCoordinate = (int) event.getRawX(); break; case GRAVITY_TOP: coordinate = getRawDisplayHeight(getContext()) - (int) event.getRawY(); tapCoordinate = (int) event.getRawY(); break; // if view position is not initialized throw an error default: throw new IllegalStateException("Failed to initialize coordinates"); } switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: /* * Return the pointer identifier associated with a particular pointer data index is * this event. The identifier tells you the actual pointer number associated with * the data, accounting for individual pointers going up and down since the start * of the current gesture. */ mActivePointerId = event.getPointerId(0); switch (mStickTo) { case GRAVITY_BOTTOM: mDelta = coordinate - ((RelativeLayout.LayoutParams) getLayoutParams()).topMargin; break; case GRAVITY_LEFT: mDelta = coordinate - ((RelativeLayout.LayoutParams) getLayoutParams()).rightMargin; break; case GRAVITY_RIGHT: mDelta = coordinate - ((RelativeLayout.LayoutParams) getLayoutParams()).leftMargin; break; case GRAVITY_TOP: mDelta = coordinate - ((RelativeLayout.LayoutParams) getLayoutParams()).bottomMargin; break; } mLastCoordinate = coordinate; mPressStartTime = System.currentTimeMillis(); break; case MotionEvent.ACTION_MOVE: RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams(); final int farMargin = coordinate - mDelta; final int closeMargin = distance - farMargin; switch (mStickTo) { case GRAVITY_BOTTOM: if (farMargin > distance && closeMargin > mOffsetHeight - getHeight()) { layoutParams.bottomMargin = closeMargin; layoutParams.topMargin = farMargin; } break; case GRAVITY_LEFT: if (farMargin > distance && closeMargin > mOffsetHeight - getWidth()) { layoutParams.leftMargin = closeMargin; layoutParams.rightMargin = farMargin; } break; case GRAVITY_RIGHT: if (farMargin > distance && closeMargin > mOffsetHeight - getWidth()) { layoutParams.rightMargin = closeMargin; layoutParams.leftMargin = farMargin; } break; case GRAVITY_TOP: if (farMargin > distance && closeMargin > mOffsetHeight - getHeight()) { layoutParams.topMargin = closeMargin; layoutParams.bottomMargin = farMargin; } break; } setLayoutParams(layoutParams); break; case MotionEvent.ACTION_UP: final int diff = coordinate - mLastCoordinate; final long pressDuration = System.currentTimeMillis() - mPressStartTime; switch (mStickTo) { case GRAVITY_BOTTOM: // determine if fling int relativeVelocity; final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocityY = (int) VelocityTrackerCompat.getYVelocity( velocityTracker, mActivePointerId); relativeVelocity = initialVelocityY * -1; // take absolute value to have positive values final int absoluteVelocity = Math.abs(relativeVelocity); if (Math.abs(diff) > mFlingDistance && absoluteVelocity > mMinimumVelocity) { if (tapCoordinate > parent.getHeight() - mOffsetHeight && mLockMode == LockMode.LOCK_MODE_CLOSED) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, parent.getHeight() - mOffsetHeight, true); } else if (Math.abs(getRawDisplayHeight(getContext()) - tapCoordinate - getHeight()) < mOffsetHeight && mLockMode == LockMode.LOCK_MODE_OPEN) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_CLOSED, parent.getHeight() - mOffsetHeight, true); } } else { if (isClicked(getContext(), diff, pressDuration)) { if (tapCoordinate > parent.getHeight() - mOffsetHeight && mLockMode == LockMode.LOCK_MODE_CLOSED) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, parent.getHeight() - mOffsetHeight, true); } else if (Math.abs(getRawDisplayHeight(getContext()) - tapCoordinate - getHeight()) < mOffsetHeight && mLockMode == LockMode.LOCK_MODE_OPEN) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_CLOSED, parent.getHeight() - mOffsetHeight, true); } } else { smoothScrollToAndNotify(diff); } } break; case GRAVITY_TOP: if (isClicked(getContext(), diff, pressDuration)) { final int y = getLocationInYAxis(this); if (tapCoordinate - Math.abs(y) <= mOffsetHeight && mLockMode == LockMode.LOCK_MODE_CLOSED) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, parent.getHeight() - mOffsetHeight, true); } else if (getHeight() - (tapCoordinate - Math.abs(y)) < mOffsetHeight && mLockMode == LockMode.LOCK_MODE_OPEN) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_CLOSED, parent.getHeight() - mOffsetHeight, true); } } else { smoothScrollToAndNotify(diff); } break; case GRAVITY_LEFT: if (isClicked(getContext(), diff, pressDuration)) { if (tapCoordinate <= mOffsetHeight && mLockMode == LockMode.LOCK_MODE_CLOSED) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, getWidth() - mOffsetHeight, true); } else if (tapCoordinate > getWidth() - mOffsetHeight && mLockMode == LockMode.LOCK_MODE_OPEN) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_CLOSED, getWidth() - mOffsetHeight, true); } } else { smoothScrollToAndNotify(diff); } break; case GRAVITY_RIGHT: if (isClicked(getContext(), diff, pressDuration)) { if (parent.getWidth() - tapCoordinate <= mOffsetHeight && mLockMode == LockMode.LOCK_MODE_CLOSED) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, getWidth() - mOffsetHeight, true); } else if (parent.getWidth() - tapCoordinate > getWidth() - mOffsetHeight && mLockMode == LockMode.LOCK_MODE_OPEN) { notifyActionAndAnimateForState(LockMode.LOCK_MODE_CLOSED, getWidth() - mOffsetHeight, true); } } else { smoothScrollToAndNotify(diff); } break; } break; } return true; } /** * Method is used to animate the view to the given position * * @param diff */ private void smoothScrollToAndNotify(int diff) { int length = getLength(); LockMode stateToApply; if (diff > 0) { if (diff > length / 2.5) { stateToApply = LockMode.LOCK_MODE_CLOSED; notifyActionAndAnimateForState(stateToApply, getTranslationFor(stateToApply), true); } else if (mLockMode == LockMode.LOCK_MODE_OPEN) { stateToApply = LockMode.LOCK_MODE_OPEN; notifyActionAndAnimateForState(stateToApply, getTranslationFor(stateToApply), false); } } else { if (Math.abs(diff) > length / 2.5) { stateToApply = LockMode.LOCK_MODE_OPEN; notifyActionAndAnimateForState(stateToApply, getTranslationFor(stateToApply), true); } else if (mLockMode == LockMode.LOCK_MODE_CLOSED) { stateToApply = LockMode.LOCK_MODE_CLOSED; notifyActionAndAnimateForState(stateToApply, getTranslationFor(stateToApply), false); } } } /** * Method is used to retrieve dimensions meant for translation * * @param stateToApply * @return */ private int getTranslationFor(LockMode stateToApply) { switch (mStickTo) { case GRAVITY_BOTTOM: switch (stateToApply) { case LOCK_MODE_OPEN: return getHeight() - (getRawDisplayHeight(getContext()) - getLocationInYAxis(this)); case LOCK_MODE_CLOSED: return getRawDisplayHeight(getContext()) - getLocationInYAxis(this) - mOffsetHeight; } break; case GRAVITY_TOP: final int actionBarDiff = getRawDisplayHeight(getContext()) - ((View) getParent()).getHeight(); final int y = getLocationInYAxis(this) + getHeight(); switch (stateToApply) { case LOCK_MODE_OPEN: return getHeight() - y + actionBarDiff; case LOCK_MODE_CLOSED: return y - mOffsetHeight - actionBarDiff; } break; case GRAVITY_LEFT: final int x = getLocationInXAxis(this) + getWidth(); switch (stateToApply) { case LOCK_MODE_OPEN: return getWidth() - x; case LOCK_MODE_CLOSED: return x - mOffsetHeight; } break; case GRAVITY_RIGHT: switch (stateToApply) { case LOCK_MODE_OPEN: return getWidth() - (getRawDisplayWidth(getContext()) - getLocationInXAxis(this)); case LOCK_MODE_CLOSED: return getRawDisplayWidth(getContext()) - getLocationInXAxis(this) - mOffsetHeight; } break; } throw new IllegalStateException("Failed to return translation for drawer"); } /** * Method is used to perform the animations * * @param stateToApply * @param translation * @param notify */ private void notifyActionAndAnimateForState(final LockMode stateToApply, final int translation, final boolean notify) { switch (mStickTo) { case GRAVITY_BOTTOM: switch (stateToApply) { case LOCK_MODE_OPEN: animate().translationY(-translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationY(0); } }); break; case LOCK_MODE_CLOSED: animate().translationY(translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationY(0); } }); break; } break; case GRAVITY_TOP: switch (stateToApply) { case LOCK_MODE_OPEN: animate().translationY(translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationY(0); } }); break; case LOCK_MODE_CLOSED: animate().translationY(-translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationY(0); } }); break; } break; case GRAVITY_LEFT: switch (stateToApply) { case LOCK_MODE_OPEN: animate().translationX(translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationX(0); } }); break; case LOCK_MODE_CLOSED: animate().translationX(-translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationX(0); } }); break; } break; case GRAVITY_RIGHT: switch (stateToApply) { case LOCK_MODE_OPEN: animate().translationX(-translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationX(0); } }); break; case LOCK_MODE_CLOSED: animate().translationX(translation) .setDuration(TRANSLATION_ANIM_DURATION) .setInterpolator(new DecelerateInterpolator()) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); notifyActionForState(stateToApply, notify); setTranslationX(0); } }); break; } break; } } 

补充说明:我对这个问题有进一步的了解。 我将ACTION_MOVE注释掉,以消除在投掷前已经移动的抽屉的位置。 animation完美。 我相信我的想法是正确的。 为了获得“开放”的翻译我做

 getHeight() - (getRawDisplayHeight(getContext()) - getLocationInYAxis(this)) 
  • getHeight()是抽屉的高度
  • getRawDisplayHeight(getContext())是设备屏幕的高度
  • getLocationInYAxis(this),当ACTION_MOVE超出图片时,是有效的(getRawDisplayHeight(getContext()) – mOffsetHeight)。 在
    这种情况下他们应该是可以互换的。

所以,剩下的就是翻译所需的距离。 一旦抽屉已被拖拽X距离,但是,我期待getLocationInYAxis(this)将我拖回的位置。 但计算结果是closures的。

似乎应该使用getTranslationFor函数来计算状态的新翻译。

您目前只考虑高度和偏移量,但是从getTranslationFor代码看来,您应该考虑getLocationInYAxis

所以,而不是这一行:

 notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, parent.getHeight() - mOffsetHeight, true); 

试试这一行:

 notifyActionAndAnimateForState(LockMode.LOCK_MODE_OPEN, getTranslationFor(LockMode.LOCK_MODE_OPEN), true);