-->

How to smoothly animate a view up/down based on a

2019-08-08 20:58发布

问题:

I created a view that has a MapFragment that occupies the top 1/3 of the screen and a ListView that occupies the bottom 2/3 of the screen. The ListView has a "handle" directly above its list items that the user can use to drag the entire ListView up or down. Once the user releases their finger off of the screen the ListView should animate to the closet top or bottom border of the entire screen. I have it working so far but the animation is not smooth. There is a noticeable pause, after the user release their finger from the screen, before the ListView starts animating towards the closest edge. I'm using the nineoldandroids implementation of ObjectAnimator so that the animations work on pre-Honeycomb devices. Any ideas?

Here's my onTouch impl below:

public boolean onTouch(View v, MotionEvent event) {
    final LayoutParams listLp = (LayoutParams) mListFrame.getLayoutParams();
    final int topMargin = -mHandleShadow.getHeight();
    final int middleMargin = getResources().getDimensionPixelSize(R.dimen.map_handle_margin_top);
    final int bottomMargin = getView().getHeight() - mHandle.getHeight() - mHandleShadow.getHeight();

    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mInitY = (int) event.getRawY();
            mTopMargin = listLp.topMargin;

            break;
        case MotionEvent.ACTION_MOVE:
            int y = mTopMargin + (int) event.getRawY() - mInitY;

            if (y >= -mHandleShadow.getHeight() && y <= (mViewHeight - mHandle.getHeight() - mHandleShadow.getHeight())) {
                listLp.topMargin = y;
                mListFrame.setLayoutParams(listLp);
            }

            break;
        case MotionEvent.ACTION_UP:
            if ((mInitY > event.getRawY() && mClosestAnchor == Anchor.MIDDLE) || (listLp.topMargin < middleMargin && mClosestAnchor == Anchor.BOTTOM)) {
                ObjectAnimator animator = ObjectAnimator.ofInt(AnimatorProxy.wrap(mListFrame), "topMargin", topMargin);
                animator.setInterpolator(new AccelerateInterpolator());
                animator.start();

                mClosestAnchor = Anchor.TOP;
            }
            else if ((mInitY < event.getRawY() && mClosestAnchor == Anchor.MIDDLE) || (listLp.topMargin > middleMargin && mClosestAnchor == Anchor.TOP)) {
                ObjectAnimator animator = ObjectAnimator.ofInt(AnimatorProxy.wrap(mListFrame), "topMargin", bottomMargin);
                animator.setInterpolator(new AccelerateInterpolator());
                animator.start();

                mClosestAnchor = Anchor.BOTTOM;
            }
            else {
                ObjectAnimator animator = ObjectAnimator.ofInt(AnimatorProxy.wrap(mListFrame), "topMargin", middleMargin);
                animator.setInterpolator(new AccelerateInterpolator());
                animator.start();

                mClosestAnchor = Anchor.MIDDLE;
            }

            break;
    }

    return true;
}

回答1:

Finally fixed the issue after reading Google's Chet Haase's answer to a similar question found here. https://stackoverflow.com/a/14780019/476005

Adjusting a view's margin on every frame is too expensive and will always stutter unless the View you're moving is very simple. So to fix this, I do the following in my OnTouch(...) method:

  1. On MotionEvent.ACTION_DOWN: Set the View's height to it's maximum height.
  2. On MotionEvent.ACTION_MOVE: Animate the View's "y" property to the event.getRawY with a duration of 0.
  3. On MotionEvent.ACTION_UP: Animate the View's "y" property to one of the View's parent's edges. The important thing to do here is to set the height of the View appropriately in onAnimationEnd.

I hope this helps everyone.