滑动抽屉中的缓动动画

Posted

技术标签:

【中文标题】滑动抽屉中的缓动动画【英文标题】: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>

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

【问题讨论】:

【参考方案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_
    android:layout_
    android:orientation="vertical">

    <!-- Some fragment with main content of activity screen -->
    <fragment
        android:layout_
        android:layout_
        class="com.alexstarc.testapp.VoteListFragment"
        android:tag="mainFragment"/>

    <com.alexstarc.testapp.MySlidingDrawer
        android:id="@+id/drawer"
        android:layout_
        android:layout_
        custom:handle="@+id/handle"
        custom:orientation="vertical"
        custom:content="@+id/content">

        <ImageView
            android:id="@id/handle"
            android:layout_
            android:layout_
            android:src="@android:drawable/ic_delete"/>

        <ImageView
            android:id="@id/content"
            android:layout_
            android:layout_
            android:src="@drawable/cover"
            android:scaleType="fitXY"/>
    </com.alexstarc.testapp.MySlidingDrawer>
    <!--
    <com.alexstarc.testapp.MySimpleSlidingDrawer
        android:id="@+id/drawer"
        android:layout_
        android:layout_
        custom:handle="@+id/handle"
        custom:content="@+id/content">

        <ImageView
            android:id="@id/handle"
            android:layout_
            android:layout_
            android:src="@android:drawable/ic_delete"/>

        <ImageView
            android:id="@id/content"
            android:layout_
            android:layout_
            android:src="@drawable/cover"
            android:scaleType="fitXY"/>
    </com.alexstarc.testapp.MySimpleSlidingDrawer>
    -->
</RelativeLayout>

【讨论】:

以上是关于滑动抽屉中的缓动动画的主要内容,如果未能解决你的问题,请参考以下文章

div盒子的缓动函数封装

WPF中ListBox滚动时的缓动效果

quick-cocos2dx 之transition.execute()的缓动效果

如何构建自定义 jQuery 缓动/弹跳动画?

svg animate中的缓动不起作用

聊聊CSS 缓动函数的新成员linear()