【问题标题】:Ease animation in slidingdrawer滑动抽屉中的缓动动画
【发布时间】:2014-07-29 02:12:44
【问题描述】:

我想使用插值器(DecelerateInterpolator)减慢 SlidingDrawer 的打开速度 这可能吗。我想实现缓动动画。

final Animation slowDown = AnimationUtils.loadAnimation(((Swipe)getActivity()), R.anim.ease);

这是 XML

<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator">
<translate
    android:fromYDelta="0"
    android:toYDelta="100%p"
    android:duration="2000"/>
</set>

使用这个我没有得到我想要的。

【问题讨论】:

    标签: android android-animation interpolation slidingdrawer


    【解决方案1】:

    实际上,如果您只想打开/关闭该滑动布局,我建议您使用自定义ViewGroup 而不是弃用的SlidingDrawer。下面是该实现的一些简单示例:

    public class MySimpleSlidingDrawer extends RelativeLayout implements View.OnClickListener {
    
        private static final int SLIDING_TIME = 500;
    
        private View mHandle;
        private int mHandleId;
        private View mContent;
        private int mContentId;
    
        private ObjectAnimator mOpenAnimator = ObjectAnimator.ofFloat(this, "slide", 0f, 1f);
        private ObjectAnimator mCloseAnimator = ObjectAnimator.ofFloat(this, "slide", 1f, 0f);
    
        private int mAnimationTime = SLIDING_TIME;
        private boolean mOpened = false;
    
        private int mSlideHeight = 0;
    
        public MySimpleSlidingDrawer(final Context context) {
            super(context);
            init(context, null, 0);
        }
    
        public MySimpleSlidingDrawer(final Context context, final AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs, 0);
        }
    
        public MySimpleSlidingDrawer(final Context context, final AttributeSet attrs, final int defStyle) {
            super(context, attrs, defStyle);
            init(context, attrs, defStyle);
        }
    
        private void init(final Context context, final AttributeSet attrs, final int defStyle) {
            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MySlidingDrawer, defStyle, 0);
    
            mHandleId = a.getResourceId(R.styleable.MySlidingDrawer_handle, 0);
            mContentId = a.getResourceId(R.styleable.MySlidingDrawer_content, 0);
    
            mOpenAnimator.setInterpolator(new AccelerateInterpolator());
            mOpenAnimator.setDuration(SLIDING_TIME);
            mCloseAnimator.setInterpolator(new DecelerateInterpolator());
            mCloseAnimator.setDuration(SLIDING_TIME);
    
            setClipChildren(false);
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
    
            if (getChildCount() != 2) {
                throw new InflateException("Only to child are supported for this layout");
            }
    
            if (mHandleId != 0) {
                mHandle = findViewById(mHandleId);
            } else {
                mHandle = getChildAt(0);
            }
    
            if (mContentId != 0) {
                mContent = findViewById(mContentId);
            } else {
                mContent = getChildAt(1);
            }
    
            final LayoutParams handleParams = (LayoutParams) mHandle.getLayoutParams();
    
            handleParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
            handleParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
            handleParams.addRule(ALIGN_PARENT_BOTTOM, 1/* true */);
            handleParams.addRule(CENTER_HORIZONTAL, 1/* true */);
            mHandle.setLayoutParams(handleParams);
    
            mHandle.setOnClickListener(this);
        }
    
        @Override
        public void onClick(final View v) {
            if (mSlideHeight == 0) {
                mSlideHeight = getHeight() - mHandle.getHeight();
            }
    
            // Handle have been clicked. Execute animation depending on open / close state
            if (!mOpened) {
                mOpened = true;
    
                mCloseAnimator.cancel();
                mOpenAnimator.start();
            } else {
                mOpened = false;
    
                mOpenAnimator.cancel();
                mCloseAnimator.start();
            }
        }
    
        /**
         * Sets slide percent value
         *
         * @param slidePercent % of slide (0 - closed, 1 - opened)
         */
        @SuppressWarnings("UnusedDeclaration")
        public void setSlide(final float slidePercent) {
            final LayoutParams handleParams = (LayoutParams) mHandle.getLayoutParams();
    
            handleParams.bottomMargin = (int) (slidePercent * mSlideHeight);
            mHandle.setLayoutParams(handleParams);
    
            final LayoutParams contentParams = (LayoutParams) mContent.getLayoutParams();
    
            contentParams.bottomMargin = (int) -((1- slidePercent) * mSlideHeight);
            mContent.setLayoutParams(contentParams);
        }
    
        /**
         * Sets open interpolator
         *
         * @param interpolator {@link android.view.animation.Interpolator} for open animation
         */
        @SuppressWarnings("UnusedDeclaration")
        public void setOpenInterpolator(final Interpolator interpolator) {
            if (mOpenAnimator.isRunning()) {
                mOpenAnimator.cancel();
            }
    
            mOpenAnimator.setInterpolator(interpolator);
        }
    
        /**
         * Sets close interpolator
         *
         * @param interpolator {@link android.view.animation.Interpolator} for close animation
         */
        @SuppressWarnings("UnusedDeclaration")
        public void setCloseInterpolator(final Interpolator interpolator) {
            if (mCloseAnimator.isRunning()) {
                mCloseAnimator.cancel();
            }
    
            mCloseAnimator.setInterpolator(interpolator);
        }
    
        @Override
        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            // Trick to avoid content to be resized - measure it as it visible
            final int contentHeightMeasure = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - mHandle.getMeasuredHeight(), MeasureSpec.EXACTLY);
            final int contentWidthMeasure = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
    
            mContent.measure(contentHeightMeasure, contentWidthMeasure);
    
            if (mSlideHeight == 0) {
                mSlideHeight = getMeasuredHeight() - mHandle.getMeasuredHeight();
    
                final LayoutParams contentParams = (LayoutParams) mContent.getLayoutParams();
    
                contentParams.height = mSlideHeight;
                contentParams.addRule(ALIGN_PARENT_BOTTOM, 1 /* true */);
                contentParams.bottomMargin = - mSlideHeight;
    
                mContent.setLayoutParams(contentParams);
            }
        }
    }
    

    注意:它没有经过大量优化或测试,但基本功能有效。

    另一种方法是根据您的需要调整现有的SlidingDrawer 代码。对我来说,它看起来并不那么容易或灵活,因为它现有的实现是特定的。首先在SlidingDrawer文档中明确提到:

    不再支持此类。建议您根据自己的 Android开源源代码的自己的实现 如果您必须在您的应用程序中使用它,请进行项目。

    SlidingDrawer 没有公开动画更改 API。主要问题是根本没有动画,只是发送了一些计时事件来更新视图位置,这里:

    private void doAnimation() {
        if (mAnimating) {
            incrementAnimation();
            if (mAnimationPosition >= mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1) {
                mAnimating = false;
                closeDrawer();
            } else if (mAnimationPosition < mTopOffset) {
                mAnimating = false;
                openDrawer();
            } else {
                moveHandle((int) mAnimationPosition);
                mCurrentAnimationTime += ANIMATION_FRAME_DURATION;
                mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_ANIMATE),
                        mCurrentAnimationTime);
            }
        }
    }
    

    因此,为了修改插值,您需要更改动画逻辑或提供您自己的。第二种方式更安全,因为不影响现有逻辑。下面是一些如何做的草稿变体(检测与原始SlidingDrawer 比较的确切变化,在您的 android SDK 安装中使用原始 API 19 版本打开下面的类差异),下面仅显示更改的代码:

    public class MySlidingDrawer extends ViewGroup {
    
        /** Click animation duration */
        // TODO: ideally you should properly calculate that value
        private static final long CLICK_ANIMATION_DURATION = 1000;
    
        /** New field for custom interpolator */
        private TimeInterpolator mAnimationInterpolator = new BounceInterpolator();
        /** just to distinguish click and moving by user animations */
        private boolean mAnimatedClick = false;
        /** Specific click animator */
        private ObjectAnimator mClickToggleAnimation;
        /** Specific listener just to handle animation end properly */
        private Animator.AnimatorListener mClickAnimationListener = new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(final Animator animation) {
                // nothing to do here
            }
    
            @Override
            public void onAnimationEnd(final Animator animation) {
                mAnimating = false;
                // Determine if it close or open, by comparing to some final value
                if (mAnimationPosition == mTopOffset) {
                    openDrawer();
                } else {
                    closeDrawer();
                }
            }
    
            @Override
            public void onAnimationCancel(final Animator animation) {
                // TODO: should be handled properly
            }
    
            @Override
            public void onAnimationRepeat(final Animator animation) {
    
            }
        };
    
        ...
    
        /**
         * Creates a new SlidingDrawer from a specified set of attributes defined in XML.
         *
         * @param context The application's environment.
         * @param attrs The attributes defined in XML.
         */
        public MySlidingDrawer(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        ...
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (mLocked) {
                return true;
            }
    
            if (mTracking) {
                mVelocityTracker.addMovement(event);
                final int action = event.getAction();
                switch (action) {
                    case MotionEvent.ACTION_MOVE:
                        moveHandle((int) (mVertical ? event.getY() : event.getX()) - mTouchDelta);
                        break;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL: {
                        final VelocityTracker velocityTracker = mVelocityTracker;
                        velocityTracker.computeCurrentVelocity(mVelocityUnits);
    
                        float yVelocity = velocityTracker.getYVelocity();
                        float xVelocity = velocityTracker.getXVelocity();
                        boolean negative;
    
                        final boolean vertical = mVertical;
                        if (vertical) {
                            negative = yVelocity < 0;
                            if (xVelocity < 0) {
                                xVelocity = -xVelocity;
                            }
                            if (xVelocity > mMaximumMinorVelocity) {
                                xVelocity = mMaximumMinorVelocity;
                            }
                        } else {
                            negative = xVelocity < 0;
                            if (yVelocity < 0) {
                                yVelocity = -yVelocity;
                            }
                            if (yVelocity > mMaximumMinorVelocity) {
                                yVelocity = mMaximumMinorVelocity;
                            }
                        }
    
                        float velocity = (float) Math.hypot(xVelocity, yVelocity);
                        if (negative) {
                            velocity = -velocity;
                        }
    
                        final int top = mHandle.getTop();
                        final int left = mHandle.getLeft();
    
                        if (Math.abs(velocity) < mMaximumTapVelocity) {
                            if (vertical ? (mExpanded && top < mTapThreshold + mTopOffset) ||
                                    (!mExpanded && top > mBottomOffset + getBottom() - getTop() -
                                            mHandleHeight - mTapThreshold) :
                                    (mExpanded && left < mTapThreshold + mTopOffset) ||
                                            (!mExpanded && left > mBottomOffset + getRight() - getLeft() -
                                                    mHandleWidth - mTapThreshold)) {
    
                                if (mAllowSingleTap) {
                                    playSoundEffect(SoundEffectConstants.CLICK);
    
                                    animateToggle();
                                    /*
                                    if (mExpanded) {
                                        animateClose(vertical ? top : left);
                                    } else {
                                        animateOpen(vertical ? top : left);
                                    }
                                    */
                                } else {
                                    performFling(vertical ? top : left, velocity, false);
                                }
    
                            } else {
                                performFling(vertical ? top : left, velocity, false);
                            }
                        } else {
                            performFling(vertical ? top : left, velocity, false);
                        }
                    }
                    break;
                }
            }
    
            return mTracking || mAnimating || super.onTouchEvent(event);
        }
    
        ...
    
        /**
         * Toggles the drawer open and close with an animation.
         *
         * @see #open()
         * @see #close()
         * @see #animateClose()
         * @see #animateOpen()
         * @see #toggle()
         */
        public void animateToggle() {
            mAnimatedClick = true;
            if (!mExpanded) {
                animateClickOpen();
            } else {
                animateClickClose();
            }
        }
    
        /**
         * For doing our animation for close
         */
        private void animateClickClose() {
            mAnimating = true;
            mClickToggleAnimation = ObjectAnimator.ofInt(this, "togglePosition", (int) mAnimationPosition, mBottomOffset + (mVertical ? getHeight() : getWidth()) - 1);
            mClickToggleAnimation.setInterpolator(mAnimationInterpolator);
            mClickToggleAnimation.setDuration(CLICK_ANIMATION_DURATION);
            mClickToggleAnimation.addListener(mClickAnimationListener);
            mClickToggleAnimation.start();
        }
    
        /**
         * For doing our animation for open
         */
        private void animateClickOpen() {
            mAnimating = true;
            mClickToggleAnimation = ObjectAnimator.ofInt(this, "togglePosition", (int)mAnimationPosition, mTopOffset);
            mClickToggleAnimation.setInterpolator(mAnimationInterpolator);
            mClickToggleAnimation.setDuration(CLICK_ANIMATION_DURATION);
            mClickToggleAnimation.addListener(mClickAnimationListener);
            mClickToggleAnimation.start();
        }
    
        /**
         * Sets current animation position
         *
         * @param position to be set
         */
        @SuppressWarnings("UnusedDeclaration")
        public void setTogglePosition(final int position) {
            mAnimationPosition = position;
            moveHandle((int) mAnimationPosition);
        }
    
        ...
    
        private class DrawerToggler implements OnClickListener {
            public void onClick(View v) {
                if (mLocked) {
                    return;
                }
                // mAllowSingleTap isn't relevant here; you're *always*
                // allowed to open/close the drawer by clicking with the
                // trackball.
    
                if (mAnimateOnClick) {
                    animateToggle();
                } else {
                    toggle();
                }
            }
        }
    
        ...
    
        /**
         * New API to modify timing of interpolator
         *
         * @param interpolator {@link android.animation.TimeInterpolator} to be used for onClick open / close
         *
         * TODO: it's also possible to add XML attribute for the same
         */
        public void setAnimationInterpolator(final TimeInterpolator interpolator) {
            mAnimationInterpolator = interpolator;
        }
    }
    

    还有我用来测试的 xml(只应启用一个滑动抽屉部分):

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    xmlns:custom="http://schemas.android.com/apk/res/com.alexstarc.testapp"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <!-- Some fragment with main content of activity screen -->
        <fragment
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            class="com.alexstarc.testapp.VoteListFragment"
            android:tag="mainFragment"/>
    
        <com.alexstarc.testapp.MySlidingDrawer
            android:id="@+id/drawer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            custom:handle="@+id/handle"
            custom:orientation="vertical"
            custom:content="@+id/content">
    
            <ImageView
                android:id="@id/handle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@android:drawable/ic_delete"/>
    
            <ImageView
                android:id="@id/content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/cover"
                android:scaleType="fitXY"/>
        </com.alexstarc.testapp.MySlidingDrawer>
        <!--
        <com.alexstarc.testapp.MySimpleSlidingDrawer
            android:id="@+id/drawer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            custom:handle="@+id/handle"
            custom:content="@+id/content">
    
            <ImageView
                android:id="@id/handle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@android:drawable/ic_delete"/>
    
            <ImageView
                android:id="@id/content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:src="@drawable/cover"
                android:scaleType="fitXY"/>
        </com.alexstarc.testapp.MySimpleSlidingDrawer>
        -->
    </RelativeLayout>
    

    【讨论】:

      猜你喜欢
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 2013-07-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多