滑动抽屉中的缓动动画
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>
【讨论】:
以上是关于滑动抽屉中的缓动动画的主要内容,如果未能解决你的问题,请参考以下文章